webwidgets 1.0.0__py3-none-any.whl → 1.1.1__py3-none-any.whl

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.
@@ -63,6 +63,33 @@ class HTMLNode(ReprMixin):
63
63
  f'{k}="{v}"' for k, v in sorted(self.attributes.items())
64
64
  )
65
65
 
66
+ @property
67
+ def end_tag(self) -> str:
68
+ """Returns the closing tag of the HTML node.
69
+
70
+ :return: A string containing the closing tag of the element.
71
+ :rtype: str
72
+ """
73
+ return f"</{self._get_tag_name()}>"
74
+
75
+ @property
76
+ def start_tag(self) -> str:
77
+ """Returns the opening tag of the HTML node, including any attributes.
78
+
79
+ Attributes are validated with :py:meth:`HTMLNode.validate_attributes`
80
+ before rendering.
81
+
82
+ :return: A string containing the opening tag of the element with its attributes.
83
+ :rtype: str
84
+ """
85
+ # Rendering attributes
86
+ self.validate_attributes()
87
+ attributes = self._render_attributes()
88
+ maybe_space = ' ' if attributes else ''
89
+
90
+ # Building start tag
91
+ return f"<{self._get_tag_name()}{maybe_space}{attributes}>"
92
+
66
93
  def add(self, child: 'HTMLNode') -> None:
67
94
  """Adds a child to the HTML node.
68
95
 
@@ -101,33 +128,6 @@ class HTMLNode(ReprMixin):
101
128
  styles.update(child.get_styles())
102
129
  return styles
103
130
 
104
- @property
105
- def start_tag(self) -> str:
106
- """Returns the opening tag of the HTML node, including any attributes.
107
-
108
- Attributes are validated with :py:meth:`HTMLNode.validate_attributes`
109
- before rendering.
110
-
111
- :return: A string containing the opening tag of the element with its attributes.
112
- :rtype: str
113
- """
114
- # Rendering attributes
115
- self.validate_attributes()
116
- attributes = self._render_attributes()
117
- maybe_space = ' ' if attributes else ''
118
-
119
- # Building start tag
120
- return f"<{self._get_tag_name()}{maybe_space}{attributes}>"
121
-
122
- @property
123
- def end_tag(self) -> str:
124
- """Returns the closing tag of the HTML node.
125
-
126
- :return: A string containing the closing tag of the element.
127
- :rtype: str
128
- """
129
- return f"</{self._get_tag_name()}>"
130
-
131
131
  def to_html(self, collapse_empty: bool = True,
132
132
  indent_size: int = 4, indent_level: int = 0,
133
133
  force_one_line: bool = False, return_lines: bool = False,
@@ -45,6 +45,11 @@ class Body(HTMLNode):
45
45
  pass
46
46
 
47
47
 
48
+ class Div(HTMLNode):
49
+ """A `<div>` element used for grouping elements."""
50
+ pass
51
+
52
+
48
53
  @one_line
49
54
  @no_end_tag
50
55
  class Doctype(HTMLNode):
@@ -10,7 +10,9 @@
10
10
  #
11
11
  # =======================================================================
12
12
 
13
+ from .enums import *
13
14
  from .indentation import *
14
15
  from .representation import *
15
16
  from .sanitizing import *
17
+ from .sizes import *
16
18
  from .validation import *
@@ -0,0 +1,18 @@
1
+ # =======================================================================
2
+ #
3
+ # This file is part of WebWidgets, a Python package for designing web
4
+ # UIs.
5
+ #
6
+ # You should have received a copy of the MIT License along with
7
+ # WebWidgets. If not, see <https://opensource.org/license/mit>.
8
+ #
9
+ # Copyright(C) 2025, mlaasri
10
+ #
11
+ # =======================================================================
12
+
13
+ from enum import auto, Enum
14
+
15
+
16
+ class Direction(Enum):
17
+ HORIZONTAL = auto()
18
+ VERTICAL = auto()
@@ -0,0 +1,14 @@
1
+ # =======================================================================
2
+ #
3
+ # This file is part of WebWidgets, a Python package for designing web
4
+ # UIs.
5
+ #
6
+ # You should have received a copy of the MIT License along with
7
+ # WebWidgets. If not, see <https://opensource.org/license/mit>.
8
+ #
9
+ # Copyright(C) 2025, mlaasri
10
+ #
11
+ # =======================================================================
12
+
13
+ from .size import *
14
+ from .sizes import *
@@ -0,0 +1,92 @@
1
+ # =======================================================================
2
+ #
3
+ # This file is part of WebWidgets, a Python package for designing web
4
+ # UIs.
5
+ #
6
+ # You should have received a copy of the MIT License along with
7
+ # WebWidgets. If not, see <https://opensource.org/license/mit>.
8
+ #
9
+ # Copyright(C) 2025, mlaasri
10
+ #
11
+ # =======================================================================
12
+
13
+ from webwidgets.utility.representation import ReprMixin
14
+ from typing import Callable, Type, Union
15
+
16
+
17
+ class Size(ReprMixin):
18
+ """Base class representing a length.
19
+
20
+ Sizes are specified by a numerical value and a CSS unit (e.g. `px`, `%`,
21
+ etc.). The value is provided upon creation and the unit is derived from the
22
+ class name of the :py:class:`Size` object.
23
+ """
24
+
25
+ def __init__(self, value: Union[float, int]):
26
+ """Creates a new :py:class:`Size` object with the given numerical
27
+ value.
28
+
29
+ :param value: The numerical value of the size.
30
+ :type value: Union[float, int]
31
+ """
32
+ self.value = value
33
+
34
+ @property
35
+ def unit(self) -> str:
36
+ """Returns the unit of the size's numerical value.
37
+
38
+ The unit of a size object is the name of its class in lowercase.
39
+
40
+ :return: The unit of the size.
41
+ :rtype: str
42
+ """
43
+ return self.__class__.__name__.lower()
44
+
45
+ def to_css(self) -> str:
46
+ """Compiles and returns the CSS representation of the :py:class:`Size`
47
+ object.
48
+
49
+ The CSS representation is obtained by concatenating the value of the
50
+ size with its unit (e.g. `"10px"`).
51
+
52
+ :return: The CSS representation of the size.
53
+ :rtype: str
54
+ """
55
+ return str(self.value) + self.unit
56
+
57
+
58
+ class AbsoluteSize(Size):
59
+ """A size whose unit is an absolute unit that does not depend on any
60
+ context. Examples include pixels (`"px"`) and centimeters (`"cm"`).
61
+ """
62
+ pass
63
+
64
+
65
+ class RelativeSize(Size):
66
+ """A size whose unit is relative to the size of other elements, such as the
67
+ size of a display or a font. Examples include percentages (`"%"`) and ems
68
+ (`"em"`).
69
+ """
70
+ pass
71
+
72
+
73
+ def with_unit(unit: str) -> Callable[[Type[Size]], Type[Size]]:
74
+ """Returns a decorator to override the unit of a Size subclass with the
75
+ given unit.
76
+
77
+ :param unit: The unit to be used for the Size subclass.
78
+ :type unit: str
79
+ :return: A decorator that overrides the unit of a Size subclass with the
80
+ given unit.
81
+ :rtype: Callable[[Type[Size]], Type[Size]]
82
+ """
83
+ def _decorator(cls):
84
+ """Decorator to override the unit of a Size subclass.
85
+
86
+ :param cls: A subclass of Size whose unit should be overridden.
87
+ :return: The given class with a new unit.
88
+ """
89
+ cls.unit = property(
90
+ lambda _: unit, doc=f"Always returns '{unit}'.")
91
+ return cls
92
+ return _decorator
@@ -0,0 +1,31 @@
1
+ # =======================================================================
2
+ #
3
+ # This file is part of WebWidgets, a Python package for designing web
4
+ # UIs.
5
+ #
6
+ # You should have received a copy of the MIT License along with
7
+ # WebWidgets. If not, see <https://opensource.org/license/mit>.
8
+ #
9
+ # Copyright(C) 2025, mlaasri
10
+ #
11
+ # =======================================================================
12
+
13
+ from .size import AbsoluteSize, RelativeSize, with_unit
14
+
15
+
16
+ # Only exposing concrete size classes when using a wildcard import
17
+ __all__ = [
18
+ "Percent",
19
+ "Px"
20
+ ]
21
+
22
+
23
+ @with_unit("%")
24
+ class Percent(RelativeSize):
25
+ """A size expressed in percentage (`"%"`)."""
26
+ pass
27
+
28
+
29
+ class Px(AbsoluteSize):
30
+ """A size expressed in pixels (`"px"`)."""
31
+ pass
@@ -10,10 +10,37 @@
10
10
  #
11
11
  # =======================================================================
12
12
 
13
- from typing import *
14
13
  import re
15
14
 
16
15
 
16
+ # CSS selectors that are considered valid as selectors but not as identifiers
17
+ # according to the `validate_css_identifier()` function.
18
+ SPECIAL_SELECTORS = [
19
+ "*", "*::before", "*::after"
20
+ ]
21
+
22
+
23
+ def validate_css_comment(comment: str) -> None:
24
+ """Checks if the given comment is a valid CSS comment according to the CSS
25
+ syntax rules and raises an exception if not.
26
+
27
+ This function just checks that the comment does not contain any closing
28
+ sequence `*/` as defined in the CSS Syntax Module Level 3, paragraph 4.3.2
29
+ (see source: https://www.w3.org/TR/css-syntax-3/#consume-comment).
30
+
31
+ :param comment: The CSS comment to validate, without its opening and
32
+ closing sequences. It can include any number of opening sequences
33
+ (`/*`) as part of its content, in which case it is still a valid
34
+ comment per the CSS specification, but it cannot contain any closing
35
+ sequences (`*/`).
36
+ :type comment: str
37
+ :raises ValueError: If the comment is not a valid CSS comment.
38
+ """
39
+ if "*/" in comment:
40
+ raise ValueError(
41
+ f"Invalid CSS comment: '{comment}' contains closing sequence '*/'")
42
+
43
+
17
44
  def validate_css_identifier(identifier: str) -> None:
18
45
  """Checks if the given identifier is a valid identifier token according to
19
46
  the CSS syntax rules and raises an exception if not.
@@ -57,6 +84,77 @@ def validate_css_identifier(identifier: str) -> None:
57
84
  "allowed.")
58
85
 
59
86
 
87
+ def validate_css_selector(selector: str) -> None:
88
+ """Checks if the given CSS selector is valid and raises an exception if
89
+ not.
90
+
91
+ To be valid, the selector must either be:
92
+ - a special selector, which is defined as either `*`, `*::before`, or
93
+ `*::after`
94
+ - any combination of special selectors separated by a comma and a single
95
+ space (e.g. `*::before, *::after`)
96
+ - or a class selector, which is defined as a dot `.` followed by a valid
97
+ CSS identifier, as defined and enforced by the
98
+ :py:func:`validate_css_identifier` function
99
+
100
+ Note that this function imposes stricter rules than the official CSS
101
+ Selector Level 4 specification (see source:
102
+ https://www.w3.org/TR/selectors-4/). For example, this function does not
103
+ allow selectors with the relational pseudo-class `:has()` whereas the
104
+ specification does.
105
+
106
+ :param selector: The CSS selector to validate.
107
+ :type selector: str
108
+ :raises ValueError: If the selector is not a special selector nor a valid
109
+ CSS identifier.
110
+ """
111
+ # Checking if the selector is a special selector
112
+ if selector in SPECIAL_SELECTORS:
113
+ return
114
+
115
+ # Checking if the selector is a combination of special selectors
116
+ if all(part in SPECIAL_SELECTORS for part in selector.split(", ")):
117
+ return
118
+
119
+ # Otherwise, checking if the selector is a class selector
120
+ if not selector.startswith("."):
121
+ raise ValueError("Class selector must start with '.' but got: "
122
+ f"{selector}")
123
+ validate_css_identifier(selector[1:])
124
+
125
+
126
+ def validate_css_value(value: str) -> None:
127
+ """Checks if the given value is a valid CSS property value and raises an
128
+ exception if not.
129
+
130
+ To be valid, the value must only contain:
131
+ - letters (`a-z`, `A-Z`)
132
+ - digits (`0-9`)
133
+ - dots (`.`)
134
+ - spaces (` `)
135
+ - hyphens (`-`)
136
+ - percent characters (`%`)
137
+ - hashtags (`#`)
138
+
139
+ Note that this function imposes stricter rules than the official CSS
140
+ specification - more precisely, than chapter 2 of the CSS Values and Units
141
+ Module Level 3 (see source:
142
+ https://www.w3.org/TR/css-values-3/#value-defs). For example, this function
143
+ does not allow functional notations like `calc()` whereas the specification
144
+ does.
145
+
146
+ :param value: The value to validate as a CSS property value.
147
+ :type value: str
148
+ :raises ValueError: If the value is not a valid CSS property value.
149
+ """
150
+ if not re.match(r'^[a-zA-Z0-9. \-%#]+$', value):
151
+ invalid_chars = re.findall(r'[^a-zA-Z0-9. \-%#]', value)
152
+ raise ValueError("Invalid character(s) in CSS property value "
153
+ f"'{value}': {', '.join(invalid_chars)}\n"
154
+ "Only letters, digits, dots, spaces, hyphens, "
155
+ "percent characters, and hashtags are allowed.")
156
+
157
+
60
158
  def validate_html_class(class_attribute: str) -> None:
61
159
  """Checks if the given HTML class attribute is valid and raises an
62
160
  exception if not.
@@ -65,7 +163,7 @@ def validate_html_class(class_attribute: str) -> None:
65
163
  - the class attribute cannot start nor end with a space
66
164
  - the class attribute cannot contain double spaces
67
165
  - each class in the attribute must be a valid CSS identifier, as validated
68
- by the :py:func:`validate_css_identifier` function.
166
+ by the :py:func:`validate_css_identifier` function
69
167
 
70
168
  Note that this function imposes stricter rules than rule 2.3.7 of the HTML5
71
169
  specification (see source:
@@ -12,7 +12,7 @@
12
12
 
13
13
  from .compiled_website import CompiledWebsite
14
14
  from typing import Any, Callable, List
15
- from webwidgets.compilation.css import compile_css, CSSRule, apply_css
15
+ from webwidgets.compilation.css import apply_css, compile_css, ClassRule
16
16
  from webwidgets.utility.representation import ReprMixin
17
17
  from webwidgets.widgets.containers.page import Page
18
18
 
@@ -44,7 +44,7 @@ class Website(ReprMixin):
44
44
  force_one_line: bool = False,
45
45
  indent_level: int = 0,
46
46
  indent_size: int = 4,
47
- rule_namer: Callable[[List[CSSRule], int], str] = None,
47
+ class_namer: Callable[[List[ClassRule], int], str] = None,
48
48
  **kwargs: Any) -> CompiledWebsite:
49
49
  """Compiles the website into HTML and CSS code.
50
50
 
@@ -59,8 +59,8 @@ class Website(ReprMixin):
59
59
  :param indent_size: See :py:meth:`HTMLNode.to_html` and
60
60
  :py:meth:`CompiledCSS.to_css`.
61
61
  :type indent_size: int
62
- :param rule_namer: See :py:func:`compile_css`.
63
- :type rule_namer: Callable[[List[CSSRule], int], str]
62
+ :param class_namer: See :py:func:`compile_css`.
63
+ :type class_namer: Callable[[List[ClassRule], int], str]
64
64
  :param kwargs: See :py:meth:`HTMLNode.to_html`.
65
65
  :type kwargs: Any
66
66
  :return: A new :py:class:`CompiledWebsite` object containing the
@@ -72,7 +72,7 @@ class Website(ReprMixin):
72
72
  for page in self.pages]
73
73
 
74
74
  # Compiling HTML and CSS code
75
- compiled_css = compile_css(trees, rule_namer)
75
+ compiled_css = compile_css(trees, class_namer)
76
76
  for tree in trees:
77
77
  apply_css(compiled_css, tree)
78
78
  html_content = [tree.to_html(
@@ -10,5 +10,6 @@
10
10
  #
11
11
  # =======================================================================
12
12
 
13
+ from .box import Box
13
14
  from .container import Container
14
15
  from .page import Page
@@ -0,0 +1,138 @@
1
+ # =======================================================================
2
+ #
3
+ # This file is part of WebWidgets, a Python package for designing web
4
+ # UIs.
5
+ #
6
+ # You should have received a copy of the MIT License along with
7
+ # WebWidgets. If not, see <https://opensource.org/license/mit>.
8
+ #
9
+ # Copyright(C) 2025, mlaasri
10
+ #
11
+ # =======================================================================
12
+
13
+ from .container import Container
14
+ from dataclasses import dataclass
15
+ from typing import Dict, Union
16
+ from webwidgets.compilation.html.html_tags import Div
17
+ from webwidgets.utility.enums import Direction
18
+ from webwidgets.utility.sizes.sizes import AbsoluteSize
19
+ from webwidgets.widgets.widget import Widget
20
+
21
+
22
+ class Box(Container):
23
+ """A widget that lays out its child widgets inside a row or a column.
24
+ """
25
+
26
+ def __init__(self, direction: Direction):
27
+ """Creates a new Box with the given direction.
28
+
29
+ :param direction: The direction in which the child widgets should be
30
+ laid out. Can be either `Direction.HORIZONTAL` or
31
+ `Direction.VERTICAL`.
32
+ :type direction: Direction
33
+ """
34
+ super().__init__()
35
+ self.direction = direction
36
+ self._properties: Dict[int, BoxItemProperties] = {}
37
+
38
+ def add(self, widget: Widget,
39
+ space: Union[int, float, AbsoluteSize] = 1) -> None:
40
+ """Adds a widget to the box with an optional space coefficient.
41
+
42
+ This function overrides :py:meth:`Container.add` from the base class to
43
+ extend its functionality and save additional properties on each widget
44
+ added to the box.
45
+
46
+ :param widget: The widget to add to the box.
47
+ :type widget: Widget
48
+ :param space: The amount of space to allocate for the widget to live
49
+ in.
50
+
51
+ If a numeric value (int or float), it must be at least 1, and it is
52
+ construed as the weight to give to the widget during space
53
+ allocation within the entire box. For example, if widget A has a
54
+ space of 1 and widget B has a space of 2, B will be allocated twice
55
+ as much space as A, i.e. a total of 2/3 of the entire box if the
56
+ only widgets the box contains are A and B.
57
+
58
+ If an instance of :py:class:`AbsoluteSize`, it is construed as the
59
+ exact size to allocate for the widget. For example, if widget A has
60
+ a space of `Px(100)` (i.e. 100px) and widget B has a space of 1, A
61
+ will be allocated exactly 100px while B will be allocated all the
62
+ remaining space if the only widgets the box contains are A and B.
63
+
64
+ Note that this value controls the amount of free space available
65
+ for the widget to grow in, not the size of the widget itself.
66
+ :type space: Union[int, float, AbsoluteSize]
67
+ """
68
+ super().add(widget=widget)
69
+ self._properties[id(widget)] = BoxItemProperties(space=space)
70
+
71
+ def build(self) -> Div:
72
+ """Builds the HTML representation of the Box.
73
+
74
+ The box is constructed as a `<div>` element with a flexbox layout. Its
75
+ `flex-direction` property is set to either "row" or "column" based on
76
+ the direction parameter, and it has a `data-role` attribute of "box".
77
+
78
+ Each child widget is wrapped inside its own `<div>` element with a
79
+ `data-role` attribute of "box-item". The items are centered within
80
+ their own `<div>`.
81
+
82
+ :return: A :py:class:`Div` element representing the Box.
83
+ :rtype: Div
84
+ """
85
+ # Building child nodes and retrieving their properties
86
+ nodes = [w.build() for w in self.widgets]
87
+ properties = [self._properties[id(w)] for w in self.widgets]
88
+
89
+ # Building box items that wrap around child nodes
90
+ items = [Div(
91
+ children=[node],
92
+ attributes={"data-role": "box-item"},
93
+ style={
94
+ "display": "flex",
95
+ "flex-direction": "row",
96
+ "align-items": "center",
97
+ "justify-content": "center"
98
+ } | props.to_style()) for node, props in zip(nodes, properties)]
99
+
100
+ # Assembling the box
101
+ flex_dir = "row" if self.direction == Direction.HORIZONTAL else "column"
102
+ box = Div(children=items, attributes={"data-role": "box"}, style={
103
+ "display": "flex",
104
+ "flex-direction": flex_dir
105
+ })
106
+ return box
107
+
108
+
109
+ @dataclass
110
+ class BoxItemProperties:
111
+ """A utility dataclass to store extra properties to apply to a widget
112
+ contained in a :py:class:`Box` during compilation.
113
+ """
114
+
115
+ space: Union[int, float, AbsoluteSize]
116
+
117
+ def to_style(self) -> Dict[str, str]:
118
+ """Converts the properties of the :py:class:`BoxItemProperties`
119
+ instance into a dictionary of CSS properties that can be added to the
120
+ style of an HTML node.
121
+
122
+ :return: A dictionary of CSS properties.
123
+ :rtype: Dict[str, str]
124
+ """
125
+ # If a numeric value, the space serves as a relative weight
126
+ if isinstance(self.space, (int, float)):
127
+ return {
128
+ "flex-basis": "0",
129
+ "flex-grow": str(self.space),
130
+ "flex-shrink": str(self.space)
131
+ }
132
+
133
+ # If an AbsoluteSize object, the space is a fixed size
134
+ return {
135
+ "flex-basis": self.space.to_css(),
136
+ "flex-grow": "0",
137
+ "flex-shrink": "0"
138
+ }
@@ -27,7 +27,12 @@ class Container(Widget):
27
27
  :type widgets: List[Widget]
28
28
  """
29
29
  super().__init__()
30
- self.widgets = [] if widgets is None else widgets
30
+ self._widgets = [] if widgets is None else widgets
31
+
32
+ @property
33
+ def widgets(self) -> List[Widget]:
34
+ """Returns the list of widgets contained within the container."""
35
+ return self._widgets
31
36
 
32
37
  def add(self, widget: Widget) -> None:
33
38
  """Adds a widget to the container.
@@ -35,4 +40,4 @@ class Container(Widget):
35
40
  :param widget: The widget to add to the container.
36
41
  :type widget: Widget
37
42
  """
38
- self.widgets.append(widget)
43
+ self._widgets.append(widget)
@@ -32,7 +32,7 @@ class Page(Container):
32
32
  :param css_file_name: The name of the CSS file to link to the page if
33
33
  the page elements contain any styles. Defaults to "styles.css".
34
34
  :type css_file_name: str
35
- :return: An :py:class:`RootNode` object representing the page.
35
+ :return: A :py:class:`RootNode` object representing the page.
36
36
  :rtype: RootNode
37
37
  """
38
38
  # Building nodes from the page's widgets
@@ -16,8 +16,7 @@ from webwidgets.utility.representation import ReprMixin
16
16
 
17
17
 
18
18
  class Widget(ABC, ReprMixin):
19
- """
20
- Abstract base class for all widgets.
19
+ """Abstract base class for all widgets.
21
20
 
22
21
  All subclasses of :py:class:`Widget` must implement a :py:meth:`build`
23
22
  method that returns an :py:class:`HTMLNode` object.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: webwidgets
3
- Version: 1.0.0
3
+ Version: 1.1.1
4
4
  Summary: A Python package for designing web UIs.
5
5
  Project-URL: Source code, https://github.com/mlaasri/WebWidgets
6
6
  Author: mlaasri
@@ -9,6 +9,11 @@ Keywords: design,webui
9
9
  Classifier: Operating System :: OS Independent
10
10
  Classifier: Programming Language :: Python :: 3
11
11
  Requires-Python: >=3.9
12
+ Provides-Extra: dev
13
+ Requires-Dist: numpy; extra == 'dev'
14
+ Requires-Dist: pillow; extra == 'dev'
15
+ Requires-Dist: pytest; extra == 'dev'
16
+ Requires-Dist: selenium; extra == 'dev'
12
17
  Description-Content-Type: text/markdown
13
18
 
14
19
  # WebWidgets
@@ -0,0 +1,34 @@
1
+ webwidgets/__init__.py,sha256=fkkQsGyTLNHc3pL-njm0KmAZRqDfvmwDhun0ZT7N92w,613
2
+ webwidgets/compilation/__init__.py,sha256=hb61nhmPTghIzuA_hun98xT5Ngv7QFAgMHD44g-9uOo,433
3
+ webwidgets/compilation/css/__init__.py,sha256=A4VUGyy92Vn32Km2VuJL5bBmLdovN6PDfv_DctRdzJk,534
4
+ webwidgets/compilation/css/css.py,sha256=v2GSyegop8JmE_9ITZ-2oc9P_8bE33-YWsjiO8FGaWI,9351
5
+ webwidgets/compilation/css/css_rule.py,sha256=8NKQwNfETc52KDrMoy_rJtie6si1SrdfnxKRBRjLJqM,3594
6
+ webwidgets/compilation/css/sections/__init__.py,sha256=qPLq_w0kIPGvIIXxsblF7iUdm9AOA-CUl06krTMnHHI,465
7
+ webwidgets/compilation/css/sections/css_section.py,sha256=LfQojXSYbsh9UcbqD4vn6heEO7qjGNrwy7Py5bsvy9U,4020
8
+ webwidgets/compilation/css/sections/preamble.py,sha256=zDU3JZpIYoc9Xynquc50awqhvYxNi0EbprbwCVScvoY,1355
9
+ webwidgets/compilation/css/sections/rule_section.py,sha256=BBeIJyVRz2ZeBxEC6VrEymXF6LjzE_GPu6X04A-lGF8,1456
10
+ webwidgets/compilation/html/__init__.py,sha256=h8eWh8BbjLZA1wIGAeOxyhZIUM1e36ZgMTwL5SQajHc,514
11
+ webwidgets/compilation/html/html_node.py,sha256=QJgUWpAsvJSd4C9FUAvtcCZWlCLA0fZK5A0c8_pbsWc,11697
12
+ webwidgets/compilation/html/html_tags.py,sha256=R13axcPAOnqRVPM8FP0k04QIW7gZMSJt3jlsv7Q-fXg,2276
13
+ webwidgets/utility/__init__.py,sha256=GDS2bOS36He7EAde1YAcSQ_P49zkKIPGIRP8NRCFJbs,547
14
+ webwidgets/utility/enums.py,sha256=RDgYT2S85DrhKL-3T7oHN20iW4vYoD8TKLsEmsO2OaE,495
15
+ webwidgets/utility/indentation.py,sha256=BaOQRqWdG7T5k_g1-ia9jewPFZjD3afjZH_Fc4NSVwo,906
16
+ webwidgets/utility/representation.py,sha256=lQ15v_DZOHBQKLM8pzRE1tuJkU_modhPTpWpSJ2lBCE,1061
17
+ webwidgets/utility/sanitizing.py,sha256=OKJRDqk-OXYCWeK6ie3GdfQvb49wTs93kd971mg5oK0,5770
18
+ webwidgets/utility/validation.py,sha256=ZKKEkhkdhuC7JxVG1_BKFBLT80DOGaSCOCo6ZJgzwmU,8239
19
+ webwidgets/utility/sizes/__init__.py,sha256=2tmSdXRBlIWuOfZUmToc0_gFiVKJIFUCh6H9B2Bf9rw,437
20
+ webwidgets/utility/sizes/size.py,sha256=UjffvXNNV4BhgyJ_OV_ZArYas7FP4Bhul_ujktsQbQM,2867
21
+ webwidgets/utility/sizes/sizes.py,sha256=DoKIruTw2RjuvH9MmM-CG5C2_WnmLt0mlAWphPstM6E,747
22
+ webwidgets/website/__init__.py,sha256=zp4N3CtY0SLNfDV9p2Y0tqbta-vFOX1PSJr7eQ9rQdk,471
23
+ webwidgets/website/compiled_website.py,sha256=lR_sabYtdWiRWicyxEFs4yxRUB_TbMowpsNz3CtqQBQ,1129
24
+ webwidgets/website/website.py,sha256=2vdIxYLXOu_otiF-KTOBigoic0aUWwgglfXAAZRzURg,3349
25
+ webwidgets/widgets/__init__.py,sha256=J2br7F-16URKvWshkJcc4nth27YQsaLrdVZu0xXx5CU,449
26
+ webwidgets/widgets/widget.py,sha256=NRyiy_vBStX2r-LBeJpqFy-AMw1aaqxrx4sz-GIdJnE,1034
27
+ webwidgets/widgets/containers/__init__.py,sha256=0y63aqzzJzC77oKOK1rcqQGDk710lkalc6vyZr7iJWo,473
28
+ webwidgets/widgets/containers/box.py,sha256=iwr-n09ePMICQLF4MZeAzRjBuPCdqHF2cFF8OLSH_oA,5435
29
+ webwidgets/widgets/containers/container.py,sha256=-UAnDEC0o-rP2PBF1ji8xUGRQYRnDvKmVSDaQH3_DYE,1317
30
+ webwidgets/widgets/containers/page.py,sha256=JQBV9U803nfiC20_4kJ4RkibqVeMKf30FZOGmii6YV4,2179
31
+ webwidgets-1.1.1.dist-info/METADATA,sha256=UBIZPHX_nOWMcxrnmWEgOcDJdzTuVK90DuX43JMhMi4,1607
32
+ webwidgets-1.1.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
33
+ webwidgets-1.1.1.dist-info/licenses/LICENSE,sha256=LISw1mw5eK6i8adFSlx6zltZxrJFwurngVdZAEU8g_I,1064
34
+ webwidgets-1.1.1.dist-info/RECORD,,