@pyscript/core 0.4.55 → 0.4.56

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