@pyscript/core 0.4.55 → 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.
Files changed (52) hide show
  1. package/dist/{codemirror-uEsvwGeX.js → codemirror-ZWX__zRS.js} +2 -2
  2. package/dist/{codemirror-uEsvwGeX.js.map → codemirror-ZWX__zRS.js.map} +1 -1
  3. package/dist/{codemirror_commands-BjErX5JV.js → codemirror_commands-BvofQ4B2.js} +2 -2
  4. package/dist/{codemirror_commands-BjErX5JV.js.map → codemirror_commands-BvofQ4B2.js.map} +1 -1
  5. package/dist/{codemirror_lang-python-rjOo-iGF.js → codemirror_lang-python-CAjFAwEr.js} +2 -2
  6. package/dist/{codemirror_lang-python-rjOo-iGF.js.map → codemirror_lang-python-CAjFAwEr.js.map} +1 -1
  7. package/dist/{codemirror_language-oTnz5LmX.js → codemirror_language-BzugrQDC.js} +2 -2
  8. package/dist/{codemirror_language-oTnz5LmX.js.map → codemirror_language-BzugrQDC.js.map} +1 -1
  9. package/dist/codemirror_view-DrrFKRyn.js +2 -0
  10. package/dist/codemirror_view-DrrFKRyn.js.map +1 -0
  11. package/dist/core-jHCq6GXZ.js +2 -0
  12. package/dist/core-jHCq6GXZ.js.map +1 -0
  13. package/dist/core.js +1 -1
  14. package/dist/{deprecations-manager-BsD6IG0e.js → deprecations-manager-JLWNpJ-L.js} +2 -2
  15. package/dist/{deprecations-manager-BsD6IG0e.js.map → deprecations-manager-JLWNpJ-L.js.map} +1 -1
  16. package/dist/{error-BwsFl_vB.js → error-BZdmXuMQ.js} +2 -2
  17. package/dist/{error-BwsFl_vB.js.map → error-BZdmXuMQ.js.map} +1 -1
  18. package/dist/{index-DvH26nkT.js → index-YJCpGJSa.js} +2 -2
  19. package/dist/{index-DvH26nkT.js.map → index-YJCpGJSa.js.map} +1 -1
  20. package/dist/{mpy-Cq6_SATz.js → mpy-CA-8GLzg.js} +2 -2
  21. package/dist/mpy-CA-8GLzg.js.map +1 -0
  22. package/dist/{py-editor-CO1XYgQ3.js → py-editor-BFwOcA7b.js} +2 -2
  23. package/dist/{py-editor-CO1XYgQ3.js.map → py-editor-BFwOcA7b.js.map} +1 -1
  24. package/dist/{py-terminal-7BWBB6VM.js → py-terminal-GLATgrHP.js} +2 -2
  25. package/dist/{py-terminal-7BWBB6VM.js.map → py-terminal-GLATgrHP.js.map} +1 -1
  26. package/dist/{py-Dpy4_-ky.js → py-tfDQPNNm.js} +2 -2
  27. package/dist/{py-Dpy4_-ky.js.map → py-tfDQPNNm.js.map} +1 -1
  28. package/dist/{zip-BxogYCy6.js → zip-CGWtiqjJ.js} +2 -2
  29. package/dist/zip-CGWtiqjJ.js.map +1 -0
  30. package/package.json +8 -8
  31. package/src/plugins/py-terminal/mpy.js +6 -5
  32. package/src/stdlib/pyscript/event_handling.py +13 -12
  33. package/src/stdlib/pyscript/magic_js.py +4 -12
  34. package/src/stdlib/pyscript/web/__init__.py +21 -4
  35. package/src/stdlib/pyscript/web/elements.py +919 -724
  36. package/src/stdlib/pyscript.js +4 -15
  37. package/types/core.d.ts +1 -1
  38. package/types/stdlib/pyscript.d.ts +0 -11
  39. package/dist/codemirror_view-CH_aW-qU.js +0 -2
  40. package/dist/codemirror_view-CH_aW-qU.js.map +0 -1
  41. package/dist/core-Di0g8mUD.js +0 -3
  42. package/dist/core-Di0g8mUD.js.map +0 -1
  43. package/dist/mpy-Cq6_SATz.js.map +0 -1
  44. package/dist/zip-BxogYCy6.js.map +0 -1
  45. package/dist.zip +0 -0
  46. package/src/stdlib/pyscript/web/dom.py +0 -21
  47. package/src/stdlib/pyscript/web/media.py +0 -95
  48. package/src/stdlib/pyweb/__init__.py +0 -2
  49. package/src/stdlib/pyweb/media.py +0 -95
  50. package/src/stdlib/pyweb/pydom.py +0 -569
  51. package/src/stdlib/pyweb/ui/__init__.py +0 -1
  52. package/src/stdlib/pyweb/ui/elements.py +0 -947
@@ -1,3 +1,4 @@
1
+ # noinspection PyPep8Naming
1
2
  import inspect
2
3
  import sys
3
4
 
@@ -18,23 +19,7 @@ except ImportError:
18
19
  print("WARNING: ", *args, **kwargs)
19
20
 
20
21
 
21
- try:
22
- from functools import cached_property
23
- except ImportError:
24
- # TODO: same comment about micropython as above
25
- cached_property = property
26
-
27
- try:
28
- from pyodide.ffi import JsProxy
29
- except ImportError:
30
- # TODO: same comment about micropython as above
31
- def JsProxy(obj):
32
- return obj
33
-
34
-
35
- from pyscript import document, window
36
-
37
- # from pyscript.web import dom as pydom
22
+ from pyscript import document
38
23
 
39
24
  #: A flag to show if MicroPython is the current Python interpreter.
40
25
  is_micropython = "MicroPython" in sys.version
@@ -49,330 +34,331 @@ def getmembers_static(cls):
49
34
  return inspect.getmembers_static(cls)
50
35
 
51
36
 
52
- class JSProperty:
53
- """JS property descriptor that directly maps to the property with the same
54
- name in the underlying JS component."""
37
+ class DOMProperty:
38
+ """A descriptor representing a DOM property on an `Element` instance.
39
+
40
+ This maps a property on an `Element` instance, to the property with the specified
41
+ name on the element's underlying DOM element.
42
+ """
55
43
 
56
44
  def __init__(self, name: str, allow_nones: bool = False):
57
45
  self.name = name
58
46
  self.allow_nones = allow_nones
59
47
 
60
48
  def __get__(self, obj, objtype=None):
61
- return getattr(obj._js, self.name)
49
+ return getattr(obj._dom_element, self.name)
62
50
 
63
51
  def __set__(self, obj, value):
64
52
  if not self.allow_nones and value is None:
65
53
  return
66
- setattr(obj._js, self.name, value)
54
+ setattr(obj._dom_element, self.name, value)
67
55
 
68
56
 
69
- # ------ TODO: REMOVE!!!! pydom elements
57
+ class Element:
58
+ tag = "div"
70
59
 
60
+ # These are attribute that all elements have (this list is a subset of the official
61
+ # one - we are just trying to capture the most used ones).
62
+ accesskey = DOMProperty("accesskey")
63
+ autofocus = DOMProperty("autofocus")
64
+ autocapitalize = DOMProperty("autocapitalize")
65
+ className = DOMProperty("className")
66
+ contenteditable = DOMProperty("contenteditable")
67
+ draggable = DOMProperty("draggable")
68
+ enterkeyhint = DOMProperty("enterkeyhint")
69
+ hidden = DOMProperty("hidden")
70
+ innerHTML = DOMProperty("innerHTML")
71
+ id = DOMProperty("id")
72
+ lang = DOMProperty("lang")
73
+ nonce = DOMProperty("nonce")
74
+ part = DOMProperty("part")
75
+ popover = DOMProperty("popover")
76
+ slot = DOMProperty("slot")
77
+ spellcheck = DOMProperty("spellcheck")
78
+ tabindex = DOMProperty("tabindex")
79
+ tagName = DOMProperty("tagName")
80
+ textContent = DOMProperty("textContent")
81
+ title = DOMProperty("title")
82
+ translate = DOMProperty("translate")
83
+ virtualkeyboardpolicy = DOMProperty("virtualkeyboardpolicy")
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
+ """
71
98
 
72
- class BaseElement:
73
- def __init__(self, js_element):
74
- self._js = js_element
75
- self._parent = None
76
- self.style = StyleProxy(self)
77
- self._proxies = {}
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())
78
105
 
79
- def __eq__(self, obj):
80
- """Check if the element is the same as the other element by comparing
81
- the underlying JS element"""
82
- return isinstance(obj, BaseElement) and obj._js == self._js
106
+ else:
107
+ element_cls = ELEMENT_CLASSES_BY_TAG.get(dom_element.tagName.lower())
83
108
 
84
- @property
85
- def parent(self):
86
- if self._parent:
87
- return self._parent
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
88
113
 
89
- if self._js.parentElement:
90
- # TODO: This should actually return the correct class (== to tagName)
91
- self._parent = Element(self._js.parentElement)
114
+ return element_cls(dom_element=dom_element)
92
115
 
93
- return self._parent
116
+ def __init__(self, dom_element=None, classes=None, style=None, **kwargs):
117
+ """Create a new, or wrap an existing DOM element.
94
118
 
95
- # @property
96
- # def __class(self):
97
- # return self.__class__ if self.__class__ != PyDom else Element
119
+ If `dom_element` is None we are being called to *create* a new element.
120
+ Otherwise, we are being called to *wrap* an existing DOM element.
121
+ """
122
+ self._dom_element = dom_element or document.createElement(self.tag)
98
123
 
99
- def create(self, type_, is_child=True, classes=None, html=None, label=None):
100
- js_el = document.createElement(type_)
101
- element = self.__class(js_el)
124
+ # Tag the DOM element with our class name.
125
+ #
126
+ # Using the `dataset` attribute is how you programmatically add `data-xxx`
127
+ # attributes to a DOM element. In this case it will set an attribute that
128
+ # appears in the DOM as `data-pyscript-type`.
129
+ self._dom_element.dataset.pyscriptType = type(self).__name__
102
130
 
103
- if classes:
104
- for class_ in classes:
105
- element.add_class(class_)
131
+ self._parent = None
132
+ self._classes = Classes(self)
133
+ self._style = Style(self)
106
134
 
107
- if html is not None:
108
- element.html = html
135
+ # Set any specified classes, styles, and DOM properties.
136
+ self.update(classes=classes, style=style, **kwargs)
109
137
 
110
- if label is not None:
111
- element.label = label
138
+ def update(self, classes=None, style=None, **kwargs):
139
+ """Update the element with the specified classes, styles, and DOM properties."""
112
140
 
113
- if is_child:
114
- self.append(element)
141
+ if classes:
142
+ self.classes.add(classes)
115
143
 
116
- return element
144
+ if isinstance(style, dict):
145
+ self.style.set(**style)
117
146
 
118
- def find(self, selector):
119
- """Return an ElementCollection representing all the child elements that
120
- match the specified selector.
147
+ elif style is not None:
148
+ raise ValueError(
149
+ f"Style should be a dictionary, received {style} "
150
+ f"(type {type(style)}) instead."
151
+ )
121
152
 
122
- Args:
123
- selector (str): A string containing a selector expression
153
+ self._set_dom_properties(**kwargs)
124
154
 
125
- Returns:
126
- ElementCollection: A collection of elements matching the selector
155
+ def _set_dom_properties(self, **kwargs):
156
+ """Set the specified DOM properties.
157
+
158
+ Args:
159
+ **kwargs: The properties to set
127
160
  """
128
- elements = self._js.querySelectorAll(selector)
129
- if not elements:
130
- return None
131
- return ElementCollection([Element(el) for el in elements])
161
+ # Harvest all DOM properties from the instance's class.
162
+ dom_properties = {
163
+ attribute_name: attribute_value
164
+ for attribute_name, attribute_value in getmembers_static(self.__class__)
165
+ if isinstance(attribute_value, DOMProperty)
166
+ }
167
+
168
+ for name, value in kwargs.items():
169
+ if name not in dom_properties:
170
+ raise ValueError(f"'{name}' is not a DOM property.")
171
+
172
+ try:
173
+ setattr(self, name, value)
174
+ except Exception as e:
175
+ print(f"Error setting {name} to {value}: {e}")
176
+ raise
132
177
 
178
+ def __eq__(self, obj):
179
+ """Check for equality by comparing the underlying DOM element."""
180
+ return isinstance(obj, Element) and obj._dom_element == self._dom_element
133
181
 
134
- class Element(BaseElement):
135
182
  @property
136
183
  def children(self):
137
- return [self.__class__(el) for el in self._js.children]
184
+ return ElementCollection(
185
+ [Element.from_dom_element(el) for el in self._dom_element.children]
186
+ )
138
187
 
139
- def append(self, child):
140
- # TODO: this is Pyodide specific for now!!!!!!
141
- # if we get passed a JSProxy Element directly we just map it to the
142
- # higher level Python element
143
- if inspect.isclass(JsProxy) and isinstance(child, JsProxy):
144
- return self.append(Element(child))
145
-
146
- elif isinstance(child, Element):
147
- self._js.appendChild(child._js)
148
-
149
- return child
150
-
151
- elif isinstance(child, ElementCollection):
152
- for el in child:
153
- self.append(el)
188
+ @property
189
+ def classes(self):
190
+ return self._classes
154
191
 
155
- # -------- Pythonic Interface to Element -------- #
156
192
  @property
157
- def html(self):
158
- return self._js.innerHTML
193
+ def parent(self):
194
+ if self._parent:
195
+ return self._parent
196
+
197
+ if self._dom_element.parentElement:
198
+ self._parent = Element.from_dom_element(self._dom_element.parentElement)
159
199
 
160
- @html.setter
161
- def html(self, value):
162
- self._js.innerHTML = value
200
+ return self._parent
163
201
 
164
202
  @property
165
- def text(self):
166
- return self._js.textContent
203
+ def style(self):
204
+ return self._style
167
205
 
168
- @text.setter
169
- def text(self, value):
170
- self._js.textContent = value
206
+ def append(self, child):
207
+ if isinstance(child, Element):
208
+ self._dom_element.appendChild(child._dom_element)
171
209
 
172
- @property
173
- def content(self):
174
- # TODO: This breaks with with standard template elements. Define how to best
175
- # handle this specifica use case. Just not support for now?
176
- if self._js.tagName == "TEMPLATE":
177
- warnings.warn(
178
- "Content attribute not supported for template elements.", stacklevel=2
179
- )
180
- return None
181
- return self._js.innerHTML
182
-
183
- @content.setter
184
- def content(self, value):
185
- # TODO: (same comment as above)
186
- if self._js.tagName == "TEMPLATE":
187
- warnings.warn(
188
- "Content attribute not supported for template elements.", stacklevel=2
189
- )
190
- return
210
+ elif isinstance(child, ElementCollection):
211
+ for el in child:
212
+ self._dom_element.appendChild(el._dom_element)
191
213
 
192
- display(value, target=self.id)
214
+ else:
215
+ # In this case we know it's not an Element or an ElementCollection, so we
216
+ # guess that it's either a DOM element or NodeList returned via the ffi.
217
+ try:
218
+ # First, we try to see if it's an element by accessing the 'tagName'
219
+ # attribute.
220
+ child.tagName
221
+ self._dom_element.appendChild(child)
222
+
223
+ except AttributeError:
224
+ try:
225
+ # Ok, it's not an element, so let's see if it's a NodeList by
226
+ # accessing the 'length' attribute.
227
+ child.length
228
+ for element_ in child:
229
+ self._dom_element.appendChild(element_)
230
+
231
+ except AttributeError:
232
+ # Nope! This is not an element or a NodeList.
233
+ raise TypeError(
234
+ f'Element "{child}" is a proxy object, "'
235
+ f"but not a valid element or a NodeList."
236
+ )
237
+
238
+ def clone(self, clone_id=None):
239
+ """Make a clone of the element (clones the underlying DOM object too)."""
240
+ clone = Element.from_dom_element(self._dom_element.cloneNode(True))
241
+ clone.id = clone_id
242
+ return clone
193
243
 
194
- @property
195
- def id(self):
196
- return self._js.id
244
+ def find(self, selector):
245
+ """Return an ElementCollection representing all the child elements that
246
+ match the specified selector.
197
247
 
198
- @id.setter
199
- def id(self, value):
200
- self._js.id = value
248
+ Args:
249
+ selector (str): A string containing a selector expression
201
250
 
202
- @property
203
- def options(self):
204
- if "options" in self._proxies:
205
- return self._proxies["options"]
251
+ Returns:
252
+ ElementCollection: A collection of elements matching the selector
253
+ """
254
+ return ElementCollection(
255
+ [
256
+ Element.from_dom_element(dom_element)
257
+ for dom_element in self._dom_element.querySelectorAll(selector)
258
+ ]
259
+ )
206
260
 
207
- if not self._js.tagName.lower() in {"select", "datalist", "optgroup"}:
208
- raise AttributeError(
209
- f"Element {self._js.tagName} has no options attribute."
210
- )
211
- self._proxies["options"] = OptionsProxy(self)
212
- return self._proxies["options"]
261
+ def show_me(self):
262
+ """Scroll the element into view."""
263
+ self._dom_element.scrollIntoView()
213
264
 
214
- @property
215
- def value(self):
216
- return self._js.value
217
-
218
- @value.setter
219
- def value(self, value):
220
- # in order to avoid confusion to the user, we don't allow setting the
221
- # value of elements that don't have a value attribute
222
- if not hasattr(self._js, "value"):
223
- raise AttributeError(
224
- f"Element {self._js.tagName} has no value attribute. If you want to "
225
- "force a value attribute, set it directly using the `_js.value = <value>` "
226
- "javascript API attribute instead."
227
- )
228
- self._js.value = value
229
265
 
230
- @property
231
- def selected(self):
232
- return self._js.selected
233
-
234
- @selected.setter
235
- def selected(self, value):
236
- # in order to avoid confusion to the user, we don't allow setting the
237
- # value of elements that don't have a value attribute
238
- if not hasattr(self._js, "selected"):
239
- raise AttributeError(
240
- f"Element {self._js.tagName} has no value attribute. If you want to "
241
- "force a value attribute, set it directly using the `_js.value = <value>` "
242
- "javascript API attribute instead."
243
- )
244
- self._js.selected = value
266
+ class Classes:
267
+ """A set-like interface to an element's `classList`."""
245
268
 
246
- def clone(self, new_id=None):
247
- clone = Element(self._js.cloneNode(True))
248
- clone.id = new_id
269
+ def __init__(self, element: Element):
270
+ self._element = element
271
+ self._class_list = self._element._dom_element.classList
249
272
 
250
- return clone
273
+ def __contains__(self, item):
274
+ return item in self._class_list
251
275
 
252
- def remove_class(self, classname):
253
- classList = self._js.classList
254
- if isinstance(classname, list):
255
- classList.remove(*classname)
256
- else:
257
- classList.remove(classname)
258
- return self
276
+ def __eq__(self, other):
277
+ # We allow comparison with either another `Classes` instance...
278
+ if isinstance(other, Classes):
279
+ compare_with = list(other._class_list)
259
280
 
260
- def add_class(self, classname):
261
- classList = self._js.classList
262
- if isinstance(classname, list):
263
- classList.add(*classname)
281
+ # ...or iterables of strings.
264
282
  else:
265
- self._js.classList.add(classname)
266
- return self
283
+ # TODO: Check MP for existence of better iterable test.
284
+ try:
285
+ compare_with = iter(other)
267
286
 
268
- @property
269
- def classes(self):
270
- classes = self._js.classList.values()
271
- return [x for x in classes]
287
+ except TypeError:
288
+ return False
272
289
 
273
- def show_me(self):
274
- self._js.scrollIntoView()
290
+ return set(self._class_list) == set(compare_with)
275
291
 
276
- def snap(
277
- self,
278
- to: BaseElement | str = None,
279
- width: int | None = None,
280
- height: int | None = None,
281
- ):
282
- """
283
- Captures a snapshot of a video element. (Only available for video elements)
292
+ def __iter__(self):
293
+ return iter(self._class_list)
284
294
 
285
- Inputs:
295
+ def __len__(self):
296
+ return self._class_list.length
286
297
 
287
- * to: element where to save the snapshot of the video frame to
288
- * width: width of the image
289
- * height: height of the image
298
+ def __repr__(self):
299
+ return f"Classes({', '.join(self._class_list)})"
290
300
 
291
- Output:
292
- (Element) canvas element where the video frame snapshot was drawn into
293
- """
294
- if self._js.tagName != "VIDEO":
295
- raise AttributeError("Snap method is only available for video Elements")
301
+ def __str__(self):
302
+ return " ".join(self._class_list)
296
303
 
297
- if to is None:
298
- canvas = self.create("canvas")
299
- if width is None:
300
- width = self._js.width
301
- if height is None:
302
- height = self._js.height
303
- canvas._js.width = width
304
- canvas._js.height = height
304
+ def add(self, *class_names):
305
+ for class_name in class_names:
306
+ if isinstance(class_name, list):
307
+ for item in class_name:
308
+ self.add(item)
305
309
 
306
- elif isinstance(to, Element):
307
- if to._js.tagName != "CANVAS":
308
- raise TypeError("Element to snap to must a canvas.")
309
- canvas = to
310
- elif getattr(to, "tagName", "") == "CANVAS":
311
- canvas = Element(to)
312
- elif isinstance(to, str):
313
- # TODO (fpliger): This needs a better fix but doing a local import here for a quick fix
314
- from pyscript.web import dom
310
+ else:
311
+ self._class_list.add(class_name)
315
312
 
316
- canvas = dom[to][0]
317
- if canvas._js.tagName != "CANVAS":
318
- raise TypeError("Element to snap to must a be canvas.")
313
+ def contains(self, class_name):
314
+ return class_name in self
319
315
 
320
- canvas.draw(self, width, height)
316
+ def remove(self, *class_names):
317
+ for class_name in class_names:
318
+ if isinstance(class_name, list):
319
+ for item in class_name:
320
+ self.remove(item)
321
321
 
322
- return canvas
322
+ else:
323
+ self._class_list.remove(class_name)
323
324
 
324
- def download(self, filename: str = "snapped.png") -> None:
325
- """Download the current element (only available for canvas elements) with the filename
326
- provided in input.
325
+ def replace(self, old_class, new_class):
326
+ self.remove(old_class)
327
+ self.add(new_class)
327
328
 
328
- Inputs:
329
- * filename (str): name of the file being downloaded
329
+ def toggle(self, *class_names):
330
+ for class_name in class_names:
331
+ if class_name in self:
332
+ self.remove(class_name)
330
333
 
331
- Output:
332
- None
333
- """
334
- if self._js.tagName != "CANVAS":
335
- raise AttributeError(
336
- "The download method is only available for canvas Elements"
337
- )
334
+ else:
335
+ self.add(class_name)
338
336
 
339
- link = self.create("a")
340
- link._js.download = filename
341
- link._js.href = self._js.toDataURL()
342
- link._js.click()
343
337
 
344
- def draw(self, what, width, height):
345
- """Draw `what` on the current element (only available for canvas elements).
338
+ class HasOptions:
339
+ """Mix-in for elements that have an options attribute.
346
340
 
347
- Inputs:
341
+ The elements that support options are: <datalist>, <optgroup>, and <select>.
342
+ """
348
343
 
349
- * what (canvas image source): An element to draw into the context. The specification permits any canvas
350
- image source, specifically, an HTMLImageElement, an SVGImageElement, an HTMLVideoElement,
351
- an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a VideoFrame.
352
- """
353
- if self._js.tagName != "CANVAS":
354
- raise AttributeError(
355
- "The draw method is only available for canvas Elements"
356
- )
344
+ @property
345
+ def options(self):
346
+ if not hasattr(self, "_options"):
347
+ self._options = Options(self)
357
348
 
358
- if isinstance(what, Element):
359
- what = what._js
349
+ return self._options
360
350
 
361
- # https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
362
- self._js.getContext("2d").drawImage(what, 0, 0, width, height)
363
351
 
352
+ class Options:
353
+ """This class represents the <option>s of a <datalist>, <optgroup> or <select>
354
+ element.
364
355
 
365
- class OptionsProxy:
366
- """This class represents the options of a select element. It
367
- allows to access to add and remove options by using the `add` and `remove` methods.
356
+ It allows to access to add and remove <option>s by using the `add` and `remove`
357
+ methods.
368
358
  """
369
359
 
370
360
  def __init__(self, element: Element) -> None:
371
361
  self._element = element
372
- if self._element._js.tagName.lower() != "select":
373
- raise AttributeError(
374
- f"Element {self._element._js.tagName} has no options attribute."
375
- )
376
362
 
377
363
  def add(
378
364
  self,
@@ -383,7 +369,7 @@ class OptionsProxy:
383
369
  **kws,
384
370
  ) -> None:
385
371
  """Add a new option to the select element"""
386
- # create the option element and set the attributes
372
+
387
373
  option = document.createElement("option")
388
374
  if value is not None:
389
375
  kws["value"] = value
@@ -397,13 +383,13 @@ class OptionsProxy:
397
383
 
398
384
  if before:
399
385
  if isinstance(before, Element):
400
- before = before._js
386
+ before = before._dom_element
401
387
 
402
- self._element._js.add(option, before)
388
+ self._element._dom_element.add(option, before)
403
389
 
404
390
  def remove(self, item: int) -> None:
405
391
  """Remove the option at the specified index"""
406
- self._element._js.remove(item)
392
+ self._element._dom_element.remove(item)
407
393
 
408
394
  def clear(self) -> None:
409
395
  """Remove all the options"""
@@ -413,12 +399,14 @@ class OptionsProxy:
413
399
  @property
414
400
  def options(self):
415
401
  """Return the list of options"""
416
- return [Element(opt) for opt in self._element._js.options]
402
+ return [
403
+ Element.from_dom_element(opt) for opt in self._element._dom_element.options
404
+ ]
417
405
 
418
406
  @property
419
407
  def selected(self):
420
408
  """Return the selected option"""
421
- return self.options[self._element._js.selectedIndex]
409
+ return self.options[self._element._dom_element.selectedIndex]
422
410
 
423
411
  def __iter__(self):
424
412
  yield from self.options
@@ -433,13 +421,12 @@ class OptionsProxy:
433
421
  return self.options[key]
434
422
 
435
423
 
436
- class StyleProxy: # (dict):
424
+ class Style:
425
+ """A dict-like interface to an element's css style."""
426
+
437
427
  def __init__(self, element: Element) -> None:
438
428
  self._element = element
439
-
440
- @cached_property
441
- def _style(self):
442
- return self._element._js.style
429
+ self._style = self._element._dom_element.style
443
430
 
444
431
  def __getitem__(self, key):
445
432
  return self._style.getPropertyValue(key)
@@ -450,944 +437,1039 @@ class StyleProxy: # (dict):
450
437
  def remove(self, key):
451
438
  self._style.removeProperty(key)
452
439
 
453
- def set(self, **kws):
454
- for k, v in kws.items():
455
- self._element._js.style.setProperty(k, v)
440
+ def set(self, **kwargs):
441
+ for key, value in kwargs.items():
442
+ self._element._dom_element.style.setProperty(key, value)
456
443
 
457
444
  # CSS Properties
458
445
  # Reference: https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L3799C1-L5005C2
459
- # Following prperties automatically generated from the above reference using
446
+ # Following properties automatically generated from the above reference using
460
447
  # tools/codegen_css_proxy.py
461
448
  @property
462
449
  def visible(self):
463
- return self._element._js.style.visibility
450
+ return self._element._dom_element.style.visibility
464
451
 
465
452
  @visible.setter
466
453
  def visible(self, value):
467
- self._element._js.style.visibility = value
454
+ self._element._dom_element.style.visibility = value
468
455
 
469
456
 
470
- # --------- END OF PYDOM STUFF ------
457
+ class ContainerElement(Element):
458
+ """Base class for elements that can contain other elements."""
471
459
 
460
+ def __init__(
461
+ self, *args, children=None, dom_element=None, style=None, classes=None, **kwargs
462
+ ):
463
+ super().__init__(
464
+ dom_element=dom_element, style=style, classes=classes, **kwargs
465
+ )
472
466
 
473
- class ElementBase(Element):
474
- tag = "div"
467
+ for child in list(args) + (children or []):
468
+ if isinstance(child, Element) or isinstance(child, ElementCollection):
469
+ self.append(child)
475
470
 
476
- # GLOBAL ATTRIBUTES
477
- # These are attribute that all elements have (this list is a subset of the official one)
478
- # We are trying to capture the most used ones
479
- accesskey = JSProperty("accesskey")
480
- autofocus = JSProperty("autofocus")
481
- autocapitalize = JSProperty("autocapitalize")
482
- className = JSProperty("className")
483
- contenteditable = JSProperty("contenteditable")
484
- draggable = JSProperty("draggable")
485
- enterkeyhint = JSProperty("enterkeyhint")
486
- hidden = JSProperty("hidden")
487
- id = JSProperty("id")
488
- lang = JSProperty("lang")
489
- nonce = JSProperty("nonce")
490
- part = JSProperty("part")
491
- popover = JSProperty("popover")
492
- slot = JSProperty("slot")
493
- spellcheck = JSProperty("spellcheck")
494
- tabindex = JSProperty("tabindex")
495
- title = JSProperty("title")
496
- translate = JSProperty("translate")
497
- virtualkeyboardpolicy = JSProperty("virtualkeyboardpolicy")
498
-
499
- def __init__(self, style=None, **kwargs):
500
- super().__init__(document.createElement(self.tag))
501
-
502
- # set all the style properties provided in input
503
- if isinstance(style, dict):
504
- for key, value in style.items():
505
- self.style[key] = value
506
- elif style is None:
507
- pass
508
- else:
509
- raise ValueError(
510
- f"Style should be a dictionary, received {style} (type {type(style)}) instead."
511
- )
512
-
513
- # IMPORTANT!!! This is used to auto-harvest all input arguments and set them as properties
514
- self._init_properties(**kwargs)
471
+ else:
472
+ self.innerHTML += child
515
473
 
516
- def _init_properties(self, **kwargs):
517
- """Set all the properties (of type JSProperties) provided in input as properties
518
- of the class instance.
519
474
 
520
- Args:
521
- **kwargs: The properties to set
522
- """
523
- # Look at all the properties of the class and see if they were provided in kwargs
524
- for attr_name, attr in getmembers_static(self.__class__):
525
- # For each one, actually check if it is a property of the class and set it
526
- if isinstance(attr, JSProperty) and attr_name in kwargs:
527
- try:
528
- setattr(self, attr_name, kwargs[attr_name])
529
- except Exception as e:
530
- print(f"Error setting {attr_name} to {kwargs[attr_name]}: {e}")
531
- raise
532
-
533
-
534
- class TextElementBase(ElementBase):
535
- def __init__(self, content=None, style=None, **kwargs):
536
- super().__init__(style=style, **kwargs)
537
-
538
- # If it's an element, append the element
539
- if isinstance(content, Element):
540
- self.append(content)
541
- # If it's a list of elements
542
- elif isinstance(content, list):
543
- for item in content:
544
- self.append(item)
545
- # If the content wasn't set just ignore
546
- elif content is None:
547
- pass
548
- else:
549
- # Otherwise, set content as the html of the element
550
- self.html = content
551
-
552
-
553
- # IMPORTANT: For all HTML components defined below, we are not mapping all
554
- # available attributes, just the global and the most common ones.
555
- # If you need to access a specific attribute, you can always use the `_js.<attribute>`
556
- class a(TextElementBase):
475
+ # IMPORTANT: For all HTML components defined below, we are not mapping all possible
476
+ # attributes, just the global and the most common ones. If you need to access a
477
+ # specific attribute, you can always use the `_dom_element.<attribute>`
478
+ class a(ContainerElement):
557
479
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a"""
558
480
 
559
481
  tag = "a"
560
482
 
561
- download = JSProperty("download")
562
- href = JSProperty("href")
563
- referrerpolicy = JSProperty("referrerpolicy")
564
- rel = JSProperty("rel")
565
- target = JSProperty("target")
566
- type = JSProperty("type")
483
+ download = DOMProperty("download")
484
+ href = DOMProperty("href")
485
+ referrerpolicy = DOMProperty("referrerpolicy")
486
+ rel = DOMProperty("rel")
487
+ target = DOMProperty("target")
488
+ type = DOMProperty("type")
567
489
 
568
490
 
569
- class abbr(TextElementBase):
491
+ class abbr(ContainerElement):
570
492
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr"""
571
493
 
572
494
  tag = "abbr"
573
495
 
574
496
 
575
- class address(TextElementBase):
497
+ class address(ContainerElement):
576
498
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address"""
577
499
 
578
500
  tag = "address"
579
501
 
580
502
 
581
- class area(ElementBase):
503
+ class area(Element):
582
504
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area"""
583
505
 
584
506
  tag = "area"
585
507
 
586
- alt = JSProperty("alt")
587
- coords = JSProperty("coords")
588
- download = JSProperty("download")
589
- href = JSProperty("href")
590
- ping = JSProperty("ping")
591
- referrerpolicy = JSProperty("referrerpolicy")
592
- rel = JSProperty("rel")
593
- shape = JSProperty("shape")
594
- target = JSProperty("target")
508
+ alt = DOMProperty("alt")
509
+ coords = DOMProperty("coords")
510
+ download = DOMProperty("download")
511
+ href = DOMProperty("href")
512
+ ping = DOMProperty("ping")
513
+ referrerpolicy = DOMProperty("referrerpolicy")
514
+ rel = DOMProperty("rel")
515
+ shape = DOMProperty("shape")
516
+ target = DOMProperty("target")
595
517
 
596
518
 
597
- class article(TextElementBase):
519
+ class article(ContainerElement):
598
520
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article"""
599
521
 
600
522
  tag = "article"
601
523
 
602
524
 
603
- class aside(TextElementBase):
525
+ class aside(ContainerElement):
604
526
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside"""
605
527
 
606
528
  tag = "aside"
607
529
 
608
530
 
609
- class audio(ElementBase):
531
+ class audio(ContainerElement):
610
532
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio"""
611
533
 
612
534
  tag = "audio"
613
535
 
614
- autoplay = JSProperty("autoplay")
615
- controls = JSProperty("controls")
616
- controlslist = JSProperty("controlslist")
617
- crossorigin = JSProperty("crossorigin")
618
- disableremoteplayback = JSProperty("disableremoteplayback")
619
- loop = JSProperty("loop")
620
- muted = JSProperty("muted")
621
- preload = JSProperty("preload")
622
- src = JSProperty("src")
536
+ autoplay = DOMProperty("autoplay")
537
+ controls = DOMProperty("controls")
538
+ controlslist = DOMProperty("controlslist")
539
+ crossorigin = DOMProperty("crossorigin")
540
+ disableremoteplayback = DOMProperty("disableremoteplayback")
541
+ loop = DOMProperty("loop")
542
+ muted = DOMProperty("muted")
543
+ preload = DOMProperty("preload")
544
+ src = DOMProperty("src")
623
545
 
624
546
 
625
- class b(TextElementBase):
547
+ class b(ContainerElement):
626
548
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b"""
627
549
 
628
550
  tag = "b"
629
551
 
630
552
 
631
- class blockquote(TextElementBase):
553
+ class base(Element):
554
+ """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base"""
555
+
556
+ tag = "base"
557
+
558
+ href = DOMProperty("href")
559
+ target = DOMProperty("target")
560
+
561
+
562
+ class blockquote(ContainerElement):
632
563
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"""
633
564
 
634
565
  tag = "blockquote"
635
566
 
636
- cite = JSProperty("cite")
567
+ cite = DOMProperty("cite")
568
+
569
+
570
+ class body(ContainerElement):
571
+ """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/body"""
637
572
 
573
+ tag = "body"
638
574
 
639
- class br(ElementBase):
575
+
576
+ class br(Element):
640
577
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br"""
641
578
 
642
579
  tag = "br"
643
580
 
644
581
 
645
- class button(TextElementBase):
582
+ class button(ContainerElement):
646
583
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button"""
647
584
 
648
585
  tag = "button"
649
586
 
650
- autofocus = JSProperty("autofocus")
651
- disabled = JSProperty("disabled")
652
- form = JSProperty("form")
653
- formaction = JSProperty("formaction")
654
- formenctype = JSProperty("formenctype")
655
- formmethod = JSProperty("formmethod")
656
- formnovalidate = JSProperty("formnovalidate")
657
- formtarget = JSProperty("formtarget")
658
- name = JSProperty("name")
659
- type = JSProperty("type")
660
- value = JSProperty("value")
587
+ autofocus = DOMProperty("autofocus")
588
+ disabled = DOMProperty("disabled")
589
+ form = DOMProperty("form")
590
+ formaction = DOMProperty("formaction")
591
+ formenctype = DOMProperty("formenctype")
592
+ formmethod = DOMProperty("formmethod")
593
+ formnovalidate = DOMProperty("formnovalidate")
594
+ formtarget = DOMProperty("formtarget")
595
+ name = DOMProperty("name")
596
+ type = DOMProperty("type")
597
+ value = DOMProperty("value")
661
598
 
662
599
 
663
- class canvas(TextElementBase):
600
+ class canvas(ContainerElement):
664
601
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas"""
665
602
 
666
603
  tag = "canvas"
667
604
 
668
- height = JSProperty("height")
669
- width = JSProperty("width")
605
+ height = DOMProperty("height")
606
+ width = DOMProperty("width")
607
+
608
+ def download(self, filename: str = "snapped.png") -> None:
609
+ """Download the current element with the filename provided in input.
610
+
611
+ Inputs:
612
+ * filename (str): name of the file being downloaded
613
+
614
+ Output:
615
+ None
616
+ """
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)
670
622
 
623
+ download_link._dom_element.click()
671
624
 
672
- class caption(TextElementBase):
625
+ def draw(self, what, width=None, height=None):
626
+ """Draw `what` on the current element
627
+
628
+ Inputs:
629
+
630
+ * what (canvas image source): An element to draw into the context. The
631
+ specification permits any canvas image source, specifically, an
632
+ HTMLImageElement, an SVGImageElement, an HTMLVideoElement,
633
+ an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a
634
+ VideoFrame.
635
+ """
636
+ if isinstance(what, Element):
637
+ what = what._dom_element
638
+
639
+ # https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
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)
646
+
647
+
648
+ class caption(ContainerElement):
673
649
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption"""
674
650
 
675
651
  tag = "caption"
676
652
 
677
653
 
678
- class cite(TextElementBase):
654
+ class cite(ContainerElement):
679
655
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite"""
680
656
 
681
657
  tag = "cite"
682
658
 
683
659
 
684
- class code(TextElementBase):
660
+ class code(ContainerElement):
685
661
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code"""
686
662
 
687
663
  tag = "code"
688
664
 
689
665
 
690
- class data(TextElementBase):
666
+ class col(Element):
667
+ """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/col"""
668
+
669
+ tag = "col"
670
+
671
+ span = DOMProperty("span")
672
+
673
+
674
+ class colgroup(ContainerElement):
675
+ """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/colgroup"""
676
+
677
+ tag = "colgroup"
678
+
679
+
680
+ class data(ContainerElement):
691
681
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data"""
692
682
 
693
683
  tag = "data"
694
684
 
695
- value = JSProperty("value")
685
+ value = DOMProperty("value")
696
686
 
697
687
 
698
- class datalist(TextElementBase):
688
+ class datalist(ContainerElement, HasOptions):
699
689
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist"""
700
690
 
701
691
  tag = "datalist"
702
692
 
703
693
 
704
- class dd(TextElementBase):
694
+ class dd(ContainerElement):
705
695
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd"""
706
696
 
707
697
  tag = "dd"
708
698
 
709
699
 
710
- class del_(TextElementBase):
700
+ class del_(ContainerElement):
711
701
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del"""
712
702
 
713
703
  tag = "del"
714
704
 
715
- cite = JSProperty("cite")
716
- datetime = JSProperty("datetime")
705
+ cite = DOMProperty("cite")
706
+ datetime = DOMProperty("datetime")
717
707
 
718
708
 
719
- class details(TextElementBase):
709
+ class details(ContainerElement):
720
710
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"""
721
711
 
722
712
  tag = "details"
723
713
 
724
- open = JSProperty("open")
714
+ open = DOMProperty("open")
725
715
 
726
716
 
727
- class dialog(TextElementBase):
717
+ class dialog(ContainerElement):
728
718
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"""
729
719
 
730
720
  tag = "dialog"
731
721
 
732
- open = JSProperty("open")
722
+ open = DOMProperty("open")
733
723
 
734
724
 
735
- class div(TextElementBase):
725
+ class div(ContainerElement):
736
726
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div"""
737
727
 
738
728
  tag = "div"
739
729
 
740
730
 
741
- class dl(TextElementBase):
731
+ class dl(ContainerElement):
742
732
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl"""
743
733
 
744
734
  tag = "dl"
745
735
 
746
- value = JSProperty("value")
736
+ value = DOMProperty("value")
747
737
 
748
738
 
749
- class dt(TextElementBase):
739
+ class dt(ContainerElement):
750
740
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt"""
751
741
 
752
742
  tag = "dt"
753
743
 
754
744
 
755
- class em(TextElementBase):
745
+ class em(ContainerElement):
756
746
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em"""
757
747
 
758
748
  tag = "em"
759
749
 
760
750
 
761
- class embed(TextElementBase):
751
+ class embed(Element):
762
752
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed"""
763
753
 
764
754
  tag = "embed"
765
755
 
766
- height = JSProperty("height")
767
- src = JSProperty("src")
768
- type = JSProperty("type")
769
- width = JSProperty("width")
756
+ height = DOMProperty("height")
757
+ src = DOMProperty("src")
758
+ type = DOMProperty("type")
759
+ width = DOMProperty("width")
770
760
 
771
761
 
772
- class fieldset(TextElementBase):
762
+ class fieldset(ContainerElement):
773
763
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset"""
774
764
 
775
765
  tag = "fieldset"
776
766
 
777
- disabled = JSProperty("disabled")
778
- form = JSProperty("form")
779
- name = JSProperty("name")
767
+ disabled = DOMProperty("disabled")
768
+ form = DOMProperty("form")
769
+ name = DOMProperty("name")
780
770
 
781
771
 
782
- class figcaption(TextElementBase):
772
+ class figcaption(ContainerElement):
783
773
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption"""
784
774
 
785
775
  tag = "figcaption"
786
776
 
787
777
 
788
- class figure(TextElementBase):
778
+ class figure(ContainerElement):
789
779
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure"""
790
780
 
791
781
  tag = "figure"
792
782
 
793
783
 
794
- class footer(TextElementBase):
784
+ class footer(ContainerElement):
795
785
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer"""
796
786
 
797
787
  tag = "footer"
798
788
 
799
789
 
800
- class form(TextElementBase):
790
+ class form(ContainerElement):
801
791
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form"""
802
792
 
803
793
  tag = "form"
804
794
 
805
- accept_charset = JSProperty("accept-charset")
806
- action = JSProperty("action")
807
- autocapitalize = JSProperty("autocapitalize")
808
- autocomplete = JSProperty("autocomplete")
809
- enctype = JSProperty("enctype")
810
- name = JSProperty("name")
811
- method = JSProperty("method")
812
- nonvalidate = JSProperty("nonvalidate")
813
- rel = JSProperty("rel")
814
- target = JSProperty("target")
795
+ accept_charset = DOMProperty("accept-charset")
796
+ action = DOMProperty("action")
797
+ autocapitalize = DOMProperty("autocapitalize")
798
+ autocomplete = DOMProperty("autocomplete")
799
+ enctype = DOMProperty("enctype")
800
+ name = DOMProperty("name")
801
+ method = DOMProperty("method")
802
+ nonvalidate = DOMProperty("nonvalidate")
803
+ rel = DOMProperty("rel")
804
+ target = DOMProperty("target")
815
805
 
816
806
 
817
- class h1(TextElementBase):
807
+ class h1(ContainerElement):
818
808
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1"""
819
809
 
820
810
  tag = "h1"
821
811
 
822
812
 
823
- class h2(TextElementBase):
813
+ class h2(ContainerElement):
824
814
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2"""
825
815
 
826
816
  tag = "h2"
827
817
 
828
818
 
829
- class h3(TextElementBase):
819
+ class h3(ContainerElement):
830
820
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3"""
831
821
 
832
822
  tag = "h3"
833
823
 
834
824
 
835
- class h4(TextElementBase):
825
+ class h4(ContainerElement):
836
826
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4"""
837
827
 
838
828
  tag = "h4"
839
829
 
840
830
 
841
- class h5(TextElementBase):
831
+ class h5(ContainerElement):
842
832
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5"""
843
833
 
844
834
  tag = "h5"
845
835
 
846
836
 
847
- class h6(TextElementBase):
837
+ class h6(ContainerElement):
848
838
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6"""
849
839
 
850
840
  tag = "h6"
851
841
 
852
842
 
853
- class header(TextElementBase):
843
+ class head(ContainerElement):
844
+ """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head"""
845
+
846
+ tag = "head"
847
+
848
+
849
+ class header(ContainerElement):
854
850
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header"""
855
851
 
856
852
  tag = "header"
857
853
 
858
854
 
859
- class hgroup(TextElementBase):
855
+ class hgroup(ContainerElement):
860
856
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup"""
861
857
 
862
858
  tag = "hgroup"
863
859
 
864
860
 
865
- class hr(TextElementBase):
861
+ class hr(Element):
866
862
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr"""
867
863
 
868
864
  tag = "hr"
869
865
 
866
+ align = DOMProperty("align")
867
+ color = DOMProperty("color")
868
+ noshade = DOMProperty("noshade")
869
+ size = DOMProperty("size")
870
+ width = DOMProperty("width")
870
871
 
871
- class i(TextElementBase):
872
+
873
+ class html(ContainerElement):
874
+ """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/html"""
875
+
876
+ tag = "html"
877
+
878
+
879
+ class i(ContainerElement):
872
880
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i"""
873
881
 
874
882
  tag = "i"
875
883
 
876
884
 
877
- class iframe(TextElementBase):
885
+ class iframe(ContainerElement):
878
886
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"""
879
887
 
880
888
  tag = "iframe"
881
889
 
882
- allow = JSProperty("allow")
883
- allowfullscreen = JSProperty("allowfullscreen")
884
- height = JSProperty("height")
885
- loading = JSProperty("loading")
886
- name = JSProperty("name")
887
- referrerpolicy = JSProperty("referrerpolicy")
888
- sandbox = JSProperty("sandbox")
889
- src = JSProperty("src")
890
- srcdoc = JSProperty("srcdoc")
891
- width = JSProperty("width")
890
+ allow = DOMProperty("allow")
891
+ allowfullscreen = DOMProperty("allowfullscreen")
892
+ height = DOMProperty("height")
893
+ loading = DOMProperty("loading")
894
+ name = DOMProperty("name")
895
+ referrerpolicy = DOMProperty("referrerpolicy")
896
+ sandbox = DOMProperty("sandbox")
897
+ src = DOMProperty("src")
898
+ srcdoc = DOMProperty("srcdoc")
899
+ width = DOMProperty("width")
892
900
 
893
901
 
894
- class img(ElementBase):
902
+ class img(Element):
895
903
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img"""
896
904
 
897
905
  tag = "img"
898
906
 
899
- alt = JSProperty("alt")
900
- crossorigin = JSProperty("crossorigin")
901
- decoding = JSProperty("decoding")
902
- fetchpriority = JSProperty("fetchpriority")
903
- height = JSProperty("height")
904
- ismap = JSProperty("ismap")
905
- loading = JSProperty("loading")
906
- referrerpolicy = JSProperty("referrerpolicy")
907
- sizes = JSProperty("sizes")
908
- src = JSProperty("src")
909
- width = JSProperty("width")
907
+ alt = DOMProperty("alt")
908
+ crossorigin = DOMProperty("crossorigin")
909
+ decoding = DOMProperty("decoding")
910
+ fetchpriority = DOMProperty("fetchpriority")
911
+ height = DOMProperty("height")
912
+ ismap = DOMProperty("ismap")
913
+ loading = DOMProperty("loading")
914
+ referrerpolicy = DOMProperty("referrerpolicy")
915
+ sizes = DOMProperty("sizes")
916
+ src = DOMProperty("src")
917
+ width = DOMProperty("width")
910
918
 
911
919
 
912
920
  # NOTE: Input is a reserved keyword in Python, so we use input_ instead
913
- class input_(ElementBase):
921
+ class input_(Element):
914
922
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input"""
915
923
 
916
924
  tag = "input"
917
925
 
918
- accept = JSProperty("accept")
919
- alt = JSProperty("alt")
920
- autofocus = JSProperty("autofocus")
921
- capture = JSProperty("capture")
922
- checked = JSProperty("checked")
923
- dirname = JSProperty("dirname")
924
- disabled = JSProperty("disabled")
925
- form = JSProperty("form")
926
- formaction = JSProperty("formaction")
927
- formenctype = JSProperty("formenctype")
928
- formmethod = JSProperty("formmethod")
929
- formnovalidate = JSProperty("formnovalidate")
930
- formtarget = JSProperty("formtarget")
931
- height = JSProperty("height")
932
- list = JSProperty("list")
933
- max = JSProperty("max")
934
- maxlength = JSProperty("maxlength")
935
- min = JSProperty("min")
936
- minlength = JSProperty("minlength")
937
- multiple = JSProperty("multiple")
938
- name = JSProperty("name")
939
- pattern = JSProperty("pattern")
940
- placeholder = JSProperty("placeholder")
941
- popovertarget = JSProperty("popovertarget")
942
- popovertargetaction = JSProperty("popovertargetaction")
943
- readonly = JSProperty("readonly")
944
- required = JSProperty("required")
945
- size = JSProperty("size")
946
- src = JSProperty("src")
947
- step = JSProperty("step")
948
- type = JSProperty("type")
949
- value = JSProperty("value")
950
- width = JSProperty("width")
951
-
952
-
953
- class ins(TextElementBase):
926
+ accept = DOMProperty("accept")
927
+ alt = DOMProperty("alt")
928
+ autofocus = DOMProperty("autofocus")
929
+ capture = DOMProperty("capture")
930
+ checked = DOMProperty("checked")
931
+ dirname = DOMProperty("dirname")
932
+ disabled = DOMProperty("disabled")
933
+ form = DOMProperty("form")
934
+ formaction = DOMProperty("formaction")
935
+ formenctype = DOMProperty("formenctype")
936
+ formmethod = DOMProperty("formmethod")
937
+ formnovalidate = DOMProperty("formnovalidate")
938
+ formtarget = DOMProperty("formtarget")
939
+ height = DOMProperty("height")
940
+ list = DOMProperty("list")
941
+ max = DOMProperty("max")
942
+ maxlength = DOMProperty("maxlength")
943
+ min = DOMProperty("min")
944
+ minlength = DOMProperty("minlength")
945
+ multiple = DOMProperty("multiple")
946
+ name = DOMProperty("name")
947
+ pattern = DOMProperty("pattern")
948
+ placeholder = DOMProperty("placeholder")
949
+ popovertarget = DOMProperty("popovertarget")
950
+ popovertargetaction = DOMProperty("popovertargetaction")
951
+ readonly = DOMProperty("readonly")
952
+ required = DOMProperty("required")
953
+ size = DOMProperty("size")
954
+ src = DOMProperty("src")
955
+ step = DOMProperty("step")
956
+ type = DOMProperty("type")
957
+ value = DOMProperty("value")
958
+ width = DOMProperty("width")
959
+
960
+
961
+ class ins(ContainerElement):
954
962
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins"""
955
963
 
956
964
  tag = "ins"
957
965
 
958
- cite = JSProperty("cite")
959
- datetime = JSProperty("datetime")
966
+ cite = DOMProperty("cite")
967
+ datetime = DOMProperty("datetime")
960
968
 
961
969
 
962
- class kbd(TextElementBase):
970
+ class kbd(ContainerElement):
963
971
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd"""
964
972
 
965
973
  tag = "kbd"
966
974
 
967
975
 
968
- class label(TextElementBase):
976
+ class label(ContainerElement):
969
977
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label"""
970
978
 
971
979
  tag = "label"
972
980
 
973
- for_ = JSProperty("for")
981
+ for_ = DOMProperty("for")
974
982
 
975
983
 
976
- class legend(TextElementBase):
984
+ class legend(ContainerElement):
977
985
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend"""
978
986
 
979
987
  tag = "legend"
980
988
 
981
989
 
982
- class li(TextElementBase):
990
+ class li(ContainerElement):
983
991
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li"""
984
992
 
985
993
  tag = "li"
986
994
 
987
- value = JSProperty("value")
995
+ value = DOMProperty("value")
988
996
 
989
997
 
990
- class link(TextElementBase):
998
+ class link(Element):
991
999
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link"""
992
1000
 
993
1001
  tag = "link"
994
1002
 
995
- as_ = JSProperty("as")
996
- crossorigin = JSProperty("crossorigin")
997
- disabled = JSProperty("disabled")
998
- fetchpriority = JSProperty("fetchpriority")
999
- href = JSProperty("href")
1000
- imagesizes = JSProperty("imagesizes")
1001
- imagesrcset = JSProperty("imagesrcset")
1002
- integrity = JSProperty("integrity")
1003
- media = JSProperty("media")
1004
- rel = JSProperty("rel")
1005
- referrerpolicy = JSProperty("referrerpolicy")
1006
- sizes = JSProperty("sizes")
1007
- title = JSProperty("title")
1008
- type = JSProperty("type")
1009
-
1010
-
1011
- class main(TextElementBase):
1003
+ as_ = DOMProperty("as")
1004
+ crossorigin = DOMProperty("crossorigin")
1005
+ disabled = DOMProperty("disabled")
1006
+ fetchpriority = DOMProperty("fetchpriority")
1007
+ href = DOMProperty("href")
1008
+ imagesizes = DOMProperty("imagesizes")
1009
+ imagesrcset = DOMProperty("imagesrcset")
1010
+ integrity = DOMProperty("integrity")
1011
+ media = DOMProperty("media")
1012
+ rel = DOMProperty("rel")
1013
+ referrerpolicy = DOMProperty("referrerpolicy")
1014
+ sizes = DOMProperty("sizes")
1015
+ title = DOMProperty("title")
1016
+ type = DOMProperty("type")
1017
+
1018
+
1019
+ class main(ContainerElement):
1012
1020
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main"""
1013
1021
 
1014
1022
  tag = "main"
1015
1023
 
1016
1024
 
1017
- class map_(TextElementBase):
1025
+ class map_(ContainerElement):
1018
1026
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map"""
1019
1027
 
1020
1028
  tag = "map"
1021
1029
 
1022
- name = JSProperty("name")
1030
+ name = DOMProperty("name")
1023
1031
 
1024
1032
 
1025
- class mark(TextElementBase):
1033
+ class mark(ContainerElement):
1026
1034
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark"""
1027
1035
 
1028
1036
  tag = "mark"
1029
1037
 
1030
1038
 
1031
- class menu(TextElementBase):
1039
+ class menu(ContainerElement):
1032
1040
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu"""
1033
1041
 
1034
1042
  tag = "menu"
1035
1043
 
1036
1044
 
1037
- class meter(TextElementBase):
1045
+ class meta(ContainerElement):
1046
+ """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta"""
1047
+
1048
+ tag = "meta"
1049
+
1050
+ charset = DOMProperty("charset")
1051
+ content = DOMProperty("content")
1052
+ http_equiv = DOMProperty("http-equiv")
1053
+ name = DOMProperty("name")
1054
+
1055
+
1056
+ class meter(ContainerElement):
1038
1057
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter"""
1039
1058
 
1040
1059
  tag = "meter"
1041
1060
 
1042
- form = JSProperty("form")
1043
- high = JSProperty("high")
1044
- low = JSProperty("low")
1045
- max = JSProperty("max")
1046
- min = JSProperty("min")
1047
- optimum = JSProperty("optimum")
1048
- value = JSProperty("value")
1061
+ form = DOMProperty("form")
1062
+ high = DOMProperty("high")
1063
+ low = DOMProperty("low")
1064
+ max = DOMProperty("max")
1065
+ min = DOMProperty("min")
1066
+ optimum = DOMProperty("optimum")
1067
+ value = DOMProperty("value")
1049
1068
 
1050
1069
 
1051
- class nav(TextElementBase):
1070
+ class nav(ContainerElement):
1052
1071
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav"""
1053
1072
 
1054
1073
  tag = "nav"
1055
1074
 
1056
1075
 
1057
- class object_(TextElementBase):
1076
+ class object_(ContainerElement):
1058
1077
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object"""
1059
1078
 
1060
1079
  tag = "object"
1061
1080
 
1062
- data = JSProperty("data")
1063
- form = JSProperty("form")
1064
- height = JSProperty("height")
1065
- name = JSProperty("name")
1066
- type = JSProperty("type")
1067
- usemap = JSProperty("usemap")
1068
- width = JSProperty("width")
1081
+ data = DOMProperty("data")
1082
+ form = DOMProperty("form")
1083
+ height = DOMProperty("height")
1084
+ name = DOMProperty("name")
1085
+ type = DOMProperty("type")
1086
+ usemap = DOMProperty("usemap")
1087
+ width = DOMProperty("width")
1069
1088
 
1070
1089
 
1071
- class ol(TextElementBase):
1090
+ class ol(ContainerElement):
1072
1091
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol"""
1073
1092
 
1074
1093
  tag = "ol"
1075
1094
 
1076
- reversed = JSProperty("reversed")
1077
- start = JSProperty("start")
1078
- type = JSProperty("type")
1095
+ reversed = DOMProperty("reversed")
1096
+ start = DOMProperty("start")
1097
+ type = DOMProperty("type")
1079
1098
 
1080
1099
 
1081
- class optgroup(TextElementBase):
1100
+ class optgroup(ContainerElement, HasOptions):
1082
1101
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup"""
1083
1102
 
1084
1103
  tag = "optgroup"
1085
1104
 
1086
- disabled = JSProperty("disabled")
1087
- label = JSProperty("label")
1105
+ disabled = DOMProperty("disabled")
1106
+ label = DOMProperty("label")
1088
1107
 
1089
1108
 
1090
- class option(TextElementBase):
1109
+ class option(ContainerElement):
1091
1110
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option"""
1092
1111
 
1093
1112
  tag = "option"
1094
1113
 
1095
- disabled = JSProperty("value")
1096
- label = JSProperty("label")
1097
- selected = JSProperty("selected")
1098
- value = JSProperty("value")
1114
+ disabled = DOMProperty("value")
1115
+ label = DOMProperty("label")
1116
+ selected = DOMProperty("selected")
1117
+ value = DOMProperty("value")
1099
1118
 
1100
1119
 
1101
- class output(TextElementBase):
1120
+ class output(ContainerElement):
1102
1121
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output"""
1103
1122
 
1104
1123
  tag = "output"
1105
1124
 
1106
- for_ = JSProperty("for")
1107
- form = JSProperty("form")
1108
- name = JSProperty("name")
1125
+ for_ = DOMProperty("for")
1126
+ form = DOMProperty("form")
1127
+ name = DOMProperty("name")
1109
1128
 
1110
1129
 
1111
- class p(TextElementBase):
1130
+ class p(ContainerElement):
1112
1131
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p"""
1113
1132
 
1114
1133
  tag = "p"
1115
1134
 
1116
1135
 
1117
- class picture(TextElementBase):
1136
+ class param(ContainerElement):
1137
+ """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p"""
1138
+
1139
+ tag = "p"
1140
+
1141
+
1142
+ class picture(ContainerElement):
1118
1143
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture"""
1119
1144
 
1120
1145
  tag = "picture"
1121
1146
 
1122
1147
 
1123
- class pre(TextElementBase):
1148
+ class pre(ContainerElement):
1124
1149
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre"""
1125
1150
 
1126
1151
  tag = "pre"
1127
1152
 
1128
1153
 
1129
- class progress(TextElementBase):
1154
+ class progress(ContainerElement):
1130
1155
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress"""
1131
1156
 
1132
1157
  tag = "progress"
1133
1158
 
1134
- max = JSProperty("max")
1135
- value = JSProperty("value")
1159
+ max = DOMProperty("max")
1160
+ value = DOMProperty("value")
1136
1161
 
1137
1162
 
1138
- class q(TextElementBase):
1163
+ class q(ContainerElement):
1139
1164
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q"""
1140
1165
 
1141
1166
  tag = "q"
1142
1167
 
1143
- cite = JSProperty("cite")
1168
+ cite = DOMProperty("cite")
1144
1169
 
1145
1170
 
1146
- class s(TextElementBase):
1171
+ class s(ContainerElement):
1147
1172
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s"""
1148
1173
 
1149
1174
  tag = "s"
1150
1175
 
1151
1176
 
1152
- class script(TextElementBase):
1177
+ class script(ContainerElement):
1153
1178
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script"""
1154
1179
 
1155
1180
  tag = "script"
1156
1181
 
1157
1182
  # Let's add async manually since it's a reserved keyword in Python
1158
- async_ = JSProperty("async")
1159
- blocking = JSProperty("blocking")
1160
- crossorigin = JSProperty("crossorigin")
1161
- defer = JSProperty("defer")
1162
- fetchpriority = JSProperty("fetchpriority")
1163
- integrity = JSProperty("integrity")
1164
- nomodule = JSProperty("nomodule")
1165
- nonce = JSProperty("nonce")
1166
- referrerpolicy = JSProperty("referrerpolicy")
1167
- src = JSProperty("src")
1168
- type = JSProperty("type")
1169
-
1170
-
1171
- class section(TextElementBase):
1183
+ async_ = DOMProperty("async")
1184
+ blocking = DOMProperty("blocking")
1185
+ crossorigin = DOMProperty("crossorigin")
1186
+ defer = DOMProperty("defer")
1187
+ fetchpriority = DOMProperty("fetchpriority")
1188
+ integrity = DOMProperty("integrity")
1189
+ nomodule = DOMProperty("nomodule")
1190
+ nonce = DOMProperty("nonce")
1191
+ referrerpolicy = DOMProperty("referrerpolicy")
1192
+ src = DOMProperty("src")
1193
+ type = DOMProperty("type")
1194
+
1195
+
1196
+ class section(ContainerElement):
1172
1197
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section"""
1173
1198
 
1174
1199
  tag = "section"
1175
1200
 
1176
1201
 
1177
- class select(TextElementBase):
1202
+ class select(ContainerElement, HasOptions):
1178
1203
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select"""
1179
1204
 
1180
1205
  tag = "select"
1181
1206
 
1207
+ value = DOMProperty("value")
1182
1208
 
1183
- class small(TextElementBase):
1209
+
1210
+ class small(ContainerElement):
1184
1211
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small"""
1185
1212
 
1186
1213
  tag = "small"
1187
1214
 
1188
1215
 
1189
- class source(TextElementBase):
1216
+ class source(Element):
1190
1217
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source"""
1191
1218
 
1192
1219
  tag = "source"
1193
1220
 
1194
- media = JSProperty("media")
1195
- sizes = JSProperty("sizes")
1196
- src = JSProperty("src")
1197
- srcset = JSProperty("srcset")
1198
- type = JSProperty("type")
1221
+ media = DOMProperty("media")
1222
+ sizes = DOMProperty("sizes")
1223
+ src = DOMProperty("src")
1224
+ srcset = DOMProperty("srcset")
1225
+ type = DOMProperty("type")
1199
1226
 
1200
1227
 
1201
- class span(TextElementBase):
1228
+ class span(ContainerElement):
1202
1229
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span"""
1203
1230
 
1204
1231
  tag = "span"
1205
1232
 
1206
1233
 
1207
- class strong(TextElementBase):
1234
+ class strong(ContainerElement):
1208
1235
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong"""
1209
1236
 
1210
1237
  tag = "strong"
1211
1238
 
1212
1239
 
1213
- class style(TextElementBase):
1240
+ class style(ContainerElement):
1214
1241
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style"""
1215
1242
 
1216
1243
  tag = "style"
1217
1244
 
1218
- blocking = JSProperty("blocking")
1219
- media = JSProperty("media")
1220
- nonce = JSProperty("nonce")
1221
- title = JSProperty("title")
1245
+ blocking = DOMProperty("blocking")
1246
+ media = DOMProperty("media")
1247
+ nonce = DOMProperty("nonce")
1248
+ title = DOMProperty("title")
1222
1249
 
1223
1250
 
1224
- class sub(TextElementBase):
1251
+ class sub(ContainerElement):
1225
1252
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub"""
1226
1253
 
1227
1254
  tag = "sub"
1228
1255
 
1229
1256
 
1230
- class summary(TextElementBase):
1257
+ class summary(ContainerElement):
1231
1258
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary"""
1232
1259
 
1233
1260
  tag = "summary"
1234
1261
 
1235
1262
 
1236
- class sup(TextElementBase):
1263
+ class sup(ContainerElement):
1237
1264
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup"""
1238
1265
 
1239
1266
  tag = "sup"
1240
1267
 
1241
1268
 
1242
- class table(TextElementBase):
1269
+ class table(ContainerElement):
1243
1270
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table"""
1244
1271
 
1245
1272
  tag = "table"
1246
1273
 
1247
1274
 
1248
- class tbody(TextElementBase):
1275
+ class tbody(ContainerElement):
1249
1276
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody"""
1250
1277
 
1251
1278
  tag = "tbody"
1252
1279
 
1253
1280
 
1254
- class td(TextElementBase):
1281
+ class td(ContainerElement):
1255
1282
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td"""
1256
1283
 
1257
1284
  tag = "td"
1258
1285
 
1259
- colspan = JSProperty("colspan")
1260
- headers = JSProperty("headers")
1261
- rowspan = JSProperty("rowspan")
1286
+ colspan = DOMProperty("colspan")
1287
+ headers = DOMProperty("headers")
1288
+ rowspan = DOMProperty("rowspan")
1262
1289
 
1263
1290
 
1264
- class template(TextElementBase):
1291
+ class template(ContainerElement):
1265
1292
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template"""
1266
1293
 
1267
1294
  tag = "template"
1268
1295
 
1269
- shadowrootmode = JSProperty("shadowrootmode")
1296
+ shadowrootmode = DOMProperty("shadowrootmode")
1270
1297
 
1271
1298
 
1272
- class textarea(TextElementBase):
1299
+ class textarea(ContainerElement):
1273
1300
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea"""
1274
1301
 
1275
1302
  tag = "textarea"
1276
1303
 
1277
- autocapitalize = JSProperty("autocapitalize")
1278
- autocomplete = JSProperty("autocomplete")
1279
- autofocus = JSProperty("autofocus")
1280
- cols = JSProperty("cols")
1281
- dirname = JSProperty("dirname")
1282
- disabled = JSProperty("disabled")
1283
- form = JSProperty("form")
1284
- maxlength = JSProperty("maxlength")
1285
- minlength = JSProperty("minlength")
1286
- name = JSProperty("name")
1287
- placeholder = JSProperty("placeholder")
1288
- readonly = JSProperty("readonly")
1289
- required = JSProperty("required")
1290
- rows = JSProperty("rows")
1291
- spellcheck = JSProperty("spellcheck")
1292
- wrap = JSProperty("wrap")
1293
-
1294
-
1295
- class tfoot(TextElementBase):
1304
+ autocapitalize = DOMProperty("autocapitalize")
1305
+ autocomplete = DOMProperty("autocomplete")
1306
+ autofocus = DOMProperty("autofocus")
1307
+ cols = DOMProperty("cols")
1308
+ dirname = DOMProperty("dirname")
1309
+ disabled = DOMProperty("disabled")
1310
+ form = DOMProperty("form")
1311
+ maxlength = DOMProperty("maxlength")
1312
+ minlength = DOMProperty("minlength")
1313
+ name = DOMProperty("name")
1314
+ placeholder = DOMProperty("placeholder")
1315
+ readonly = DOMProperty("readonly")
1316
+ required = DOMProperty("required")
1317
+ rows = DOMProperty("rows")
1318
+ spellcheck = DOMProperty("spellcheck")
1319
+ value = DOMProperty("value")
1320
+ wrap = DOMProperty("wrap")
1321
+
1322
+
1323
+ class tfoot(ContainerElement):
1296
1324
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot"""
1297
1325
 
1298
1326
  tag = "tfoot"
1299
1327
 
1300
1328
 
1301
- class th(TextElementBase):
1329
+ class th(ContainerElement):
1302
1330
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th"""
1303
1331
 
1304
1332
  tag = "th"
1305
1333
 
1306
1334
 
1307
- class thead(TextElementBase):
1335
+ class thead(ContainerElement):
1308
1336
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead"""
1309
1337
 
1310
1338
  tag = "thead"
1311
1339
 
1312
1340
 
1313
- class time(TextElementBase):
1341
+ class time(ContainerElement):
1314
1342
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time"""
1315
1343
 
1316
1344
  tag = "time"
1317
1345
 
1318
- datetime = JSProperty("datetime")
1346
+ datetime = DOMProperty("datetime")
1319
1347
 
1320
1348
 
1321
- class title(TextElementBase):
1349
+ class title(ContainerElement):
1322
1350
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title"""
1323
1351
 
1324
1352
  tag = "title"
1325
1353
 
1326
1354
 
1327
- class tr(TextElementBase):
1355
+ class tr(ContainerElement):
1328
1356
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr"""
1329
1357
 
1330
1358
  tag = "tr"
1331
1359
 
1332
- abbr = JSProperty("abbr")
1333
- colspan = JSProperty("colspan")
1334
- headers = JSProperty("headers")
1335
- rowspan = JSProperty("rowspan")
1336
- scope = JSProperty("scope")
1360
+ abbr = DOMProperty("abbr")
1361
+ colspan = DOMProperty("colspan")
1362
+ headers = DOMProperty("headers")
1363
+ rowspan = DOMProperty("rowspan")
1364
+ scope = DOMProperty("scope")
1337
1365
 
1338
1366
 
1339
- class track(TextElementBase):
1367
+ class track(Element):
1340
1368
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track"""
1341
1369
 
1342
1370
  tag = "track"
1343
1371
 
1344
- default = JSProperty("default")
1345
- kind = JSProperty("kind")
1346
- label = JSProperty("label")
1347
- src = JSProperty("src")
1348
- srclang = JSProperty("srclang")
1372
+ default = DOMProperty("default")
1373
+ kind = DOMProperty("kind")
1374
+ label = DOMProperty("label")
1375
+ src = DOMProperty("src")
1376
+ srclang = DOMProperty("srclang")
1349
1377
 
1350
1378
 
1351
- class u(TextElementBase):
1379
+ class u(ContainerElement):
1352
1380
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u"""
1353
1381
 
1354
1382
  tag = "u"
1355
1383
 
1356
1384
 
1357
- class ul(TextElementBase):
1385
+ class ul(ContainerElement):
1358
1386
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul"""
1359
1387
 
1360
1388
  tag = "ul"
1361
1389
 
1362
1390
 
1363
- class var(TextElementBase):
1391
+ class var(ContainerElement):
1364
1392
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var"""
1365
1393
 
1366
1394
  tag = "var"
1367
1395
 
1368
1396
 
1369
- class video(TextElementBase):
1397
+ class video(ContainerElement):
1370
1398
  """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video"""
1371
1399
 
1372
1400
  tag = "video"
1373
1401
 
1374
- autoplay = JSProperty("autoplay")
1375
- controls = JSProperty("controls")
1376
- crossorigin = JSProperty("crossorigin")
1377
- disablepictureinpicture = JSProperty("disablepictureinpicture")
1378
- disableremoteplayback = JSProperty("disableremoteplayback")
1379
- height = JSProperty("height")
1380
- loop = JSProperty("loop")
1381
- muted = JSProperty("muted")
1382
- playsinline = JSProperty("playsinline")
1383
- poster = JSProperty("poster")
1384
- preload = JSProperty("preload")
1385
- src = JSProperty("src")
1386
- width = JSProperty("width")
1402
+ autoplay = DOMProperty("autoplay")
1403
+ controls = DOMProperty("controls")
1404
+ crossorigin = DOMProperty("crossorigin")
1405
+ disablepictureinpicture = DOMProperty("disablepictureinpicture")
1406
+ disableremoteplayback = DOMProperty("disableremoteplayback")
1407
+ height = DOMProperty("height")
1408
+ loop = DOMProperty("loop")
1409
+ muted = DOMProperty("muted")
1410
+ playsinline = DOMProperty("playsinline")
1411
+ poster = DOMProperty("poster")
1412
+ preload = DOMProperty("preload")
1413
+ src = DOMProperty("src")
1414
+ width = DOMProperty("width")
1415
+ videoHeight = DOMProperty("videoHeight")
1416
+ videoWidth = DOMProperty("videoWidth")
1417
+
1418
+ def snap(
1419
+ self,
1420
+ to: Element | str = None,
1421
+ width: int | None = None,
1422
+ height: int | None = None,
1423
+ ):
1424
+ """
1425
+ Capture a snapshot (i.e. a single frame) of a video to a canvas.
1426
+
1427
+ Inputs:
1428
+
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).
1432
+
1433
+ Output:
1434
+ (Element) canvas element where the video frame snapshot was drawn into
1435
+ """
1436
+ width = width if width is not None else self.videoWidth
1437
+ height = height if height is not None else self.videoHeight
1438
+
1439
+ if to is None:
1440
+ to = canvas(width=width, height=height)
1441
+
1442
+ elif isinstance(to, Element):
1443
+ if to.tag != "canvas":
1444
+ raise TypeError("Element to snap to must be a canvas.")
1445
+
1446
+ elif getattr(to, "tagName", "") == "CANVAS":
1447
+ to = canvas(dom_element=to)
1448
+
1449
+ # If 'to' is a string, then assume it is a query selector.
1450
+ elif isinstance(to, str):
1451
+ nodelist = document.querySelectorAll(to) # NOQA
1452
+ if nodelist.length == 0:
1453
+ raise TypeError("No element with selector {to} to snap to.")
1454
+
1455
+ if nodelist[0].tagName != "CANVAS":
1456
+ raise TypeError("Element to snap to must be a canvas.")
1457
+
1458
+ to = canvas(dom_element=nodelist[0])
1459
+
1460
+ to.draw(self, width, height)
1461
+
1462
+ return to
1463
+
1464
+
1465
+ class wbr(Element):
1466
+ """Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/wbr"""
1467
+
1468
+ tag = "wbr"
1387
1469
 
1388
1470
 
1389
1471
  # Custom Elements
1390
- class grid(TextElementBase):
1472
+ class grid(ContainerElement):
1391
1473
  tag = "div"
1392
1474
 
1393
1475
  def __init__(self, layout, content=None, gap=None, **kwargs):
@@ -1396,10 +1478,68 @@ class grid(TextElementBase):
1396
1478
  self.style["grid-template-columns"] = layout
1397
1479
 
1398
1480
  # TODO: This should be a property
1399
- if not gap is None:
1481
+ if gap is not None:
1400
1482
  self.style["gap"] = gap
1401
1483
 
1402
1484
 
1485
+ class ClassesCollection:
1486
+ def __init__(self, collection: "ElementCollection") -> None:
1487
+ self._collection = collection
1488
+
1489
+ def __contains__(self, class_name):
1490
+ for element in self._collection:
1491
+ if class_name in element.classes:
1492
+ return True
1493
+
1494
+ return False
1495
+
1496
+ def __eq__(self, other):
1497
+ return (
1498
+ isinstance(other, ClassesCollection)
1499
+ and self._collection == other._collection
1500
+ )
1501
+
1502
+ def __iter__(self):
1503
+ for class_name in self._all_class_names():
1504
+ yield class_name
1505
+
1506
+ def __len__(self):
1507
+ return len(self._all_class_names())
1508
+
1509
+ def __repr__(self):
1510
+ return f"ClassesCollection({repr(self._collection)})"
1511
+
1512
+ def __str__(self):
1513
+ return " ".join(self._all_class_names())
1514
+
1515
+ def add(self, *class_names):
1516
+ for element in self._collection:
1517
+ element.classes.add(*class_names)
1518
+
1519
+ def contains(self, class_name):
1520
+ return class_name in self
1521
+
1522
+ def remove(self, *class_names):
1523
+ for element in self._collection:
1524
+ element.classes.remove(*class_names)
1525
+
1526
+ def replace(self, old_class, new_class):
1527
+ for element in self._collection:
1528
+ element.classes.replace(old_class, new_class)
1529
+
1530
+ def toggle(self, *class_names):
1531
+ for element in self._collection:
1532
+ element.classes.toggle(*class_names)
1533
+
1534
+ def _all_class_names(self):
1535
+ all_class_names = set()
1536
+ for element in self._collection:
1537
+ for class_name in element.classes:
1538
+ all_class_names.add(class_name)
1539
+
1540
+ return all_class_names
1541
+
1542
+
1403
1543
  class StyleCollection:
1404
1544
  def __init__(self, collection: "ElementCollection") -> None:
1405
1545
  self._collection = collection
@@ -1414,6 +1554,9 @@ class StyleCollection:
1414
1554
  for element in self._collection._elements:
1415
1555
  element.style[key] = value
1416
1556
 
1557
+ def __repr__(self):
1558
+ return f"StyleCollection({repr(self._collection)})"
1559
+
1417
1560
  def remove(self, key):
1418
1561
  for element in self._collection._elements:
1419
1562
  element.style.remove(key)
@@ -1422,29 +1565,70 @@ class StyleCollection:
1422
1565
  class ElementCollection:
1423
1566
  def __init__(self, elements: [Element]) -> None:
1424
1567
  self._elements = elements
1425
- self.style = StyleCollection(self)
1568
+ self._classes = ClassesCollection(self)
1569
+ self._style = StyleCollection(self)
1570
+
1571
+ def __eq__(self, obj):
1572
+ """Check for equality by comparing the underlying DOM elements."""
1573
+ return isinstance(obj, ElementCollection) and obj._elements == self._elements
1426
1574
 
1427
1575
  def __getitem__(self, key):
1428
1576
  # If it's an integer we use it to access the elements in the collection
1429
1577
  if isinstance(key, int):
1430
1578
  return self._elements[key]
1579
+
1431
1580
  # If it's a slice we use it to support slice operations over the elements
1432
1581
  # in the collection
1433
1582
  elif isinstance(key, slice):
1434
1583
  return ElementCollection(self._elements[key])
1435
1584
 
1436
- # If it's anything else (basically a string) we use it as a selector
1437
- # TODO: Write tests!
1438
- elements = self._element.querySelectorAll(key)
1439
- return ElementCollection([Element(el) for el in elements])
1585
+ # If it's anything else (basically a string) we use it as a query selector.
1586
+ return self.find(key)
1587
+
1588
+ def __iter__(self):
1589
+ yield from self._elements
1440
1590
 
1441
1591
  def __len__(self):
1442
1592
  return len(self._elements)
1443
1593
 
1444
- def __eq__(self, obj):
1445
- """Check if the element is the same as the other element by comparing
1446
- the underlying JS element"""
1447
- return isinstance(obj, ElementCollection) and obj._elements == self._elements
1594
+ def __repr__(self):
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)
1448
1632
 
1449
1633
  def _get_attribute(self, attr, index=None):
1450
1634
  if index is None:
@@ -1457,28 +1641,39 @@ class ElementCollection:
1457
1641
  for el in self._elements:
1458
1642
  setattr(el, attr, value)
1459
1643
 
1460
- @property
1461
- def html(self):
1462
- return self._get_attribute("html")
1463
-
1464
- @html.setter
1465
- def html(self, value):
1466
- self._set_attribute("html", value)
1467
-
1468
- @property
1469
- def value(self):
1470
- return self._get_attribute("value")
1471
-
1472
- @value.setter
1473
- def value(self, value):
1474
- self._set_attribute("value", value)
1475
1644
 
1476
- @property
1477
- def children(self):
1478
- return self._elements
1479
-
1480
- def __iter__(self):
1481
- yield from self._elements
1482
-
1483
- def __repr__(self):
1484
- return f"{self.__class__.__name__} (length: {len(self._elements)}) {self._elements}"
1645
+ # fmt: off
1646
+ ELEMENT_CLASSES = [
1647
+ # We put grid first because it is really just a <div> but we want the div class to
1648
+ # be used if wrapping existing js elements that we have not tagged with a
1649
+ # `data-pyscript-type` attribute (last one is the winner when it comes to this
1650
+ # list).
1651
+ grid,
1652
+ # The rest in alphabetical order.
1653
+ a, abbr, address, area, article, aside, audio,
1654
+ b, base, blockquote, body, br, button,
1655
+ canvas, caption, cite, code, col, colgroup,
1656
+ data, datalist, dd, del_, details, dialog, div, dl, dt,
1657
+ em, embed,
1658
+ fieldset, figcaption, figure, footer, form,
1659
+ h1, h2, h3, h4, h5, h6, head, header, hgroup, hr, html,
1660
+ i, iframe, img, input_, ins,
1661
+ kbd,
1662
+ label, legend, li, link,
1663
+ main, map_, mark, menu, meta, meter,
1664
+ nav,
1665
+ object_, ol, optgroup, option, output,
1666
+ p, param, picture, pre, progress,
1667
+ q,
1668
+ s, script, section, select, small, source, span, strong, style, sub, summary, sup,
1669
+ table, tbody, td, template, textarea, tfoot, th, thead, time, title, tr, track,
1670
+ u, ul,
1671
+ var, video,
1672
+ wbr,
1673
+ ]
1674
+ # fmt: on
1675
+
1676
+
1677
+ # Lookup tables to get an element class by its name or tag.
1678
+ ELEMENT_CLASSES_BY_NAME = {cls.__name__: cls for cls in ELEMENT_CLASSES}
1679
+ ELEMENT_CLASSES_BY_TAG = {cls.tag: cls for cls in ELEMENT_CLASSES}