diff --git a/docs/css.md b/docs/css.md
index c0d3c3a..09569d7 100644
--- a/docs/css.md
+++ b/docs/css.md
@@ -41,6 +41,7 @@ print(template)
## CSSObject
Using `style` object is not the only way to develop CSS with PH7, You can utilise a special class called `CSSObject` to define stylesheets.
+
```python
@@ -96,11 +97,10 @@ print(template)
A `CSSObject` can also be reused via class inheritance. You can define a base CSS class and reuse it however many times you like.
-
+
```python
-from ph7 import CSSObject, include
-from ph7.html import body, div, head, html
+from ph7 import CSSObject
class flex_center(CSSObject):
@@ -118,49 +118,27 @@ class textbox(flex_center):
width = "100vw"
-template = html(
- head(
- include(textbox),
- ),
- body(
- div(
- "Hello, World!",
- class_name=[textbox],
- )
- ),
-)
-
-print(template)
+print(textbox())
```
-```html
-
-
-
-
-
-
Hello, World!
-
-
+```css
+.textbox {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ width: 100vw;
+}
```
Furthermore `CSSObject` also allows for nesting by sub classes.
-
+
```python
-from ph7 import CSSObject, include
-from ph7.html import body, div, head, html
+from ph7 import CSSObject
class flex_center(CSSObject):
@@ -185,54 +163,79 @@ class textbox(flex_center):
font_family = "Lucida Console, Monaco, monospace"
-template = html(
- head(
- include(textbox),
- ),
- body(
- div(
- div(
- "Hello, World!",
- class_name=[textbox.text],
- ),
- class_name=[textbox],
- )
- ),
-)
-
-print(template)
+print(textbox())
```
-```html
-
-
-
-
-
-
-
-
+```css
+.textbox {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100vh;
+ width: 100vw;
+}
+.textbox .text {
+ font-size: 12px;
+ font-weight: 500;
+ font-family: Lucida Console, Monaco, monospace;
+}
```
To make your styles more manageable and reusable, you can define the styles in a separate module and import them throughout your codebase. Following section provides an example of a reusable stylesheet module.
+## Pseudo Classes and Elements
+
+A pseudo class can be defined by adding `_` at the beginning of class name, for selecting a child you can set `child` parameter. For pseudo, elements add `__` at the beginning of the class name.
+
+
+
+```python
+from ph7 import CSSObject, include
+from ph7.html import body, div, head, html
+
+
+class item(CSSObject):
+ """Flex center."""
+
+ height = "30px"
+ width = "100%"
+
+ margin_top = "5px"
+
+ class _nth_child(CSSObject):
+ """Nth child."""
+
+ child = 1
+ margin_top = "0px"
+
+ class __before(CSSObject):
+ """Before selector."""
+
+ content = '">"'
+
+
+print(item())
+```
+
+```css
+.item {
+ height: 30px;
+ width: 100%;
+ margin-top: 5px;
+}
+.item::before {
+ content: ">";
+}
+.item:nth-child(1) {
+ margin-top: 0px;
+}
+```
+
+
+
+
## Static Context
PH7 has a runtime context object which can be used for managing the static resource. To use a static context, import `ph7.context.ctx`, add `ctx.static.view(__name__)` at the top of your template and add `ctx.static.include` in your view. In the following example we'll use an external stylesheet for styling our view.
diff --git a/docs/html.md b/docs/html.md
index 34f2e72..b21feb0 100644
--- a/docs/html.md
+++ b/docs/html.md
@@ -246,14 +246,14 @@ You can define your view as a python function create views based on conditions,
```python
import typing as t
-from ph7.html import body, div, html, node
+from ph7.html import HtmlNode, body, div, html
user = div(class_name="user")
users = div(class_name="user")
nousers = div("Error, Users not found", class_name="error")
-def render_users(context: t.Dict) -> node:
+def render_users(context: t.Dict) -> HtmlNode:
"""Render users."""
if "number_of_users" not in context:
return nousers
@@ -301,14 +301,14 @@ You can also use named arguments instead of context argument to make thing more
```python
import typing as t
-from ph7.html import body, div, html, node
+from ph7.html import HtmlNode, body, div, html
user = div(class_name="user")
users = div(class_name="user")
nousers = div("Error, Users not found", class_name="error")
-def render_users(number_of_users: t.Optional[int] = None) -> node:
+def render_users(number_of_users: t.Optional[int] = None) -> HtmlNode:
"""Render users."""
if number_of_users is None:
return nousers
@@ -361,7 +361,7 @@ import time
import typing as t
from functools import lru_cache
-from ph7.html import body, div, html, node
+from ph7.html import HtmlNode, body, div, html
user = div(class_name="user")
users = div(class_name="user")
@@ -369,12 +369,12 @@ nousers = div("Error, Users not found", class_name="error")
@lru_cache
-def _render_users(n: int) -> node:
+def _render_users(n: int) -> HtmlNode:
"""Render users."""
return users(user(f"User {i}") for i in range(n))
-def render_users(context: t.Dict) -> node:
+def render_users(context: t.Dict) -> HtmlNode:
"""Render users."""
if "number_of_users" not in context:
return nousers
@@ -401,9 +401,9 @@ print(f"Third render: {time.perf_counter() - tick}")
```
```stdout
-First render: 6.87079775
-Second render: 0.3591619169999998
-Third render: 0.3592319160000006
+First render: 6.493273332999999
+Second render: 0.4277449579999999
+Third render: 0.3591492089999999
```
diff --git a/docs/js.md b/docs/js.md
index d86eb03..9b13948 100644
--- a/docs/js.md
+++ b/docs/js.md
@@ -92,7 +92,7 @@ If you want to render a function call with arguments you can decorate a method w
```python
from ph7 import include
-from ph7.html import body, button, div, head, html, img, node
+from ph7.html import HtmlNode, body, button, div, head, html, img
from ph7.js import document, fetch, js_callable
@@ -106,7 +106,7 @@ async def fetchUserProfilePicture(user: str) -> None:
document.getElementById(f"image-{user}").src = data.profile_picture
-def _user(name: str) -> node:
+def _user(name: str) -> HtmlNode:
return div(
img(src="#", style={"height": "200px", "width": "400px"}, id=f"image-{name}"),
button(
@@ -118,7 +118,7 @@ def _user(name: str) -> node:
)
-def _users(context: dict) -> node:
+def _users(context: dict) -> HtmlNode:
"""List users."""
return div(_user(name=name) for name in context["users"])
diff --git a/examples/css_inherit.py b/examples/css_inherit.py
index e533a0d..ecdbd79 100644
--- a/examples/css_inherit.py
+++ b/examples/css_inherit.py
@@ -1,5 +1,4 @@
-from ph7 import CSSObject, include
-from ph7.html import body, div, head, html
+from ph7 import CSSObject
class flex_center(CSSObject):
@@ -17,16 +16,4 @@ class textbox(flex_center):
width = "100vw"
-template = html(
- head(
- include(textbox),
- ),
- body(
- div(
- "Hello, World!",
- class_name=[textbox],
- )
- ),
-)
-
-print(template)
+print(textbox())
diff --git a/examples/css_nesting.py b/examples/css_nesting.py
index dd17ef1..2a3b1b5 100644
--- a/examples/css_nesting.py
+++ b/examples/css_nesting.py
@@ -1,5 +1,4 @@
-from ph7 import CSSObject, include
-from ph7.html import body, div, head, html
+from ph7 import CSSObject
class flex_center(CSSObject):
@@ -24,19 +23,4 @@ class text(CSSObject):
font_family = "Lucida Console, Monaco, monospace"
-template = html(
- head(
- include(textbox),
- ),
- body(
- div(
- div(
- "Hello, World!",
- class_name=[textbox.text],
- ),
- class_name=[textbox],
- )
- ),
-)
-
-print(template)
+print(textbox())
diff --git a/examples/css_pseudo_class.py b/examples/css_pseudo_class.py
new file mode 100644
index 0000000..66c6572
--- /dev/null
+++ b/examples/css_pseudo_class.py
@@ -0,0 +1,24 @@
+from ph7 import CSSObject
+
+
+class item(CSSObject):
+ """Flex center."""
+
+ height = "30px"
+ width = "100%"
+
+ margin_top = "5px"
+
+ class _nth_child(CSSObject):
+ """Nth child."""
+
+ child = 1
+ margin_top = "0px"
+
+ class __before(CSSObject):
+ """Before selector."""
+
+ content = '">"'
+
+
+print(item())
diff --git a/ph7/context.py b/ph7/context.py
index 10f804c..efde8f3 100644
--- a/ph7/context.py
+++ b/ph7/context.py
@@ -5,8 +5,7 @@
import typing as t
from pathlib import Path
-from ph7.css import CSSObject
-from ph7.css import render as to_css
+from ph7.css import CSSObject, to_css
from ph7.js import JavaScriptObject, JSCallable
from ph7.js.transpile import to_js
diff --git a/ph7/core/css.py b/ph7/core/css.py
new file mode 100644
index 0000000..9379ab0
--- /dev/null
+++ b/ph7/core/css.py
@@ -0,0 +1,86 @@
+"""CSS base classes."""
+
+import typing as t
+
+from ph7.core.tags import TAGS
+from ph7.style import attributes as css_attr
+
+
+class CSSNode:
+ """Renderable CSS node."""
+
+ __css__: bool = True
+
+ child: t.Optional[int] = None
+
+ @classmethod
+ def name(cls) -> str:
+ """Name string."""
+ name = cls.__name__.replace("_", "-")
+ if name in TAGS:
+ return name
+ if name.startswith("--"):
+ return f"::{name[2:]}"
+ if name.startswith("-") and cls.child is not None:
+ return f":{name[1:]}({cls.child})"
+ if name.startswith("-"):
+ return f":{name[1:]}"
+ return f" .{name}"
+
+ @classmethod
+ def format(cls) -> str:
+ """Name string."""
+ name = cls.__name__.replace("_", "-")
+ if name in TAGS:
+ raise ValueError(
+ f"{name} is a HTML tag selector, Cannot use it as a classname."
+ )
+ return name
+
+ @classmethod
+ def properties(cls) -> t.Dict[str, str]:
+ """Returns properties mapping."""
+ properties = {}
+ for base in cls.__bases__:
+ if hasattr(base, "properties"):
+ properties.update(base.properties())
+ for prop, val in cls.__dict__.items():
+ if isinstance(val, str) and not prop.startswith("__"):
+ properties[css_attr[prop]] = val
+ return properties
+
+ @classmethod
+ def subclasses(cls) -> t.List["CSSNode"]:
+ """Returns the list of available subclasses."""
+ cs = []
+ for c in cls.__dict__.values():
+ if hasattr(c, "__css__"):
+ cs.append(c)
+ return cs
+
+ @classmethod
+ def dict(cls, parent: str, context: t.Dict) -> None:
+ """Render CSS object."""
+ context[f"{parent}{cls.name()}"[1:]] = cls.properties()
+ for subc in cls.subclasses():
+ subc.dict(
+ parent=f"{parent}{cls.name()}",
+ context=context,
+ )
+
+
+def to_css(*objs: t.Union[CSSNode, t.Type[CSSNode]], minify: bool = False) -> str:
+ """Render CSS stylesheet."""
+ sheet = ""
+ container: t.Dict[str, t.Dict] = {}
+ separator = "" if minify else " "
+ newline = "" if minify else "\n"
+ tab = "" if minify else " "
+ for obj in objs:
+ obj.dict(parent="", context=container)
+ for cls in sorted(container):
+ sheet += f"{cls}" + "{" + newline
+ for prop, val in container[cls].items():
+ sheet += f"{tab}{prop}:{separator}{val};{newline}"
+ sheet += "}" + newline
+ return sheet
diff --git a/ph7/core/tags.py b/ph7/core/tags.py
new file mode 100644
index 0000000..1c81b17
--- /dev/null
+++ b/ph7/core/tags.py
@@ -0,0 +1,109 @@
+"""List of tags."""
+
+TAGS = {
+ "a",
+ "abbr",
+ "address",
+ "area",
+ "article",
+ "aside",
+ "audio",
+ "b",
+ "base",
+ "blockquote",
+ "body",
+ "br",
+ "button",
+ "canvas",
+ "caption",
+ "cite",
+ "code",
+ "col",
+ "colgroup",
+ "data",
+ "datalist",
+ "dd",
+ "details",
+ "div",
+ "dl",
+ "dt",
+ "em",
+ "embed",
+ "fieldset",
+ "figcaption",
+ "figure",
+ "footer",
+ "form",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "head",
+ "header",
+ "hr",
+ "html",
+ "i",
+ "iframe",
+ "img",
+ "input",
+ "ins",
+ "kbd",
+ "label",
+ "legend",
+ "li",
+ "link",
+ "main",
+ "map",
+ "mark",
+ "meta",
+ "meter",
+ "nav",
+ "noscript",
+ "object",
+ "ol",
+ "optgroup",
+ "option",
+ "output",
+ "p",
+ "param",
+ "picture",
+ "pre",
+ "progress",
+ "q",
+ "rp",
+ "rt",
+ "ruby",
+ "samp",
+ "script",
+ "section",
+ "select",
+ "small",
+ "source",
+ "span",
+ "strong",
+ "style",
+ "sub",
+ "summary",
+ "sup",
+ "svg",
+ "table",
+ "tbody",
+ "td",
+ "template",
+ "textarea",
+ "tfoot",
+ "th",
+ "thead",
+ "time",
+ "title",
+ "tr",
+ "track",
+ "u",
+ "ul",
+ "var",
+ "video",
+ "wbr",
+ "unpack",
+}
diff --git a/ph7/css.py b/ph7/css.py
index 048496d..4a21f64 100644
--- a/ph7/css.py
+++ b/ph7/css.py
@@ -4,18 +4,14 @@
# pylint: disable=line-too-long,too-many-lines,redefined-outer-name,redefined-builtin,invalid-name,too-many-locals
-import typing as t
-
from typing_extensions import Literal
-from ph7.style import attributes as css_attr
+from ph7.core.css import CSSNode, to_css
-class CSSObject:
+class CSSObject(CSSNode):
"""CSS class representation."""
- __css__ = True
-
animation: Literal[ # type: ignore
"animation-name",
"animation-duration",
@@ -843,8 +839,8 @@ class CSSObject:
grid: Literal["none", "initial", "inherit", str] # type: ignore
"""
- grid: none |grid-template-rows / grid-template-columns |grid-template-areas |
- grid-template-rows / [grid-auto-flow] grid-auto-columns |
+ grid: none |grid-template-rows / grid-template-columns |
+ grid-template-areas |grid-template-rows / [grid-auto-flow] grid-auto-columns |
[grid-auto-flow] grid-auto-rows / grid-template-columns |initial | inherit;
"""
@@ -1403,64 +1399,8 @@ class CSSObject:
]
"""writing-mode: horizontal-tb | vertical-rl | vertical-lr;"""
- @classmethod
- def name(cls) -> str:
- """Name string."""
- return cls.__name__.replace("_", "-")
-
- @classmethod
- def properties(cls) -> t.Dict[str, str]:
- """Returns properties mapping."""
- properties = {}
- for base in cls.__bases__:
- if hasattr(base, "properties"):
- properties.update(base.properties())
- for prop, val in cls.__dict__.items():
- if isinstance(val, str) and not prop.startswith("__"):
- properties[css_attr[prop]] = val
- return properties
-
- @classmethod
- def subclasses(cls) -> t.List["CSSObject"]:
- """Returns the list of available subclasses."""
- cs = []
- for c in cls.__dict__.values():
- if hasattr(c, "properties"):
- cs.append(c)
- return cs
-
def __str__(self) -> str:
- """String represenstation."""
- return render(self, minify=False)
+ """String representation."""
+ return to_css(self)
__repr__ = __str__
-
-
-def _render(
- obj: t.Union[CSSObject, t.Type[CSSObject]], parent: str, container: t.Dict
-) -> None:
- """Render CSS object."""
- container[f"{parent} .{obj.name()}"[1:]] = obj.properties()
- for subc in obj.subclasses():
- _render(
- obj=subc,
- parent=f"{parent} .{obj.name()}",
- container=container,
- )
-
-
-def render(*objs: t.Union[CSSObject, t.Type[CSSObject]], minify: bool = False) -> str:
- """Render CSS stylesheet."""
- sheet = ""
- container: t.Dict[str, t.Dict] = {}
- separator = "" if minify else " "
- newline = "" if minify else "\n"
- tab = "" if minify else " "
- for obj in objs:
- _render(obj=obj, parent="", container=container)
- for cls in sorted(container):
- sheet += f"{cls}" + "{" + newline
- for prop, val in container[cls].items():
- sheet += f"{tab}{prop}:{separator}{val};{newline}"
- sheet += "}" + newline
- return sheet
diff --git a/ph7/formatters.py b/ph7/formatters.py
index 2bbf0c0..893735e 100644
--- a/ph7/formatters.py
+++ b/ph7/formatters.py
@@ -68,7 +68,7 @@ def cformat(
if hasattr(class_name, "name"):
ctx.static.add(resource=t.cast(CSSObject, class_name))
- return t.cast(CSSObject, class_name).name()
+ return t.cast(CSSObject, class_name).format()
if isinstance(class_name, str):
return class_name
@@ -77,7 +77,7 @@ def cformat(
for cls in class_name:
if hasattr(cls, "name"):
ctx.static.add(resource=t.cast(CSSObject, cls))
- cls_name += f"{t.cast(CSSObject, cls).name()} "
+ cls_name += f"{t.cast(CSSObject, cls).format()} "
continue
cls_name += f"{cls} "
return cls_name[:-1]
diff --git a/ph7/static.py b/ph7/static.py
index e96d2ca..7369ea2 100644
--- a/ph7/static.py
+++ b/ph7/static.py
@@ -5,8 +5,7 @@
import typing as t
from ph7.core.html import HtmlNode
-from ph7.css import CSSObject
-from ph7.css import render as to_css
+from ph7.css import CSSObject, to_css
from ph7.html import script, style, unpack
from ph7.js import JavaScriptObject, JSCallable
from ph7.js.transpile import to_js
diff --git a/scripts/render/css.py b/scripts/render/css.py
index 140fce1..0266e3b 100644
--- a/scripts/render/css.py
+++ b/scripts/render/css.py
@@ -26,71 +26,20 @@
# pylint: disable=line-too-long,too-many-lines,redefined-outer-name,redefined-builtin,invalid-name,too-many-locals
-import typing as t
-from ph7.style import attributes as css_attr
from typing_extensions import Literal
-class CSSObject:
- \"\"\"CSS class representation.\"\"\"
+from ph7.core.css import CSSNode, to_css
- __css__ = True
+class CSSObject(CSSNode):
+ \"\"\"CSS class representation.\"\"\"
{properties}
- @classmethod
- def name(cls) -> str:
- \"\"\"Name string.\"\"\"
- return cls.__name__.replace("_", "-")
-
- @classmethod
- def properties(cls) -> t.Dict[str, str]:
- \"\"\"Returns properties mapping.\"\"\"
- properties = {{}}
- for base in cls.__bases__:
- if hasattr(base, "properties"):
- properties.update(base.properties())
- for prop, val in cls.__dict__.items():
- if isinstance(val, str) and not prop.startswith("__"):
- properties[css_attr[prop]] = val
- return properties
-
- @classmethod
- def subclasses(cls) -> t.List["CSSObject"]:
- \"\"\"Returns the list of available subclasses.\"\"\"
- cs = []
- for c in cls.__dict__.values():
- if hasattr(c, "properties"):
- cs.append(c)
- return cs
-
-
-def _render(obj: t.Union[CSSObject, t.Type[CSSObject]], parent: str, container: t.Dict) -> None:
- \"\"\"Render CSS object.\"\"\"
- container[f"{{parent}} .{{obj.name()}}"[1:]] = obj.properties()
- for subc in obj.subclasses():
- _render(
- obj=subc,
- parent=f"{{parent}} .{{obj.name()}}",
- container=container,
- )
-
-
-def render(*objs: t.Union[CSSObject, t.Type[CSSObject]], minify: bool = False) -> str:
- \"\"\"Render CSS stylesheet.\"\"\"
- sheet = ""
- container: t.Dict[str, t.Dict] = {{}}
- separator = "" if minify else " "
- newline = "" if minify else "\\n"
- tab = "" if minify else " "
- for obj in objs:
- _render(obj=obj, parent="", container=container)
- for cls in sorted(container):
- sheet += f"{{cls}}" + "{{" + newline
- for prop, val in container[cls].items():
- sheet += f"{{tab}}{{prop}}:{{separator}}{{val}};{{newline}}"
- sheet += "}}" + newline
- return sheet
+ def __str__(self) -> str:
+ \"\"\"String representation.\"\"\"
+ return to_css(self)
+ __repr__ = __str__
"""
properties = ""