#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Conclete implemententions for Element node."""
from collections import OrderedDict, UserDict
import html as html_
from typing import Any, Callable, Dict, Iterable, Iterator, List
from typing import MutableSequence, Optional, Tuple, Type, Union
from typing import TYPE_CHECKING
from weakref import WeakSet, WeakValueDictionary
from xml.etree.ElementTree import HTML_EMPTY # type: ignore
from wdom.css import CSSStyleDeclaration
from wdom.event import EventTarget, Event
from wdom.node import AbstractNode, Node, ParentNode, NonDocumentTypeChildNode
from wdom.node import DocumentFragment, NodeList, ChildNode
from wdom.parser import FragmentParser
if TYPE_CHECKING:
from typing import MutableMapping # noqa
_AttrValueType = Union[List[str], str, int, bool, CSSStyleDeclaration, None]
[docs]class DOMTokenList(MutableSequence[str]):
"""Collection of DOM token strings.
DOM token is a string which does not contain spases.
This class is mainly used for class list.
"""
def __init__(self, owner: Union[Node, Type['HTMLElement']],
*args: Union[str, 'DOMTokenList']) -> None:
"""Initialize with owner node (maybe type of node) and initial values.
:arg owner: Node/Node-class which has this collection.
:arg args: space-separated string or iterable of tokens.
"""
self._list = list() # type: List[str]
self._owner = owner
self._append(args)
def __len__(self) -> int:
return len(self._list)
def __contains__(self, item: object) -> bool:
return item in self._list
def __iter__(self) -> Iterator[str]:
for token in self._list:
yield token
def _validate_token(self, token: str) -> None:
if not isinstance(token, str):
raise TypeError(
'Token must be str, but {} passed.'.format(type(token)))
if ' ' in token:
raise ValueError(
'Token contains space characters, which are invalid.')
def _append(self, token: Union[Iterable, str]) -> None:
if isinstance(token, str):
for t in token.split(' '):
self.add(t)
elif isinstance(token, Iterable):
for t in token:
self._append(t)
elif token is None:
pass
else:
raise TypeError
def __getitem__(self, index: Union[int, slice] # type: ignore
) -> Optional[str]:
if isinstance(index, slice):
TypeError('slicing is not supported.')
elif 0 <= index < len(self._list):
return self._list[index]
return None
def __setitem__(self, s, item) -> None: # type: ignore
raise NotImplementedError
def __delitem__(self, index: int) -> None: # type: ignore
raise NotImplementedError
@property
def length(self) -> int:
"""Get number of DOM token in this list."""
return self.__len__()
[docs] def add(self, *tokens: str) -> None:
"""Add new tokens to list."""
from wdom.web_node import WdomElement
_new_tokens = []
for token in tokens:
self._validate_token(token)
if token and token not in self:
self._list.append(token)
_new_tokens.append(token)
if isinstance(self._owner, WdomElement) and _new_tokens:
self._owner.js_exec('addClass', _new_tokens) # type: ignore
[docs] def remove(self, *tokens: str) -> None:
"""Remove tokens from list."""
from wdom.web_node import WdomElement
_removed_tokens = []
for token in tokens:
self._validate_token(token)
if token in self:
self._list.remove(token)
_removed_tokens.append(token)
if isinstance(self._owner, WdomElement) and _removed_tokens:
self._owner.js_exec('removeClass', _removed_tokens) # type: ignore
[docs] def toggle(self, token: str) -> None:
"""Add or remove token to/from list.
If token is in this list, the token will be removed. Otherwise add it
to list.
"""
self._validate_token(token)
if token in self:
self.remove(token)
else:
self.add(token)
[docs] def item(self, index: int) -> Optional[str]:
"""Return the token of the ``index``.
``index`` must be 0 or positive integer. If index is out of range,
return None.
"""
return self[index]
[docs] def insert(self, index: int, item: str) -> None:
"""Not implemented."""
raise NotImplementedError
[docs] def contains(self, token: str) -> bool:
"""Return if the token is in the list or not."""
self._validate_token(token)
return token in self
[docs] def toString(self) -> str:
"""Return string representation of this list.
Actually it will be a spase-separated tokens.
"""
return ' '.join(self)
[docs]class Attr:
"""Attribute node.
In the latest DOM specification, Attr interface does not inherits ``Node``
interface. (Previously, Attr inherited Node interface.)
"""
def __init__(self, name: str,
value: _AttrValueType = None,
owner: Node = None) -> None:
"""Initialize this attribute.
:arg str name: property name.
:arg _AttrValueType value: attribute value.
:arg Node owner: owner node of this attribute (optional).
"""
self._name = name.lower()
self._value = value
self._owner = owner
@property
def html(self) -> str:
"""Return string representation of this.
Used in start tag of HTML representation of the Element node.
"""
if self._owner and self.name in self._owner._special_attr_boolean:
return self.name
else:
value = self.value
if isinstance(value, str):
value = html_.escape(value)
return '{name}="{value}"'.format(name=self.name, value=value)
@property
def name(self) -> str:
"""Name of this attr."""
return self._name
@property
def value(self) -> _AttrValueType:
"""Value of this attr."""
return self._value or ''
@value.setter
def value(self, val: str) -> None:
self._value = val
@property
def isId(self) -> bool:
"""Return True if this Attr is an ID node (name is ``id``)."""
return self.name.lower() == 'id'
class DraggableAttr(Attr):
"""Attribute node class for draggable attribute."""
@property
def html(self) -> str:
"""Return html representation."""
if isinstance(self.value, bool):
val = 'true' if self.value else 'false'
else:
val = str(self.value)
return 'draggable="{}"'.format(val)
[docs]class NamedNodeMap(UserDict):
"""Collection of Attr objects."""
def __init__(self, owner: Node) -> None:
"""Initialize with owner node.
:arg Node owner: owner node of this object.
"""
self._owner = owner
self._dict = OrderedDict() # type: OrderedDict[str, Attr]
def __len__(self) -> int:
return len(self._dict)
def __contains__(self, item: object) -> bool:
return item in self._dict
def __getitem__(self, index: Union[int, str]) -> Optional[Attr]:
if isinstance(index, int):
return tuple(self._dict.values())[index]
return None
def __setitem__(self, attr: str, item: Attr) -> None:
self._dict[attr] = item
def __delitem__(self, attr: str) -> None:
del self._dict[attr]
def __iter__(self) -> Iterator[str]:
for attr in self._dict.keys():
yield attr
@property
def length(self) -> int:
"""Return number of Attrs in this collection."""
return len(self)
[docs] def getNamedItem(self, name: str) -> Optional[Attr]:
"""Get ``Attr`` object which has ``name``.
If does not have ``name`` attr, return None.
"""
return self._dict.get(name, None)
[docs] def setNamedItem(self, item: Attr) -> None:
"""Set ``Attr`` object in this collection."""
from wdom.web_node import WdomElement
if not isinstance(item, Attr):
raise TypeError('item must be an instance of Attr')
if isinstance(self._owner, WdomElement):
self._owner.js_exec('setAttribute', item.name, # type: ignore
item.value)
self._dict[item.name] = item
item._owner = self._owner
[docs] def removeNamedItem(self, item: Attr) -> Optional[Attr]:
"""Set ``Attr`` object and return it (if exists)."""
from wdom.web_node import WdomElement
if not isinstance(item, Attr):
raise TypeError('item must be an instance of Attr')
if isinstance(self._owner, WdomElement):
self._owner.js_exec('removeAttribute', item.name)
removed_item = self._dict.pop(item.name, None)
if removed_item:
removed_item._owner = self._owner
return removed_item
[docs] def item(self, index: int) -> Optional[Attr]:
"""Return ``index``-th attr node."""
if 0 <= index < len(self):
return self._dict[tuple(self._dict.keys())[index]]
return None
[docs] def toString(self) -> str:
"""Return string representation of collections."""
return ' '.join(attr.html for attr in self._dict.values())
class ElementParser(FragmentParser):
"""HTML Parser class whose default nodes are ``Element``."""
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D102
super().__init__(*args, **kwargs)
self.default_class = Element
class HTMLElementParser(ElementParser):
"""HTML Parser class whose default nodes are ``HTMLElement``."""
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D102
super().__init__(*args, **kwargs)
self.default_class = HTMLElement
_str_attr_doc = '''
Getter: Get value of ``{attr}`` attribute of this element, as string. If
`{attr}` is not defined, return empty string.
Setter: Set the value of
``{attr}`` attribute of this element.
Deleter: Remove ``{attr}`` attribute from this element.
'''
_bool_attr_doc = '''
Getter: Return True if this element has ``{attr}`` attribute. Otherwise return
False.
Setter: If True, add ``{attr}`` attribute to this element. Otherwise remove
``{attr}``.
Deleter: Remove ``{attr}`` attribute from this element.
'''
def _string_properties(attr: str) -> property:
def getter(self: Node) -> str:
return self.getAttribute(attr) or ''
def setter(self: Node, value: str) -> None:
self.setAttribute(attr, str(value))
def deleter(self: Node) -> None:
self.removeAttribute(attr)
return property(getter, setter, deleter, _str_attr_doc.format(attr=attr))
def _boolean_properties(attr: str) -> property:
def getter(self: Node) -> bool:
return bool(self.getAttribute(attr))
def setter(self: Node, value: bool) -> None:
if value:
self.setAttribute(attr, True)
else:
self.removeAttribute(attr)
def deleter(self: Node) -> None:
self.removeAttribute(attr)
return property(getter, setter, deleter, _bool_attr_doc.format(attr=attr))
class ElementMeta(type):
"""Metaclass for Element class."""
def __new__(cls: type, name: str, bases: Tuple[type],
namespace: Dict[str, Any], **kwargs: Any) -> type:
"""Add special properties to new class."""
for attr in namespace.get('_special_attr_string', []):
namespace[attr] = _string_properties(attr)
for attr in namespace.get('_special_attr_boolean', []):
namespace[attr] = _boolean_properties(attr)
new_cls = super().__new__(cls, name, bases, dict(namespace))
return new_cls
def getElementsBy(start_node: ParentNode,
cond: Callable[['Element'], bool]) -> NodeList:
"""Return list of child elements of start_node which matches ``cond``.
``cond`` must be a function which gets a single argument ``Element``,
and returns boolean. If the node matches requested condition, ``cond``
should return True.
This searches all child elements recursively.
:arg ParentNode start_node:
:arg cond: Callable[[Element], bool]
:rtype: NodeList[Element]
"""
elements = []
for child in start_node.children:
if cond(child):
elements.append(child)
elements.extend(child.getElementsBy(cond))
return NodeList(elements)
def getElementsByTagName(start_node: ParentNode, tag: str) -> NodeList:
"""Get child nodes which tag name is ``tag``."""
_tag = tag.upper()
return getElementsBy(start_node, lambda node: node.tagName == _tag)
def getElementsByClassName(start_node: ParentNode, class_name: str
) -> NodeList:
"""Get child nodes which has ``class_name`` class attribute."""
classes = set(class_name.split(' '))
return getElementsBy(
start_node,
lambda node: classes.issubset(set(node.classList))
)
[docs]class Element(Node, EventTarget, ParentNode, NonDocumentTypeChildNode,
ChildNode, metaclass=ElementMeta):
"""Element base class."""
nodeType = Node.ELEMENT_NODE
nodeValue = None
_parser_class = ElementParser # type: Type[ElementParser]
_element_buffer = WeakSet() # type: WeakSet[Node]
_elements_with_id = WeakValueDictionary() # type: MutableMapping
_should_escape_text = True
_special_attr_string = ['id']
_special_attr_boolean = [] # type: List[str]
getElementsBy = getElementsBy
getElementsByTagName = getElementsByTagName
getElementsByClassName = getElementsByClassName
def __init__(self, tag: str='', parent: Node = None,
_registered: bool = True, **kwargs: Any) -> None:
"""Initialize.
:arg str tag: HTML tag of this node.
:arg Node parent: Parent node of this node.
:arg bool _registered: Is registered to CustomElementRegistry.
:arg kwargs: key-value pair of attributes.
"""
super().__init__(parent=parent)
self._registered = _registered
self.tag = tag
self._element_buffer.add(self) # used to suport custom elements
self.attributes = NamedNodeMap(self)
self.classList = DOMTokenList(self)
if 'class_' in kwargs:
kwargs['class'] = kwargs.pop('class_')
if 'is_' in kwargs:
kwargs['is'] = kwargs.pop('is_')
for k, v in kwargs.items():
self.setAttribute(k, v)
def _clone_node(self) -> 'Element':
clone = type(self)(self.tag)
for attr in self.attributes:
clone.setAttribute(attr, self.getAttribute(attr))
# TODO: should clone event listeners???
return clone
def _get_attrs_by_string(self) -> str:
# attrs = ' '.join(attr.html for attr in self.attributes.values())
attrs = self.attributes.toString()
classes = self.getAttribute('class')
if classes:
attrs = ' '.join((attrs.strip(), 'class="{}"'.format(classes)))
return attrs.strip()
@property
def start_tag(self) -> str:
"""Return HTML start tag."""
tag = '<' + self.tag
attrs = self._get_attrs_by_string()
if attrs:
tag = ' '.join((tag, attrs))
return tag + '>'
@property
def end_tag(self) -> str:
"""Return HTML end tag."""
return '</{}>'.format(self.tag)
def _parse_html(self, html: str) -> DocumentFragment:
parser = self._parser_class()
parser.feed(html)
return parser.root
def _get_inner_html(self) -> str:
return ''.join(child.html for child in self.childNodes)
def _set_inner_html(self, html: str) -> None:
self._empty()
self._append_child(self._parse_html(html))
@property
def innerHTML(self) -> str:
"""Return HTML representation of child nodes."""
return self._get_inner_html()
@innerHTML.setter
def innerHTML(self, html: str) -> None:
"""Remove all child nodes and set ``html`` as new contents.
``html`` is parsed to ``Element`` nodes and set as child nodes.
"""
self._set_inner_html(html)
@property
def html(self) -> str:
"""Return HTML representation of this node."""
return self.start_tag + self.innerHTML + self.end_tag
[docs] def insertAdjacentHTML(self, position: str, html: str) -> None:
"""Parse ``html`` to DOM and insert to ``position``.
``position`` is a case-insensive string, and must be one of
"beforeBegin", "afterBegin", "beforeEnd", or "afterEnd".
"""
df = self._parse_html(html)
pos = position.lower()
if pos == 'beforebegin':
self.before(df)
elif pos == 'afterbegin':
self.prepend(df)
elif pos == 'beforeend':
self.append(df)
elif pos == 'afterend':
self.after(df)
else:
raise ValueError(
'The value provided ({}) is not one of "beforeBegin", '
'"afterBegin", "beforeEnd", or "afterEnd".'.format(position)
)
@property
def outerHTML(self) -> str:
"""Return html representation of this node.
Equivalent to ``self.html``.
"""
return self.html
@property
def nodeName(self) -> str: # type: ignore
"""Return tag name (capital case)."""
return self.tag.upper()
@property
def tagName(self) -> str:
"""Return tag name (capital case)."""
return self.tag.upper()
@property
def localName(self) -> str:
"""Return tag name (lower case)."""
return self.tag.lower()
@property
def className(self) -> str:
"""Get/Set class name as/by string."""
return self.getAttribute('class') or '' # type: ignore
@className.setter
def className(self, new_class: str) -> None:
if not isinstance(new_class, str):
raise TypeError('className must be str.')
self.setAttribute('class', new_class)
[docs] def getAttribute(self, attr: str) -> _AttrValueType:
"""Get attribute of this node as string format.
If this node does not have ``attr``, return None.
"""
if attr == 'class':
if self.classList:
return self.classList.toString()
return None
attr_node = self.getAttributeNode(attr)
if attr_node is None:
return None
return attr_node.value
[docs] def getAttributeNode(self, attr: str) -> Optional[Attr]:
"""Get attribute of this node as Attr format.
If this node does not have ``attr``, return None.
"""
return self.attributes.getNamedItem(attr)
[docs] def hasAttribute(self, attr: str) -> bool:
"""Return True if this node has ``attr``."""
if attr == 'class':
return bool(self.classList)
return attr in self.attributes
[docs] def hasAttributes(self) -> bool:
"""Return True if this node has any attributes."""
return bool(self.attributes) or bool(self.classList)
def _set_attribute_class(self, value: _AttrValueType) -> None:
if isinstance(value, str):
self.classList = DOMTokenList(self, value)
elif isinstance(value, Iterable):
self.classList = DOMTokenList(self, *value)
else:
raise TypeError(
'class attribute must be str, '
'but got {}'.format(type(value))
)
def _change_id(self, value: _AttrValueType) -> None:
if 'id' in self.attributes:
# remove old reference to self
self._elements_with_id.pop(self.id, None)
# register this elements with new id
if isinstance(value, (int, str)):
self._elements_with_id[value] = self
else:
raise TypeError(
'id attribute must be int or integer-string.'
)
def _set_attribute(self, attr: str, value: _AttrValueType) -> None:
if attr == 'class':
self._set_attribute_class(value)
else:
if attr == 'id':
self._change_id(value)
if not attr == 'draggable':
attr_cls = Attr
else:
attr_cls = DraggableAttr
new_attr_node = attr_cls(attr, value)
self.setAttributeNode(new_attr_node)
[docs] def setAttribute(self, attr: str, value: _AttrValueType) -> None:
"""Set ``attr`` and ``value`` in this node."""
self._set_attribute(attr, value)
[docs] def setAttributeNode(self, attr: Attr) -> None:
"""Set ``Attr`` node as this node's attribute."""
self.attributes.setNamedItem(attr)
def _remove_attribute(self, attr: str) -> None:
if attr == 'class':
self.classList = DOMTokenList(self)
else:
if attr == 'id':
self._elements_with_id.pop(self.id, None)
_attr = self.getAttributeNode(attr)
if _attr:
self.attributes.removeNamedItem(_attr)
[docs] def removeAttribute(self, attr: str) -> None:
"""Remove ``attr`` from this node."""
self._remove_attribute(attr)
[docs] def removeAttributeNode(self, attr: Attr) -> Optional[Attr]:
"""Remove ``Attr`` node from this node."""
return self.attributes.removeNamedItem(attr)
[docs]class HTMLElement(Element):
"""Base class for HTMLElement.
This class extends `Element` class with some HTML specific features.
"""
_special_attr_string = ['title', 'type']
_special_attr_boolean = ['hidden']
_parser_class = HTMLElementParser # type: Type[ElementParser]
def __init__(self, *args: Any, style: str=None, **kwargs: Any
) -> None: # noqa: D102
super().__init__(*args, **kwargs)
self.__style = CSSStyleDeclaration(style, owner=self)
def _get_attrs_by_string(self) -> str:
attrs = super()._get_attrs_by_string()
style = self.getAttribute('style')
if style:
attrs += ' style="{}"'.format(style)
return attrs.strip()
def _clone_node(self) -> 'HTMLElement':
clone = super()._clone_node()
clone.style.update(self.style)
return clone
@property
def end_tag(self) -> str:
"""Retrun html end tag.
If tag is empty tag like <img> or <br>, return empty string.
"""
if self.tag in HTML_EMPTY:
return ''
return super().end_tag
@property
def style(self) -> CSSStyleDeclaration:
"""Return style attribute of this node."""
return self.__style
@style.setter
def style(self, style: _AttrValueType) -> None:
"""Set style attribute of this node.
If argument ``style`` is string, it will be parsed to
``CSSStyleDeclaration``.
"""
if isinstance(style, str):
self.__style._parse_str(style)
elif style is None:
self.__style._parse_str('')
elif isinstance(style, CSSStyleDeclaration):
self.__style._owner = None
if style._owner is not None:
new_style = CSSStyleDeclaration(owner=self)
new_style.update(style)
self.__style = new_style
else:
# always making new decl may be better
style._owner = self
self.__style = style
else:
raise TypeError('Invalid type for style: {}'.format(type(style)))
@property
def draggable(self) -> Union[bool, str]:
"""Get ``draggable`` property."""
if not self.hasAttribute('draggable'):
return False
return self.getAttribute('draggable') # type: ignore
@draggable.setter
def draggable(self, value: Union[bool, str]) -> None:
"""Set ``draggable`` property.
``value`` is boolean or string.
"""
if value is False:
self.removeAttribute('draggable')
else:
self.setAttribute('draggable', value)
def getAttribute(self, attr: str) -> _AttrValueType: # noqa: D102
if attr == 'style':
# if style is neither None nor empty, return None
# otherwise, return style.cssText
if self.style:
return self.style.cssText
return None
return super().getAttribute(attr)
def _set_attribute(self, attr: str, value: _AttrValueType) -> None:
if attr == 'style':
self.style = value # type: ignore
else:
super()._set_attribute(attr, value) # type: ignore
def _remove_attribute(self, attr: str) -> None:
if attr == 'style':
self.style = None # type: ignore
else:
super()._remove_attribute(attr) # type: ignore
class FormControlMixin(AbstractNode):
"""Mixin class for FormControl classes."""
def __init__(self, *args: Any, form: Union[str, 'HTMLFormElement'] = None,
**kwargs: Any) -> None:
"""``form`` is a ``HTMLFormElement`` object or id of it."""
super().__init__(*args, **kwargs) # type: ignore
self.__form = None
from wdom.document import getElementById
if isinstance(form, str):
form = getElementById(form)
if isinstance(form, HTMLFormElement):
self.__form = form
elif form is not None:
raise TypeError(
'"form" attribute must be an HTMLFormElement or id of'
'HTMLFormElement in the same document.'
)
@property
def form(self) -> Optional['HTMLFormElement']:
"""Get ``HTMLFormElement`` object related to this node."""
if self.__form:
return self.__form
parent = self.parentNode
while parent:
if isinstance(parent, HTMLFormElement):
return parent
else:
parent = parent.parentNode
return None
[docs]class HTMLAnchorElement(HTMLElement): # noqa: D204
"""HTMLAnchorElement class (<a></a> tag)."""
_special_attr_string = ['href', 'name', 'rel', 'src', 'target']
[docs]class HTMLIFrameElement(HTMLElement): # noqa: D204
"""HTMLIFrameElement class (<iframe></iframe> tag)."""
_special_attr_string = ['height', 'name', 'src', 'target', 'width']
[docs]class HTMLLabelElement(HTMLElement, FormControlMixin):
"""HTMLLabelElement class (<label></label> tag)."""
@property
def htmlFor(self) -> _AttrValueType:
"""Retrun ``for`` attribute value."""
return self.getAttribute('for')
@htmlFor.setter
def htmlFor(self, value: str) -> None:
self.setAttribute('for', value)
@property
def control(self) -> Optional[HTMLElement]:
"""Return related HTMLElement object."""
id = self.getAttribute('for')
if id:
if self.ownerDocument:
return self.ownerDocument.getElementById(id)
elif isinstance(id, str):
from wdom.document import getElementById
return getElementById(id)
else:
raise TypeError('"for" attribute must be string')
return None
[docs]class HTMLOptGroupElement(HTMLElement, FormControlMixin): # noqa: D204
"""HTMLOptionElement class (<optgroup></optgroup> tag)."""
_special_attr_string = ['label']
_special_attr_boolean = ['disabled']
[docs]class HTMLOptionElement(HTMLElement, FormControlMixin): # noqa: D204
"""HTMLOptionElement class (<option></option> tag)."""
_special_attr_string = ['label', 'value']
_special_attr_boolean = ['defaultSelected', 'disabled', 'selected']
[docs]class HTMLScriptElement(HTMLElement): # noqa: D204
"""HTMLScriptElement class (<script></script> tag).
In this tag, all inner contents are not escaped.
"""
_special_attr_string = ['charset', 'src']
_special_attr_boolean = ['async', 'defer']
_should_escape_text = False
[docs]class HTMLSelectElement(HTMLElement, FormControlMixin):
"""HTMLSelectElement class (<select></select> tag)."""
_special_attr_string = ['name', 'size', 'value']
_special_attr_boolean = ['disabled', 'multiple', 'required']
def __init__(self, *args: Any, **kwargs: Any) -> None: # noqa: D102
self._selected_options = [] # type: List[str]
super().__init__(*args, **kwargs)
[docs] def on_event_pre(self, e: Event) -> None:
"""Set values set on browser before calling event listeners."""
super().on_event_pre(e)
ct_msg = e.init.get('currentTarget', dict())
if e.type in ('input', 'change'):
self._set_attribute('value', ct_msg.get('value'))
_selected = ct_msg.get('selectedOptions', [])
self._selected_options.clear()
for opt in self.options:
if opt.wdom_id in _selected:
self._selected_options.append(opt)
opt._set_attribute('selected', True)
else:
opt._remove_attribute('selected')
@property
def length(self) -> int:
"""Return number of options in this node."""
return len(self.options)
@property
def options(self) -> NodeList:
"""Return all option nodes in this node."""
return self.getElementsByTagName('option')
@property
def selectedOptions(self) -> NodeList:
"""Return all selected option nodes."""
return NodeList(list(opt for opt in self.options if opt.selected))
[docs]class HTMLStyleElement(HTMLElement): # noqa: D204
"""HTMLStyleElement class (<style></style> tag).
In this tag, all inner contents are not escaped.
"""
_special_attr_boolean = ['disabled', 'scoped']
_should_escape_text = False
[docs]class HTMLTextAreaElement(HTMLElement, FormControlMixin): # noqa: D204
"""HTMLTextAreaElement class (<textarea></textarea> tag)."""
_special_attr_string = ['height', 'name', 'src', 'value', 'width']
_special_attr_boolean = ['disabled']
defaultValue = HTMLElement.textContent
[docs] def on_event_pre(self, e: Event) -> None:
"""Set values set on browser before calling event listeners."""
super().on_event_pre(e)
ct_msg = e.init.get('currentTarget', dict())
if e.type in ('input', 'change'):
# Update user inputs
self._set_text_content(ct_msg.get('value') or '')