mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-21 03:05:38 -05:00
add pyweb.ui
This commit is contained in:
1
pyscript.core/src/stdlib/pyweb/ui/__init__.py
Normal file
1
pyscript.core/src/stdlib/pyweb/ui/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from . import elements, shoelace
|
||||
303
pyscript.core/src/stdlib/pyweb/ui/elements.py
Normal file
303
pyscript.core/src/stdlib/pyweb/ui/elements.py
Normal file
@@ -0,0 +1,303 @@
|
||||
import string
|
||||
from textwrap import dedent
|
||||
|
||||
from pyscript import document, when, window
|
||||
from pyweb import pydom
|
||||
|
||||
|
||||
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
|
||||
kwargs['self'] = self
|
||||
self._init_properties(**kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _init_properties(**kwargs):
|
||||
self = kwargs.pop('self')
|
||||
|
||||
# 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])
|
||||
|
||||
# def __add__(self, other):
|
||||
# if isinstance(other, list):
|
||||
# other = div(*other)
|
||||
# return WidgetCollection(*self.widgets, other, separator=self.separator)
|
||||
|
||||
class TextElementBase(ElementBase):
|
||||
def __init__(self, content=None, style=None, **kwargs):
|
||||
super().__init__(style=style, **kwargs)
|
||||
|
||||
if isinstance(content, pydom.Element):
|
||||
self.append(content)
|
||||
elif isinstance(content, list):
|
||||
for item in content:
|
||||
self.append(item)
|
||||
elif content is None:
|
||||
pass
|
||||
else:
|
||||
self._js.innerHTML = content
|
||||
|
||||
class h1(TextElementBase):
|
||||
tag = "h1"
|
||||
|
||||
|
||||
class h2(TextElementBase):
|
||||
tag = "h2"
|
||||
|
||||
|
||||
class h3(TextElementBase):
|
||||
tag = "h3"
|
||||
|
||||
|
||||
class button(TextElementBase):
|
||||
tag = 'button'
|
||||
|
||||
# JS Properties
|
||||
autofocus = js_property('autofocus')
|
||||
disabled = js_property('disabled')
|
||||
name = js_property('name')
|
||||
type = js_property('type')
|
||||
value = js_property('value')
|
||||
|
||||
|
||||
class link(TextElementBase):
|
||||
tag = 'a'
|
||||
href = js_property('href')
|
||||
|
||||
def __init__(self, content, href, style = None, **kwargs):
|
||||
super().__init__(content, href=href, style=style, **kwargs)
|
||||
|
||||
class a(link):
|
||||
pass
|
||||
|
||||
class p(TextElementBase):
|
||||
tag = 'p'
|
||||
|
||||
class code(TextElementBase):
|
||||
tag = 'code'
|
||||
|
||||
class pre(TextElementBase):
|
||||
tag = 'pre'
|
||||
|
||||
class strong(TextElementBase):
|
||||
tag = 'strong'
|
||||
|
||||
class small(TextElementBase):
|
||||
tag = 'small'
|
||||
|
||||
class br(ElementBase):
|
||||
tag = 'br'
|
||||
|
||||
class div(TextElementBase):
|
||||
tag = 'div'
|
||||
|
||||
class img(ElementBase):
|
||||
tag = 'img'
|
||||
src = js_property('src')
|
||||
# TODO: This should probably go on the ElementBase class since it's a global attribtute
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/slot
|
||||
slot = js_property('slot')
|
||||
|
||||
def __init__(self, src, alt="", style = None, **kwargs):
|
||||
super().__init__(src=src, alt=alt, style=style, **kwargs)
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
# class Input(pydom.Element):
|
||||
# tag = "sl-input"
|
||||
|
||||
# label = LabelProperty()
|
||||
# placeholder = js_property('placeholder')
|
||||
# pill = js_property('pill')
|
||||
|
||||
# def __init__(self, label=None, value=None, type='text', placeholder=None, help_text=None,
|
||||
# size=None, filled=False, pill=False, disabled=False, readonly=False, autofocus=False,
|
||||
# autocomplete=None, autocorrect=None, autocapitalize=None, spellcheck=None, min=None, max=None,
|
||||
# step=None, name=None, required=False, pattern=None, minlength=None, maxlength=None,
|
||||
# ):
|
||||
# super().__init__()
|
||||
|
||||
# # Now lets map all the properties to the js object
|
||||
# self.label = label
|
||||
# self.placeholder = placeholder
|
||||
# self.pill = pill
|
||||
# # self.value = value
|
||||
# # self.type = type
|
||||
# # self.placeholder = placeholder
|
||||
# # self.help_text = help_text
|
||||
# # self.size = size
|
||||
# # self.filled = filled
|
||||
# # self.pill = pill
|
||||
# # self.disabled = disabled
|
||||
# # self.readonly = readonly
|
||||
# # self.autofocus = autofocus
|
||||
# # self.autocomplete = autocomplete
|
||||
# # self.autocorrect = autocorrect
|
||||
# # self.autocapitalize = autocapitalize
|
||||
# # self.spellcheck = spellcheck
|
||||
# # self.min = min
|
||||
# # self.max = max
|
||||
# # self.step = step
|
||||
# # self.name = name
|
||||
# # self.required = required
|
||||
# # self.pattern = pattern
|
||||
# # self.minlength = minlength
|
||||
# # self.maxlength = maxlength
|
||||
|
||||
# @property
|
||||
# def value(self):
|
||||
# return self._js.value
|
||||
|
||||
# @value.setter
|
||||
# def value(self, value):
|
||||
# self._js.value = value
|
||||
|
||||
# @property
|
||||
# def type(self):
|
||||
# return self._js.type
|
||||
|
||||
# @type.setter
|
||||
# def type(self, value):
|
||||
# self._js.type = value
|
||||
|
||||
# @property
|
||||
# def placeholder(self):
|
||||
# return self._js.placeholder
|
||||
|
||||
# @placeholder.setter
|
||||
# def placeholder(self, value):
|
||||
# self._js.placeholder = value
|
||||
|
||||
# @property
|
||||
# def help_text(self):
|
||||
# return self._js.helpText
|
||||
|
||||
# @help_text.setter
|
||||
# def help_text(self, value):
|
||||
# self._js.helpText = value
|
||||
|
||||
# @property
|
||||
# def size(self):
|
||||
# return self._js.size
|
||||
|
||||
# @size.setter
|
||||
# def size(self, value):
|
||||
# self._js.size = value
|
||||
|
||||
# @property
|
||||
# def filled(self):
|
||||
# return self._js.filled
|
||||
|
||||
# @filled.setter
|
||||
# def filled(self, value):
|
||||
# self._js.filled = value
|
||||
|
||||
# @property
|
||||
# def pill(self):
|
||||
# return self._js.pill
|
||||
|
||||
# @pill.setter
|
||||
# def pill(self, value):
|
||||
# self._js.pill = value
|
||||
|
||||
# @property
|
||||
# def disabled(self):
|
||||
# return self._js.disabled
|
||||
|
||||
# @disabled.setter
|
||||
# def disabled(self, value):
|
||||
# self._js.disabled = value
|
||||
|
||||
# @property
|
||||
# def readonly(self):
|
||||
# return self._js.readonly
|
||||
|
||||
# @readonly.setter
|
||||
# def readonly(self, value):
|
||||
# self._js.readonly = value
|
||||
|
||||
|
||||
|
||||
# class Badge(Widget):
|
||||
# template = '<sl-badge variant="{variant}" pill>{content}</sl-badge>'
|
||||
|
||||
# def __init__(self, content, variant='neutral'):
|
||||
# self.content = content
|
||||
# self.variant = variant
|
||||
|
||||
# class Card(ComplexWidget):
|
||||
# template = dedent("""
|
||||
# <sl-card class="card-overview">
|
||||
# {img}
|
||||
# {header}
|
||||
# {content}
|
||||
# {footer}
|
||||
# </sl-card>
|
||||
# """)
|
||||
# templates = {
|
||||
# 'content': '{content}',
|
||||
# 'footer': dedent("""
|
||||
# <div slot="footer">
|
||||
# {footer}
|
||||
# </div>
|
||||
# """),
|
||||
# 'header': dedent("""<div slot="header">
|
||||
# {header}
|
||||
# </div>"""
|
||||
# ),
|
||||
# 'img': dedent("""
|
||||
# <img crossorigin='anonymous' slot="image" src="{img}" alt="..."
|
||||
# />"""),
|
||||
# }
|
||||
|
||||
# def __init__(self, content=None, header=None, footer=None, img=None, img_alt=''):
|
||||
# self.content = to_widget(content)
|
||||
# self.header = to_widget(header)
|
||||
# self.footer = to_widget(footer)
|
||||
# self.img_alt = img_alt
|
||||
# if img:
|
||||
# self.img = to_widget(img)
|
||||
# else:
|
||||
# self.img = to_widget('')
|
||||
293
pyscript.core/src/stdlib/pyweb/ui/shoelace.py
Normal file
293
pyscript.core/src/stdlib/pyweb/ui/shoelace.py
Normal file
@@ -0,0 +1,293 @@
|
||||
import string
|
||||
from textwrap import dedent
|
||||
|
||||
from pyscript import document, when, window
|
||||
from pyweb import pydom
|
||||
import element as el
|
||||
from element import JSProperty, js_property
|
||||
|
||||
|
||||
class ShoeBase(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
|
||||
kwargs['self'] = self
|
||||
self._init_properties(**kwargs)
|
||||
|
||||
@staticmethod
|
||||
def _init_properties(**kwargs):
|
||||
self = kwargs.pop('self')
|
||||
|
||||
# Look at all the properties of the class and see if they were provided in kwargs
|
||||
# print(f"Looking for element properties for {self.__class__}...")
|
||||
for attr_name, attr in self.__class__.__dict__.items():
|
||||
# print("Checking", attr_name, isinstance(attr, ShoelaceProperty), attr_name in kwargs)
|
||||
# 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 TextShoeBase(ShoeBase):
|
||||
def __init__(self, content, style=None, **kwargs):
|
||||
if not style and getattr(self, 'default_style', None):
|
||||
style = self.default_style
|
||||
|
||||
super().__init__(style=style, **kwargs)
|
||||
|
||||
if isinstance(content, pydom.Element):
|
||||
self.append(content)
|
||||
else:
|
||||
self._js.innerHTML = content
|
||||
|
||||
class Button(ShoeBase):
|
||||
tag = 'sl-button'
|
||||
variant = js_property('variant')
|
||||
size = js_property('size')
|
||||
outline = js_property('outline')
|
||||
pill = js_property('pill')
|
||||
circle = js_property('circle')
|
||||
|
||||
def __init__(self, content, variant='primary', size=None, outline=False, pill=False, circle=False, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self._js.textContent = content
|
||||
|
||||
# IMPORTANT!!! This is use to auto-harvest all input arguments and set them as properties
|
||||
self._init_properties(**locals())
|
||||
|
||||
class Alert(TextShoeBase):
|
||||
"""Alerts are used to display important messages inline or as toast notifications.
|
||||
|
||||
Example: Alert("This is a standard alert. You can customize its content and even the icon.")"""
|
||||
|
||||
|
||||
|
||||
tag = 'sl-alert'
|
||||
open = js_property('open')
|
||||
variant = js_property('variant')
|
||||
|
||||
def __init__(self, content, variant=None, open=True, **kwargs):
|
||||
# TODO: Should content be appended so we can support html Elements as well?
|
||||
super().__init__(content, variant=variant, open=open, **kwargs)
|
||||
|
||||
class Select(ShoeBase):
|
||||
tag = 'sl-select'
|
||||
label = js_property('label')
|
||||
helpText = js_property('helpText')
|
||||
placeholder = js_property('placeholder')
|
||||
pill = js_property('pill')
|
||||
value = js_property('value')
|
||||
|
||||
def __init__(self, label=None, options=None, placeholder=None, help_text=None, value=None, style=None, **kwargs):
|
||||
super().__init__(label=label, placeholder=placeholder, help_text=help_text, value=value, style=style, **kwargs)
|
||||
html_ = '\n'.join([f'<sl-option value="{option}">{option}</sl-option>' for option in options])
|
||||
self.html = html_
|
||||
print("options", options)
|
||||
print("HTML", html_)
|
||||
print("HTML", self.html)
|
||||
# for option in options:
|
||||
# self.append(el.option(option))
|
||||
|
||||
class Button(TextShoeBase):
|
||||
"""Buttons represent actions that are available to the user."""
|
||||
tag = 'sl-button'
|
||||
variant = js_property('variant')
|
||||
size = js_property('size')
|
||||
outline = js_property('outline')
|
||||
pill = js_property('pill')
|
||||
circle = js_property('circle')
|
||||
|
||||
def __init__(self, content, variant='primary', size=None, outline=False, pill=False, circle=False, **kwargs):
|
||||
super().__init__(content, variant=variant, size=size, outline=outline, pill=pill, circle=circle, **kwargs)
|
||||
|
||||
|
||||
class Details(TextShoeBase):
|
||||
"""Details are used as a disclosure widget from which users can retrieve additional information."""
|
||||
tag = 'sl-details'
|
||||
open = js_property('open')
|
||||
summary = js_property('summary')
|
||||
disabled = js_property('disabled')
|
||||
update_complete = js_property('updateComplete')
|
||||
|
||||
def __init__(self, content, summary, open=None, disabled=None, style=None, **kwargs):
|
||||
super().__init__(content, summary=summary, open=open, disabled=disabled, style=style, **kwargs)
|
||||
|
||||
|
||||
class Dialog(TextShoeBase):
|
||||
tag = 'sl-dialog'
|
||||
label = js_property('label')
|
||||
noheader = js_property('noheader')
|
||||
open = js_property('open')
|
||||
# TODO: We should map the `modal` property as well but it's a bit of special...
|
||||
|
||||
|
||||
def __init__(self, content, label=None, open=None, disabled=None, style=None, **kwargs):
|
||||
super().__init__(content, label=label, open=open, disabled=disabled, style=style, **kwargs)
|
||||
|
||||
|
||||
class Divider(ShoeBase):
|
||||
tag = 'sl-divider'
|
||||
|
||||
vertical = js_property('vertical')
|
||||
def __init__(self, vertical=None, **kwargs):
|
||||
super().__init__(vertical=vertical, **kwargs)
|
||||
|
||||
self._init_properties(**locals())
|
||||
|
||||
class BaseMixin(pydom.Element):
|
||||
@property
|
||||
def label(self):
|
||||
return self._js.label
|
||||
|
||||
@label.setter
|
||||
def label(self, value):
|
||||
self._js.label = value
|
||||
|
||||
# class LabelProperty:
|
||||
# def __get__(self, obj, objtype=None):
|
||||
# return obj._js.label
|
||||
|
||||
# def __set__(self, obj, value):
|
||||
# obj._js.label = value
|
||||
|
||||
class PlaceholderProperty:
|
||||
def __get__(self, obj, objtype=None):
|
||||
return obj._js.placeholder
|
||||
|
||||
def __set__(self, obj, value):
|
||||
obj._js.placeholder = value
|
||||
|
||||
class Input(ShoeBase):
|
||||
tag = "sl-input"
|
||||
|
||||
label = js_property('label')
|
||||
placeholder = js_property('placeholder')
|
||||
pill = js_property('pill')
|
||||
help_text = js_property('helpText')
|
||||
value = js_property('value')
|
||||
|
||||
def __init__(self, label=None, value=None, type='text', placeholder=None, help_text=None,
|
||||
size=None, filled=False, pill=False, disabled=False, readonly=False, autofocus=False,
|
||||
autocomplete=None, autocorrect=None, autocapitalize=None, spellcheck=None, min=None, max=None,
|
||||
step=None, name=None, required=False, pattern=None, minlength=None, maxlength=None,
|
||||
style=None, **kwargs):
|
||||
super().__init__(style=style, label=label, value=value, type=type, placeholder=placeholder, help_text=help_text,
|
||||
size=size, filled=filled, pill=pill, disabled=disabled, readonly=readonly, autofocus=autofocus,
|
||||
autocomplete=autocomplete, autocorrect=autocorrect, autocapitalize=autocapitalize,
|
||||
spellcheck=spellcheck, min=min, max=max,
|
||||
step=step, name=name, required=required, pattern=pattern, minlength=minlength, maxlength=maxlength,
|
||||
**kwargs)
|
||||
|
||||
|
||||
class Badge(TextShoeBase):
|
||||
tag = 'sl-badge'
|
||||
variant = js_property('variant')
|
||||
pill = js_property('pill')
|
||||
pulse = js_property('pulse')
|
||||
|
||||
|
||||
class Rating(ShoeBase):
|
||||
tag = 'sl-rating'
|
||||
label = js_property('label')
|
||||
value = js_property('value')
|
||||
max = js_property('max')
|
||||
# TODO: Properties missing...
|
||||
|
||||
|
||||
class TextArea(ShoeBase):
|
||||
tag = 'sl-textarea'
|
||||
label = js_property('label')
|
||||
helpText = js_property('helpText')
|
||||
# TODO: Properties missing...
|
||||
|
||||
class Card(TextShoeBase):
|
||||
tag = 'sl-card'
|
||||
|
||||
def __init__(self, content=None, image=None, img_alt=None, header=None, footer=None, style=None, **kwargs):
|
||||
main_div = el.div()
|
||||
if image:
|
||||
if not isinstance(image, el.img):
|
||||
image = el.img(image, alt=img_alt)
|
||||
|
||||
image.slot = "image"
|
||||
|
||||
if content:
|
||||
if isinstance(content, pydom.Element):
|
||||
main_div.append(content)
|
||||
else:
|
||||
main_div.append(el.div(content))
|
||||
main_div.append(content)
|
||||
|
||||
|
||||
super().__init__(content, style=style, **kwargs)
|
||||
self._js.insertBefore(image._js, self._js.firstChild)
|
||||
|
||||
if header:
|
||||
header = el.div(header, slot="header")
|
||||
self._js.insertBefore(header._js, self._js.firstChild)
|
||||
|
||||
if footer:
|
||||
self.append(el.div(footer, slot="footer"))
|
||||
|
||||
self.add_class('card-overview')
|
||||
|
||||
|
||||
# ************* EXAMPLES SECTION *************
|
||||
|
||||
LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
details_code = """
|
||||
LOREM_IPSUM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
Details(LOREM_IPSUM, summary="Try me")
|
||||
"""
|
||||
example_dialog_close_btn = Button("Close")
|
||||
example_dialog = Dialog(el.div([el.p(LOREM_IPSUM), example_dialog_close_btn]), label="Try me")
|
||||
example_dialog_btn = Button("Open Dialog")
|
||||
|
||||
def toggle_dialog():
|
||||
example_dialog.open = not (example_dialog.open)
|
||||
|
||||
|
||||
when('click', example_dialog_btn)(toggle_dialog)
|
||||
when('click', example_dialog_close_btn)(toggle_dialog)
|
||||
|
||||
pydom.body.append(example_dialog)
|
||||
examples = {
|
||||
'Alert': {
|
||||
"instance": Alert("This is a standard alert. You can customize its content and even the icon."),
|
||||
"code": el.code("Alert('This is a standard alert. You can customize its content and even the icon.'"),
|
||||
},
|
||||
'Button': {
|
||||
"instance": Button("Try me"),
|
||||
"code": el.code('Button("Try me")'),
|
||||
},
|
||||
'Card': {
|
||||
"instance": Card(el.p("This is a cool card!"), image="https://pyscript.net/assets/images/pyscript-sticker-black.svg",
|
||||
footer=el.div([Button("More Info"), Rating()])),
|
||||
"code": el.code('''
|
||||
Card(el.p("This is a cool card!"), image="https://pyscript.net/assets/images/pyscript-sticker-black.svg", footer=el.div([Button("More Info"), Rating()]))
|
||||
'''),
|
||||
},
|
||||
'Details': {
|
||||
"instance": Details(LOREM_IPSUM, summary="Try me"),
|
||||
"code": el.code('Details(LOREM_IPSUM, summary="Try me")'),
|
||||
},
|
||||
'Dialog': {
|
||||
"instance": example_dialog_btn,
|
||||
"code": el.code('Dialog(div([p(LOREM_IPSUM), Button("Close")]), summary="Try me")'),
|
||||
},
|
||||
'Divider': {
|
||||
"instance": Divider(),
|
||||
"code": el.code('Divider()'),
|
||||
},
|
||||
'Rating': {
|
||||
"instance": Rating(),
|
||||
"code": el.code('Rating()'),
|
||||
},
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user