Files
pyscript/pyscript.core/src/stdlib/pyweb/ui/elements.py
2024-02-02 17:35:59 +00:00

383 lines
7.9 KiB
Python

from textwrap import dedent
from pyscript import document, when, window
from pyweb import JSProperty, js_property, pydom
# Global attributes that all elements have (this list is a subset of the official one)
# and tries to capture the most used ones
GLOBAL_ATTRIBUTES = [
"accesskey",
"autocapitalize",
"autofocus",
"draggable",
"enterkeyhint",
"hidden",
"id",
"lang",
"nonce",
"part",
"popover",
"slot",
"spellcheck",
"tabindex",
"title",
"translate",
"virtualkeyboardpolicy",
"className",
]
# class and style are different ones that are handled by pydom.element directly
class ElementBase(pydom.Element):
tag = "div"
def __init__(self, style=None, **kwargs):
super().__init__(document.createElement(self.tag))
# set all the style properties provided in input
if style:
for key, value in style.items():
self.style[key] = value
# IMPORTANT!!! This is used to auto-harvest all input arguments and set them as properties
self._init_properties(**kwargs)
def _init_properties(self, **kwargs):
"""Set all the properties (of type JSProperties) provided in input as properties
of the class instance.
Args:
**kwargs: The properties to set
"""
# Look at all the properties of the class and see if they were provided in kwargs
for attr_name, attr in self.__class__.__dict__.items():
# For each one, actually check if it is a property of the class and set it
if isinstance(attr, JSProperty) and attr_name in kwargs:
setattr(self, attr_name, kwargs[attr_name])
class TextElementBase(ElementBase):
def __init__(self, content=None, style=None, **kwargs):
super().__init__(style=style, **kwargs)
# If it's an element, append the element
if isinstance(content, pydom.Element):
self.append(content)
# If it's a list of elements
elif isinstance(content, list):
for item in content:
self.append(item)
# If the content wasn't set just ignore
elif content is None:
pass
else:
# Otherwise, set content as the html of the element
self.html = content
def _add_js_properties(cls, *attrs):
"""Add JSProperties to a class as `js_property` class attributes."""
# First we set all the global properties as JSProperties
for attr in GLOBAL_ATTRIBUTES:
setattr(cls, attr, js_property(attr))
# Now the specific class properties
for attr in attrs:
setattr(cls, attr, js_property(attr))
# Now we patch the __init__ method to specify the properties
cls.__init__.__doc__ = f"""Class constructor.
Args:
* content: The content of the element (can be a string, a list of elements or a single element)
* style: The style of the element (a dictionary)
* All the properties of the class: {attrs}
"""
# IMPORTANT: For all HTML components defined below, we are not mapping all
# available attributes, only the global ones
class a(TextElementBase):
tag = "a"
# Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#attributes
_add_js_properties(a, "download", "href", "referrerpolicy", "rel", "target", "type")
class button(TextElementBase):
tag = "button"
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attributes
_add_js_properties(
button,
"autofocus",
"disabled",
"form",
"formaction",
"formenctype",
"formmethod",
"formnovalidate",
"formtarget",
"name",
"type",
"value",
)
class h1(TextElementBase):
tag = "h1"
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Heading_Elements#attributes
# Heading elements only have global attributes
_add_js_properties(h1)
class h2(TextElementBase):
tag = "h2"
_add_js_properties(h2)
class h3(TextElementBase):
tag = "h3"
_add_js_properties(h3)
class h4(TextElementBase):
tag = "h4"
_add_js_properties(h4)
class h5(TextElementBase):
tag = "h5"
_add_js_properties(h5)
class h6(TextElementBase):
tag = "h6"
_add_js_properties(h6)
class link(TextElementBase):
tag = "link"
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link#attributes
_add_js_properties(
link,
"as",
"crossorigin",
"disabled",
"fetchpriority",
"href",
"imagesizes",
"imagesrcset",
"integrity",
"media",
"rel",
"referrerpolicy",
"sizes",
"title",
"type",
)
class style(TextElementBase):
tag = "style"
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style#attributes
_add_js_properties(style, "blocking", "media", "nonce", "title")
class script(TextElementBase):
tag = "script"
# Let's add async manually since it's a reserved keyword in Python
async_ = js_property("async")
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attributes
_add_js_properties(
script,
"blocking",
"crossorigin",
"defer",
"fetchpriority",
"integrity",
"nomodule",
"nonce",
"referrerpolicy",
"src",
"type",
)
class p(TextElementBase):
tag = "p"
# p tags only have the global attributes ones
_add_js_properties(p)
class code(TextElementBase):
tag = "code"
# code tags only have the global attributes ones
_add_js_properties(code)
class pre(TextElementBase):
tag = "pre"
# pre tags only have the global attributes ones (others have been deprecated)
_add_js_properties(pre)
class strong(TextElementBase):
tag = "strong"
# strong tags only have the global attributes ones
_add_js_properties(strong)
class small(TextElementBase):
tag = "small"
# small tags only have the global attributes ones
_add_js_properties(small)
class br(ElementBase):
tag = "br"
# br tags only have the global attributes ones (others have been deprecated)
_add_js_properties(br)
class div(TextElementBase):
tag = "div"
# div tags only have the global attributes ones (others have been deprecated)
_add_js_properties(div)
class img(ElementBase):
tag = "img"
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img#attributes
_add_js_properties(
img,
"alt",
"crossorigin",
"decoding",
"fetchpriority",
"height",
"ismap",
"loading",
"referrerpolicy",
"sizes",
"src",
"width",
)
class input(ElementBase):
tag = "input"
# JS Properties
autofocus = js_property("autofocus")
alt = js_property("alt")
autocapitalize = js_property("autocapitalize")
autocomplete = js_property("autocomplete")
checked = js_property("checked")
disabled = js_property("disabled")
name = js_property("name")
type = js_property("type")
value = js_property("value")
placeholder = js_property("placeholder")
# TODO: This is by anymeans complete!! We need to add more attributes
def __init__(self, style=None, **kwargs):
super().__init__(style=style, **kwargs)
# https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#attributes
_add_js_properties(
input,
"accept",
"alt",
"autofocus",
"capture",
"checked",
"dirname",
"disabled",
"form",
"formaction",
"formenctype",
"formmethod",
"formnovalidate",
"formtarget",
"height",
"list",
"max",
"maxlength",
"min",
"minlength",
"multiple",
"name",
"pattern",
"placeholder",
"popovertarget",
"popovertargetaction",
"readonly",
"required",
"size",
"src",
"step",
"type",
"value",
"width",
)
# Custom Elements
class Grid(ElementBase):
tag = "div"
def __init__(self, layout="", gap=None, **kwargs):
super().__init__(**kwargs)
self.style["display"] = "grid"
self.style["grid-template-columns"] = layout
# TODO: This should be a property
if not gap is None:
self.style["gap"] = gap