diff --git a/.github/workflows/build-node-packages.yml b/.github/workflows/build-node-packages.yml
index 7db52b6..16eb78f 100644
--- a/.github/workflows/build-node-packages.yml
+++ b/.github/workflows/build-node-packages.yml
@@ -29,7 +29,7 @@ jobs:
- name: Publish node packages
run: |
- node build.js ${{ github.event.release.tag_name }}
+ node publish.js ${{ github.event.release.tag_name }}
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
diff --git a/.github/workflows/create-release.yaml b/.github/workflows/create-release.yaml
index 2bce84f..5029a9b 100644
--- a/.github/workflows/create-release.yaml
+++ b/.github/workflows/create-release.yaml
@@ -9,9 +9,11 @@ on:
permissions:
contents: write
+ pull-requests: write
env:
VERSION: ${{ github.event.inputs.version }}
+ GH_TOKEN: ${{ github.token }}
jobs:
deploy:
@@ -45,6 +47,7 @@ jobs:
run: |
git config --local user.email "${{ github.actor }}@users.noreply.github.com"
git config --local user.name "${{ github.actor }}"
+ git checkout -b release/$VERSION
git add .
git commit -m "Update version to $VERSION"
git push origin release/$VERSION
diff --git a/.github/workflows/python-test.yml b/.github/workflows/python-test.yml
index 8abba0a..51520c7 100644
--- a/.github/workflows/python-test.yml
+++ b/.github/workflows/python-test.yml
@@ -19,14 +19,14 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: ["3.11", "3.12"]
+ python-version: ["3.9", "3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
- python-version: ${{ matrix.python-version }}
+ python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
@@ -38,7 +38,7 @@ jobs:
working-directory: ${{ github.workspace }}
run: |
python -m unittest discover -s tests -p 'test_*.py' -t ${{ github.workspace }}
-
+
- name: Run build test
working-directory: ${{ github.workspace }}
run: |
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 1243c12..72e839d 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -10,4 +10,6 @@ sphinx:
python:
install:
- - requirements: docs/requirements.txt
\ No newline at end of file
+ - requirements: docs/requirements.txt
+ - method: pip
+ path: .
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 5a20232..e82f7cc 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,7 @@
{
- "cSpell.words": [
- "Renderable"
- ]
-}
\ No newline at end of file
+ "cSpell.words": ["pydom", "Renderable"],
+ "python.analysis.diagnosticSeverityOverrides": {
+ "reportWildcardImportFromLibrary": "none"
+ },
+ "python.analysis.extraPaths": ["../pydom"]
+}
diff --git a/README.md b/README.md
index 60213b2..5a01d04 100644
--- a/README.md
+++ b/README.md
@@ -74,20 +74,20 @@ class Person(Component):
```
To call a function on the server include this script in your file
```html
-
+
```
Import the middleware and mount it to your app
```python
from fastapi import FastAPI
-from seamless.middlewares import ASGIMiddleware as SeamlessMiddleware
+from seamless.middlewares import SocketIOMiddleware
app = FastAPI()
-app.add_middleware(SeamlessMiddleware)
+app.add_middleware(SocketIOMiddleware)
```
You can pass the following config to the middleware to change the socket path of all seamless endpoints.
```python
app.add_middleware(
- SeamlessMiddleware,
+ SocketIOMiddleware,
socket_path="/my/custom/path"
)
```
diff --git a/docs/.gitignore b/docs/.gitignore
new file mode 100644
index 0000000..a6e7e9b
--- /dev/null
+++ b/docs/.gitignore
@@ -0,0 +1,4 @@
+.lh/
+build/*
+.venv/
+seamless/
\ No newline at end of file
diff --git a/docs/3-events/1-middleware.rst b/docs/3-events/1-middleware.rst
deleted file mode 100644
index 7cfd8f3..0000000
--- a/docs/3-events/1-middleware.rst
+++ /dev/null
@@ -1,21 +0,0 @@
-.. _asg-middleware:
-
-###############
-ASGI Middleware
-###############
-
-The ``ASGIMiddleware`` class is the middleware that handles all the linked actions between the
-frontend components and the backend functions.
-
-When linking a python function to a frontend component, the ``ASGIMiddleware`` middleware must be
-added to the ASGI application.
-
-.. code-block:: python
- :caption: Adding the ASGIMiddleware to the ASGI application
-
- from fastapi import FastAPI
- from seamless.middlewares import ASGIMiddleware as SeamlessMiddleware
-
- app = FastAPI()
- app.add_middleware(SeamlessMiddleware)
-
diff --git a/docs/99-api-reference/1-components/1-base.rst b/docs/99-api-reference/1-components/1-base.rst
deleted file mode 100644
index 4a1209d..0000000
--- a/docs/99-api-reference/1-components/1-base.rst
+++ /dev/null
@@ -1,26 +0,0 @@
-.. _base-component-api-reference:
-
-#########
-Component
-#########
-
-.. py:class:: Component
- :module: seamless
- :canonical: seamless.core.component.Component
-
- The base class for all components.
-
- .. py:method:: __init__(*children: ChildType, **props: Any)
-
- :param children: The children of the component
- :type children: :py:type:`~seamless.types.ChildrenType`
- :param props: The props of the component
- :type props: :py:type:`~seamless.types.PropsType`
-
- .. py:method:: render(self) -> RenderResult
- :abstractmethod:
-
- Renders the component. |br|
- This method is an abstract method that must be implemented by the subclass.
-
- :rtype: :py:type:`~seamless.types.RenderResult`
\ No newline at end of file
diff --git a/docs/99-api-reference/1-components/index.rst b/docs/99-api-reference/1-components/index.rst
deleted file mode 100644
index 05441fb..0000000
--- a/docs/99-api-reference/1-components/index.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-##########
-Components
-##########
-
-.. toctree::
- :glob:
- :maxdepth: 1
-
- *
- */index
\ No newline at end of file
diff --git a/docs/99-api-reference/1-components/page.rst b/docs/99-api-reference/1-components/page.rst
deleted file mode 100644
index eb72aeb..0000000
--- a/docs/99-api-reference/1-components/page.rst
+++ /dev/null
@@ -1,34 +0,0 @@
-.. _page-api-reference:
-
-####
-Page
-####
-
-.. py:currentmodule:: seamless.components
-
-.. py:class:: Page
- :canonical: seamless.components.page.Page
-
- Inherits from :py:class:`~seamless.core.component.Component`.
-
- Methods
- =======
-
- .. py:method:: __init__(self, title=None, html_props=None, head_props=None, body_props=None)
-
- :param string title: The page's title
- :param dict html_props: The props to insert inside the ``html`` tag |br| **default**: ``{ "lang": "en" }``
- :param dict head_props: The props to insert inside the ``head`` tag |br| **default**: ``{}``
- :param dict body_props: The props to insert inside the ``body`` tag |br| **default**: ``{ "dir": "ltr" }``
-
- .. py:method:: head(self) -> Iterable[ChildType]
-
- The children that will be inside the ``head`` tag.
-
- :rtype: :py:type:`~seamless.types.ChildrenType`
-
- .. py:method:: body(self) -> Iterable[ChildType]
-
- The children that will be inside the ``body`` tag.
-
- :rtype: :py:type:`~seamless.types.ChildrenType`
\ No newline at end of file
diff --git a/docs/99-api-reference/1-components/router/index.rst b/docs/99-api-reference/1-components/router/index.rst
deleted file mode 100644
index 241f592..0000000
--- a/docs/99-api-reference/1-components/router/index.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-######
-Router
-######
-
-.. py:module:: seamless.components.router
-
-.. toctree::
- :glob:
- :maxdepth: 1
-
- *
\ No newline at end of file
diff --git a/docs/99-api-reference/1-components/router/route.rst b/docs/99-api-reference/1-components/router/route.rst
deleted file mode 100644
index bf5a214..0000000
--- a/docs/99-api-reference/1-components/router/route.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-.. _route-api-reference:
-
-#####
-Route
-#####
-
-.. py:currentmodule:: seamless.components.router
-
-.. py:class:: Route
- :canonical: seamless.components.router.route.Route
-
- Inherits from :py:class:`~seamless.core.component.Component`.
-
- Methods
- =======
-
- .. py:method:: __init__(self, path: str, component: type[Component])
-
- :param string path: The path of the page
- :param ``Component`` component: The component to render when the path is matched
diff --git a/docs/99-api-reference/1-components/router/router-link.rst b/docs/99-api-reference/1-components/router/router-link.rst
deleted file mode 100644
index db09700..0000000
--- a/docs/99-api-reference/1-components/router/router-link.rst
+++ /dev/null
@@ -1,20 +0,0 @@
-.. _router-link-api-reference:
-
-###########
-Router Link
-###########
-
-.. py:currentmodule:: seamless.components.router
-
-.. py:class:: RouterLink
- :canonical: seamless.components.router.router_link.RouterLink
-
- Inherits from :py:class:`~seamless.core.component.Component`.
-
- Methods
- =======
-
- .. py:method:: __init__(self, *, to: str, **anchor_props)
-
- :param string to: The path to navigate to
- :param dict anchor_props: The properties to pass to the anchor element
diff --git a/docs/99-api-reference/1-components/router/router.rst b/docs/99-api-reference/1-components/router/router.rst
deleted file mode 100644
index dada75e..0000000
--- a/docs/99-api-reference/1-components/router/router.rst
+++ /dev/null
@@ -1,22 +0,0 @@
-.. _router-api-reference:
-
-######
-Router
-######
-
-.. py:currentmodule:: seamless.components.router
-
-.. py:class:: Router
- :canonical: seamless.components.router.router.Router
-
- Inherits from :py:class:`~seamless.core.component.Component`.
-
- Methods
- =======
-
- .. py:method:: __init__(self, *, loading_component: type[Component] | None = None)
- .. py:method:: __init__(self, *routes: Route, loading_component: type[Component] | None = None)
- :no-index:
-
- :param ``Component`` loading_component: The component to show between components loading
- :param ``Route`` routes: The routes to include in the application - can also passed as children of the Router component
diff --git a/docs/99-api-reference/2-state/index.rst b/docs/99-api-reference/2-state/index.rst
deleted file mode 100644
index 2bdcbaa..0000000
--- a/docs/99-api-reference/2-state/index.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-.. _state-api-reference:
-
-#####
-State
-#####
-
-.. toctree::
- :glob:
-
- *
- */index
\ No newline at end of file
diff --git a/docs/99-api-reference/3-core/index.rst b/docs/99-api-reference/3-core/index.rst
deleted file mode 100644
index a13af66..0000000
--- a/docs/99-api-reference/3-core/index.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-##########
-Components
-##########
-
-.. py:module:: seamless.core
-
-.. toctree::
- :glob:
- :maxdepth: 1
-
- *
- */index
\ No newline at end of file
diff --git a/docs/99-api-reference/3-core/rendering.rst b/docs/99-api-reference/3-core/rendering.rst
deleted file mode 100644
index d7bbfc2..0000000
--- a/docs/99-api-reference/3-core/rendering.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-.. _rendering-api-reference:
-
-#########
-Rendering
-#########
-
diff --git a/docs/99-api-reference/99-misc/index.rst b/docs/99-api-reference/99-misc/index.rst
deleted file mode 100644
index 2478815..0000000
--- a/docs/99-api-reference/99-misc/index.rst
+++ /dev/null
@@ -1,25 +0,0 @@
-.. _misc:
-
-#############
-Miscellaneous
-#############
-
-.. py:type:: Primitive
- :module: seamless.types
- :canonical: Union[str, int, float, bool, None]
-
-.. py:type:: Renderable
- :module: seamless.types
- :canonical: Union[Component, Element]
-
-.. py:type:: ChildType
- :module: seamless.types
- :canonical: Union[Renderable, Primitive]
-
-.. py:type:: ChildrenType
- :module: seamless.types
- :canonical: Collection[ChildType]
-
-.. py:type:: RenderResult
- :module: seamless.types
- :canonical: Renderable | Primitive
diff --git a/docs/99-api-reference/index.rst b/docs/99-api-reference/index.rst
deleted file mode 100644
index 70a93fe..0000000
--- a/docs/99-api-reference/index.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-.. _api-reference:
-
-#############
-API Reference
-#############
-
-This section contains the API reference for Seamless.
-
-.. toctree::
- :glob:
- :maxdepth: 2
-
- */index
diff --git a/docs/Makefile b/docs/Makefile
new file mode 100644
index 0000000..d0c3cbf
--- /dev/null
+++ b/docs/Makefile
@@ -0,0 +1,20 @@
+# Minimal makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line, and also
+# from the environment for the first two.
+SPHINXOPTS ?=
+SPHINXBUILD ?= sphinx-build
+SOURCEDIR = source
+BUILDDIR = build
+
+# Put it first so that "make" without argument is like "make help".
+help:
+ @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
+
+.PHONY: help Makefile
+
+# Catch-all target: route all unknown targets to Sphinx using the new
+# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
+%: Makefile
+ @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css
new file mode 100644
index 0000000..b4454fc
--- /dev/null
+++ b/docs/_static/css/custom.css
@@ -0,0 +1,28 @@
+html[data-theme="light"] {
+ --pst-color-primary: #00518a;
+ --pst-color-secondary: #f7f7f7;
+ --pst-color-secondary-hover: #e6e6e6;
+}
+
+html[data-theme="dark"] {
+ --pst-color-primary: #0b7ed1;
+ --pst-color-secondary: #f7f7f7;
+ --pst-color-secondary-hover: #e6e6e6;
+}
+
+.navbar-brand {
+ gap: 1rem;
+}
+
+.navbar-brand img {
+ height: 85%;
+}
+
+.navbar-item nav {
+ border-left: 2px solid #5d5d5f;
+ padding-left: 8px;
+}
+
+#pst-back-to-top:hover {
+ background-color: var(--pst-color-secondary-hover);
+}
\ No newline at end of file
diff --git a/docs/favicon.ico b/docs/_static/images/favicon.ico
similarity index 100%
rename from docs/favicon.ico
rename to docs/_static/images/favicon.ico
diff --git a/docs/_static/images/favicon.png b/docs/_static/images/favicon.png
new file mode 100644
index 0000000..04a2534
Binary files /dev/null and b/docs/_static/images/favicon.png differ
diff --git a/docs/_static/images/favicon.svg b/docs/_static/images/favicon.svg
new file mode 100644
index 0000000..142e95e
--- /dev/null
+++ b/docs/_static/images/favicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/_static/images/seamless.svg b/docs/_static/images/seamless.svg
index 57b0f7a..2fec43f 100644
--- a/docs/_static/images/seamless.svg
+++ b/docs/_static/images/seamless.svg
@@ -1 +1 @@
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/docs/api-reference/index.rst b/docs/api-reference/index.rst
new file mode 100644
index 0000000..c1abcb2
--- /dev/null
+++ b/docs/api-reference/index.rst
@@ -0,0 +1,11 @@
+API Reference
+=============
+
+This page contains auto-generated API reference documentation [#f1]_.
+
+.. toctree::
+ :titlesonly:
+
+ /api-reference/seamless/index
+
+.. [#f1] Created with `sphinx-autoapi `_
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index eac12d9..c140fc4 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,4 +1,7 @@
-from seamless.version import version as __version__
+import sys, datetime
+
+sys.path.insert(0, "..")
+from seamless import __version__
# Configuration file for the Sphinx documentation builder.
#
@@ -9,33 +12,73 @@
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = "Seamless"
-copyright = "2024, Xpo Development"
author = "Xpo Development"
+copyright = f"{datetime.date.today().year}, {author}"
version = __version__
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
- "sphinx_rtd_theme",
+ "pydata_sphinx_theme",
"sphinx_substitution_extensions",
+ "autoapi.extension",
]
templates_path = ["_templates"]
-exclude_patterns = []
+exclude_patterns = ["_build", "_templates", "Thumbs.db", ".DS_Store"]
+
+autoapi_dirs = ["../seamless"]
+autoapi_ignore = ["*/internal/*"]
+autoapi_options = [
+ "members",
+ "undoc-members",
+ "show-inheritance",
+ "show-module-summary",
+ "special-members",
+ "imported-members",
+]
+autoapi_root = "api-reference"
+autoapi_keep_files = True
+autoapi_generate_api_docs = True
+autoapi_add_toctree_entry = True
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
-html_theme = "sphinx_rtd_theme"
+html_favicon = "_static/images/favicon.ico"
+html_logo = "_static/images/favicon.svg"
html_static_path = ["_static"]
-html_favicon = "favicon.ico"
+html_theme = "pydata_sphinx_theme"
html_title = "Seamless Documentation"
+html_theme_options = {
+ "icon_links": [
+ {
+ "name": "GitHub",
+ "url": "https://github.com/xpodev/seamless",
+ "icon": "fa-brands fa-github",
+ },
+ {
+ "name": "PyPI",
+ "url": "https://pypi.org/project/python-seamless",
+ "icon": "fa-brands fa-python",
+ },
+ ],
+ "logo": {
+ "alt_text": "Seamless",
+ "text": "Seamless",
+ },
+}
+
+html_css_files = [
+ "css/custom.css",
+]
+
rst_prolog = f"""
.. |version| replace:: {version}
.. |br| raw:: html
-"""
\ No newline at end of file
+"""
diff --git a/docs/index.rst b/docs/index.rst
index c3f5744..1efad52 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -2,9 +2,22 @@
Seamless
########
-.. image:: _static/images/seamless.svg
+.. raw:: html
+
+
+
+.. image:: /_static/images/seamless.svg
:alt: Seamless
:align: center
+ :class: dark-light py-4
+
+.. raw:: html
+
+
+ Simple to learn, easy to use, fully-featured UI library for Python
+
.. image:: https://img.shields.io/pypi/v/python-seamless.svg
:target: https://pypi.org/project/python-seamless/
@@ -13,21 +26,29 @@ Seamless
|br|
-Welcome to Seamless's documentation!
-####################################
Seamless is a Python library for building web applications with a focus on simplicity and ease of use.
-It is designed to be easy to learn and use, while still providing the power and flexibility needed for complex applications.
-
+It is designed to be easy to learn and use, while still providing the power and flexibility needed for
+complex applications.
+
+Seamless provides a simple and intuitive API that makes it easy to create complex web applications with
+just a few lines of code. It includes a wide range of components and utilities that make it easy to build
+responsive and interactive user interfaces.
+
+Key features of Seamless include:
+
+- **Simple API**: Seamless provides a simple and intuitive API that makes it easy to create complex web applications
+ with just a few lines of code.
+- **Familiarity**: Seamless is designed to be familiar to web developers, with a syntax that is similar to React.
+- **Extensibility**: Seamless is built on top of `PyDOM `_, a powerful and flexible
+ library for creating and manipulating HTML elements in Python.
+- **Compatibility**: Seamless is compatible with any Python web framework that supports ASGI, including `Starlette `_
+ and `FastAPI `_. It can also be used with other ASGI-compatible frameworks such as `Django `_
+ and `Flask `_.
+
.. toctree::
- :maxdepth: 2
:glob:
+ :hidden:
- quick-start
- */index
-
-
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
-
+ user-guide/index
+ api-reference/index
diff --git a/docs/make.bat b/docs/make.bat
new file mode 100644
index 0000000..747ffb7
--- /dev/null
+++ b/docs/make.bat
@@ -0,0 +1,35 @@
+@ECHO OFF
+
+pushd %~dp0
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+ set SPHINXBUILD=sphinx-build
+)
+set SOURCEDIR=source
+set BUILDDIR=build
+
+%SPHINXBUILD% >NUL 2>NUL
+if errorlevel 9009 (
+ echo.
+ echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
+ echo.installed, then set the SPHINXBUILD environment variable to point
+ echo.to the full path of the 'sphinx-build' executable. Alternatively you
+ echo.may add the Sphinx directory to PATH.
+ echo.
+ echo.If you don't have Sphinx installed, grab it from
+ echo.https://www.sphinx-doc.org/
+ exit /b 1
+)
+
+if "%1" == "" goto help
+
+%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+goto end
+
+:help
+%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
+
+:end
+popd
diff --git a/docs/package.json b/docs/package.json
new file mode 100644
index 0000000..aa66f8c
--- /dev/null
+++ b/docs/package.json
@@ -0,0 +1,6 @@
+{
+ "scripts": {
+ "dev": "sphinx-autobuild -b html . _build --host 0.0.0.0 --port 8080",
+ "dev:clean": "sphinx-autobuild -a -b html . _build --host 0.0.0.0 --port 8080"
+ }
+}
\ No newline at end of file
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 71d8116..bb8d2be 100644
Binary files a/docs/requirements.txt and b/docs/requirements.txt differ
diff --git a/docs/1-basics/1-syntax.rst b/docs/user-guide/1-basics/1-syntax.rst
similarity index 76%
rename from docs/1-basics/1-syntax.rst
rename to docs/user-guide/1-basics/1-syntax.rst
index de7b8bf..ce92fbf 100644
--- a/docs/1-basics/1-syntax.rst
+++ b/docs/user-guide/1-basics/1-syntax.rst
@@ -22,11 +22,11 @@ Using the card component as an example, we will show the different syntaxes.
self.title = title
def render(self):
- return Div(class_name="card")(
- self.title and Div(class_name="card-title")(
+ return Div(classes="card")(
+ self.title and Div(classes="card-title")(
self.title
),
- Div(class_name="card-content")(
+ Div(classes="card-content")(
*self.children
)
)
@@ -53,13 +53,15 @@ This is called "Pythonic" because it is similar to how we write Python code.
from seamless import Div, render
from card import Card
- render(Card(
- Div( # This is the Card's child
- "Hello, World!", # This is the content of the Div
- id="card-content"
- ),
- title="Card Title"
- ))
+ render(
+ Card(
+ Div( # This is the Card's child
+ "Hello, World!", # This is the content of the Div
+ id="card-content"
+ ),
+ title="Card Title"
+ )
+ )
HTML Syntax
###########
@@ -74,11 +76,13 @@ This is similar to how we write HTML tags, with the props as attributes and the
from seamless import Div, render
from card import Card
- render(Card(title="Card Title")(
- Div(id="card-content")( # This is the Card's child
- "Hello, World!" # This is the content of the Div
+ render(
+ Card(title="Card Title")(
+ Div(id="card-content")( # This is the Card's child
+ "Hello, World!" # This is the content of the Div
+ )
)
- ))
+ )
Flutter Syntax
##############
@@ -93,13 +97,15 @@ This is similar to how we write Flutter widgets.
from seamless import Div, render
from card import Card
- render(Card(
- title="Card Title",
- children=[Div( # This is the Card's child
- id="card-content".
- children=["Hello, World!"] # This is the content of the Div
- )]
- ))
+ render(
+ Card(
+ title="Card Title",
+ children=[Div( # This is the Card's child
+ id="card-content".
+ children=["Hello, World!"] # This is the content of the Div
+ )]
+ )
+ )
.. note::
diff --git a/docs/1-basics/2-rendering-components.rst b/docs/user-guide/1-basics/2-rendering-components.rst
similarity index 91%
rename from docs/1-basics/2-rendering-components.rst
rename to docs/user-guide/1-basics/2-rendering-components.rst
index 8717043..02d468f 100644
--- a/docs/1-basics/2-rendering-components.rst
+++ b/docs/user-guide/1-basics/2-rendering-components.rst
@@ -114,11 +114,11 @@ Props Rendering
###############
When rendering components, some props names are converted to another name in the HTML representation.
-For example, the ``class_name`` prop is converted to the ``class`` attribute in the HTML representation.
+For example, the ``classes`` prop is converted to the ``class`` attribute in the HTML representation.
The full list of prop names and their corresponding HTML attributes is as follows:
-- ``class_name`` -> ``class``
+- ``classes`` -> ``class``
- ``html_for`` -> ``for``
- ``accept_charset`` -> ``accept-charset``
- ``http_equiv`` -> ``http-equiv``
@@ -134,3 +134,6 @@ The full list of prop names and their corresponding HTML attributes is as follow
All ``on_`` props that are python functions are converted to event listeners in the HTML representation
and will not be rendered as attributes.
+By default, all props with underscore in their name and a value of :py:type:`~seamless.types.Primitive` type,
+are converted to HTML attributes with dash instead of underscore.
+
diff --git a/docs/1-basics/3-dynamic-pages.rst b/docs/user-guide/1-basics/3-dynamic-pages.rst
similarity index 95%
rename from docs/1-basics/3-dynamic-pages.rst
rename to docs/user-guide/1-basics/3-dynamic-pages.rst
index 1f67ee9..ad936bf 100644
--- a/docs/1-basics/3-dynamic-pages.rst
+++ b/docs/user-guide/1-basics/3-dynamic-pages.rst
@@ -63,5 +63,5 @@ alternatively, you can add the script at the end of the body tag.
def head(self):
return (
*super().head(),
- Script(src="https://cdn.jsdelivr.net/npm/@python-seamless@|version|/umd/seamless.min.js")
+ Script(src="https://cdn.jsdelivr.net/npm/python-seamless@|version|/umd/seamless.min.js")
)
\ No newline at end of file
diff --git a/docs/1-basics/4-state.rst b/docs/user-guide/1-basics/4-state.rst
similarity index 96%
rename from docs/1-basics/4-state.rst
rename to docs/user-guide/1-basics/4-state.rst
index 6ab2198..76fab2d 100644
--- a/docs/1-basics/4-state.rst
+++ b/docs/user-guide/1-basics/4-state.rst
@@ -55,7 +55,7 @@ Using a State
To use a state, call the state object with the new value to update the state.
Setting the state is done by calling the state object with the new value as a JavaScript expression.
-When setting a new value to the state, the current state is available as ``state`` in the expression.
+When setting a new value to the state, the current state is available as ``current`` in the expression.
Getting the state is done by calling the state object without any arguments.
@@ -67,7 +67,7 @@ Getting the state is done by calling the state object without any arguments.
return Div(
Button(
"Increment",
- on_click=counter("state + 1")
+ on_click=counter("current + 1")
),
Span(counter())
)
diff --git a/docs/1-basics/index.rst b/docs/user-guide/1-basics/index.rst
similarity index 100%
rename from docs/1-basics/index.rst
rename to docs/user-guide/1-basics/index.rst
diff --git a/docs/2-components/1-base-component.rst b/docs/user-guide/2-components/1-base-component.rst
similarity index 91%
rename from docs/2-components/1-base-component.rst
rename to docs/user-guide/2-components/1-base-component.rst
index f378d33..7beec68 100644
--- a/docs/2-components/1-base-component.rst
+++ b/docs/user-guide/2-components/1-base-component.rst
@@ -25,11 +25,11 @@ It provides a ``children`` property that is a tuple of the components that are c
self.title = title
def render(self):
- return Div(class_name="card")(
- self.title and Div(class_name="card-title")(
+ return Div(classes="card")(
+ self.title and Div(classes="card-title")(
self.title
),
- Div(class_name="card-content")(
+ Div(classes="card-content")(
*self.children
)
)
@@ -82,8 +82,7 @@ The most common way of handling props is to store them as instance variables and
class AppButton(Component):
def __init__(self, type: str):
- self.color = color
- self.style = Style(background_color=f"var(--color-{color})")
+ self.style = Style(background_color=f"var(--color-{type})")
def render(self):
return Button(style=self.style)(
@@ -137,11 +136,11 @@ component with the children as arguments. (See :ref:`syntax`)
self.title = title
def render(self):
- return Div(class_name="card")(
- Div(class_name="card-header")(
+ return Div(classes="card")(
+ Div(classes="card-header")(
self.title
),
- Div(class_name="card-body")(
+ Div(classes="card-body")(
*self.children
)
)
diff --git a/docs/2-components/2-page.rst b/docs/user-guide/2-components/2-page.rst
similarity index 88%
rename from docs/2-components/2-page.rst
rename to docs/user-guide/2-components/2-page.rst
index f822f91..32575e0 100644
--- a/docs/2-components/2-page.rst
+++ b/docs/user-guide/2-components/2-page.rst
@@ -7,7 +7,7 @@ Page
The page component is a the top-level component that represents a web page.
It is a container that holds all the other components that will be rendered on the page.
-The default page component includes the components for the following HTML structure:
+The default page component includes the elements for the following HTML structure:
.. code-block:: html
:caption: Default page structure
@@ -42,14 +42,15 @@ The page component can be used to create a new page by passing the following pro
from seamless import Div, P
from seamless.components import Page
+
def my_awesome_page():
return Page(title="My awesome page")(
- Div(class_name="container mt-5")(
- Div(class_name="text-center p-4 rounded")(
- Div(class_name="h-1")(
+ Div(classes="container mt-5")(
+ Div(classes="text-center p-4 rounded")(
+ Div(classes="h-1")(
"Awesome page"
),
- P(class_name="lead")(
+ P(classes="lead")(
"Welcome to seamless"
)
)
@@ -68,6 +69,7 @@ You can create custom pages by extending the page component and overriding the d
from seamless import Div, Link
from seamless.components import Page
+
class MyPage(Page):
def head(self):
return (
@@ -85,14 +87,15 @@ You can create custom pages by extending the page component and overriding the d
)
)
+
def my_awesome_page():
return MyPage(title="My awesome page")(
- Div(class_name="container mt-5")(
- Div(class_name="text-center p-4 rounded")(
- Div(class_name="h-1")(
+ Div(classes="container mt-5")(
+ Div(classes="text-center p-4 rounded")(
+ Div(classes="h-1")(
"Awesome page"
),
- P(class_name="lead")(
+ P(classes="lead")(
"Welcome to seamless"
)
)
diff --git a/docs/2-components/3-fragment.rst b/docs/user-guide/2-components/3-fragment.rst
similarity index 100%
rename from docs/2-components/3-fragment.rst
rename to docs/user-guide/2-components/3-fragment.rst
diff --git a/docs/2-components/4-router.rst b/docs/user-guide/2-components/4-router.rst
similarity index 93%
rename from docs/2-components/4-router.rst
rename to docs/user-guide/2-components/4-router.rst
index 9d7aa59..2327867 100644
--- a/docs/2-components/4-router.rst
+++ b/docs/user-guide/2-components/4-router.rst
@@ -54,7 +54,7 @@ To navigate between the pages, use the ``RouterLink`` component from ``seamless.
class MyApp(Component):
def render(self):
- return Div(class_name="root")(
+ return Div(classes="root")(
Nav(
RouterLink(to="/")(
"Home"
@@ -66,7 +66,7 @@ To navigate between the pages, use the ``RouterLink`` component from ``seamless.
"Contact"
)
),
- Div(class_name="content")(
+ Div(classes="content")(
Router(
Route(path="/", component=Home),
Route(path="/about", component=About),
@@ -117,6 +117,7 @@ The supported types are:
- ``int``
- ``float``
+- ``path`` - a string that captures the path until the end of the path without query parameters (e.g. ``/user/{path:path}``)
The parameter will be passed to the component as a prop with the same name.
@@ -148,7 +149,7 @@ The parameter will be passed to the component as a prop with the same name.
class MyApp(Component):
def render(self):
- return Div(class_name="root")(
+ return Div(classes="root")(
Nav(
RouterLink(to="/user/1")(
"User 1"
@@ -157,7 +158,7 @@ The parameter will be passed to the component as a prop with the same name.
"User 2"
)
),
- Div(class_name="content")(
+ Div(classes="content")(
Router(
Route(path="/user/{id:int}", component=User)
)
diff --git a/docs/2-components/index.rst b/docs/user-guide/2-components/index.rst
similarity index 100%
rename from docs/2-components/index.rst
rename to docs/user-guide/2-components/index.rst
diff --git a/docs/user-guide/3-events/1-transports.rst b/docs/user-guide/3-events/1-transports.rst
new file mode 100644
index 0000000..cced813
--- /dev/null
+++ b/docs/user-guide/3-events/1-transports.rst
@@ -0,0 +1,47 @@
+.. _transports:
+
+##########
+Transports
+##########
+
+Transports are the underlying mechanism that the event system uses to send
+messages between the server and the client.
+
+By default, Seamless uses `socket.io `_ as the transport mechanism. This
+allows for real-time, bidirectional and event-based communication between the
+server and the client.
+
+.. note::
+ The transport mechanism is abstracted away from the user, and you do not
+ need to interact with it directly. The event system provides a high-level
+ API that allows you to send and receive messages without worrying about the
+ underlying transport mechanism.
+
+To use the event system, you need to add the transport middleware to your ASGI app and
+initialize the event system.
+
+.. code-block:: python
+ :caption: Adding the transport middleware
+
+ from fastapi import FastAPI
+ from seamless.middlewares import SocketIOMiddleware
+
+ app = FastAPI()
+ app.add_middleware(SocketIOMiddleware)
+
+To initialize the event system, in the root component of your application (can be the base page or the main layout)
+call the ``SocketIOTransport.init`` method from the ``seamless.extension`` module.
+
+.. code-block:: python
+ :caption: Initializing the event system
+
+ from seamless.extension import SocketIOTransport
+
+ class MyPage(Page):
+ def body(self):
+ return (
+ SocketIOTransport.init(),
+ *super().body()
+ )
+
+This will initialize the event system and make it available to the components.
\ No newline at end of file
diff --git a/docs/3-events/2-data-validation.rst b/docs/user-guide/3-events/2-data-validation.rst
similarity index 94%
rename from docs/3-events/2-data-validation.rst
rename to docs/user-guide/3-events/2-data-validation.rst
index b3c5b7a..16a0a8a 100644
--- a/docs/3-events/2-data-validation.rst
+++ b/docs/user-guide/3-events/2-data-validation.rst
@@ -33,8 +33,8 @@ Here is an example of how to use ``pydantic`` to validate data:
)
)
- def on_submit(self, data: SubmitEvent[UserLogin]):
- print(data)
+ def on_submit(self, event: SubmitEvent[UserLogin]):
+ print(event.data)
This will create a form with the fields ``username``, ``password``, and ``remember_me``, and a submit button.
diff --git a/docs/3-events/index.rst b/docs/user-guide/3-events/index.rst
similarity index 82%
rename from docs/3-events/index.rst
rename to docs/user-guide/3-events/index.rst
index dfb25f9..d3f7c20 100644
--- a/docs/3-events/index.rst
+++ b/docs/user-guide/3-events/index.rst
@@ -39,11 +39,19 @@ The following example demonstrates how to submit a form to the server:
),
)
- def save_email(self, event_data: SubmitEvent):
+ def save_email(self, event: SubmitEvent):
user = get_user(self.email)
- user.email = event_data["email"]
+ user.email = event.data["email"]
user.save()
+In this example, we bind the ``submit`` event of the form to the ``save_email`` method of the user.
+
+.. code-block:: python
+
+ Form(on_submit=self.save_email)
+
+When the form is submitted, the ``save_email`` method is called with the event data.
+
Scoping
#######
@@ -104,7 +112,7 @@ of the element and will register the event listener.
:caption: Event function wrapper
function (seamless) {
- this.addEventListener(EVENT_NAME, function(event) {
+ this.addEventListener(EVENT_NAME, (event) => {
// JavaScript code
});
}
@@ -118,19 +126,18 @@ You can inject parameters into the event handler by using annotations in the fun
The injectable parameters are always passed as keyword arguments. |br|
Event functions can not have positional-only arguments.
-For example, you can access the socket ID by adding a parameter to the event handler function
-with the ``SocketID`` type from the ``seamless.core`` module.
-
-The socket id can be used to send messages to the client that triggered the event.
+For example, you can access the client ID by adding a parameter to the event handler function
+with the ``ClientID`` type from the ``seamless.extra.transports`` module.
.. code-block:: python
:caption: Accessing the socket ID
- from seamless.core import SocketID
+ from seamless.extra.transports import ClientID
- def on_event(self, event_data: SubmitEvent, socket_id: SocketID):
- print(socket_id)
+ def on_event(self, event_data: SubmitEvent, cid: ClientID):
+ print("Client ID:", cid)
In the standard context, the following injectable parameters are available:
-- **Socket ID**: The unique ID of the socket that triggered the event. Generated by socket.io (`More `_).
\ No newline at end of file
+- **Client ID**: The unique ID of the client that triggered the event.
+- **Context**: The current :ref:`Seamless context `.
\ No newline at end of file
diff --git a/docs/4-styling/1-style-object.rst b/docs/user-guide/4-styling/1-style-object.rst
similarity index 85%
rename from docs/4-styling/1-style-object.rst
rename to docs/user-guide/4-styling/1-style-object.rst
index b139084..61889ab 100644
--- a/docs/4-styling/1-style-object.rst
+++ b/docs/user-guide/4-styling/1-style-object.rst
@@ -43,3 +43,9 @@ This will create an inline style with the properties from the style object.
:caption: Style object output
Hello, world!
+
+Properties
+##########
+
+The ``StyleObject`` class has all the properties from the CSS specification.
+The only difference is that the properties are written in snake_case instead of kebab-case.
\ No newline at end of file
diff --git a/docs/4-styling/2-css-modules.rst b/docs/user-guide/4-styling/2-css-modules.rst
similarity index 61%
rename from docs/4-styling/2-css-modules.rst
rename to docs/user-guide/4-styling/2-css-modules.rst
index 25d5674..fbbc8ff 100644
--- a/docs/4-styling/2-css-modules.rst
+++ b/docs/user-guide/4-styling/2-css-modules.rst
@@ -10,7 +10,7 @@ It's a way to write modular CSS that won't conflict with other styles in your ap
Usage
#####
-To use CSS Modules in a component, import the ``CSS`` object from the ``seamless.styling`` module.
+To use CSS Modules in a component, import the ``CSS`` class from the ``seamless.styling`` module.
Then, use the ``CSS`` to import your css files.
.. code-block:: css
@@ -34,7 +34,7 @@ Then, use the ``CSS`` to import your css files.
class MyComponent(Component):
def render(self, css):
- return Div(class_name=styles.card)(
+ return Div(classes=styles.card)(
"Hello, world!"
)
@@ -42,3 +42,20 @@ This will generate a class name that is unique to this css file, and apply the s
This way, you can be sure that your styles won't conflict with other styles in your app.
When importing the same css file in multiple components, the class name will be the same across all components.
+
+CSS File Lookup
+###############
+
+When importing a css file, unless the path starts with a ``./``, Seamless will look for the css file in
+the root folder for CSS Modules.
+By default, the root folder for CSS Modules is the current working directory.
+You can change the root folder by calling the ``CSS.set_root_folder`` method.
+
+.. code-block:: python
+ :caption: Changing the root folder for CSS Modules
+
+ from seamless.styling import CSS
+
+ CSS.set_root_folder("./styles")
+
+ styles = CSS.module("card.css")
diff --git a/docs/4-styling/index.rst b/docs/user-guide/4-styling/index.rst
similarity index 100%
rename from docs/4-styling/index.rst
rename to docs/user-guide/4-styling/index.rst
diff --git a/docs/5-advanced/1-javascript.rst b/docs/user-guide/5-advanced/1-javascript.rst
similarity index 100%
rename from docs/5-advanced/1-javascript.rst
rename to docs/user-guide/5-advanced/1-javascript.rst
diff --git a/docs/5-advanced/2-empty.rst b/docs/user-guide/5-advanced/2-empty.rst
similarity index 100%
rename from docs/5-advanced/2-empty.rst
rename to docs/user-guide/5-advanced/2-empty.rst
diff --git a/docs/5-advanced/3-context.rst b/docs/user-guide/5-advanced/3-context.rst
similarity index 84%
rename from docs/5-advanced/3-context.rst
rename to docs/user-guide/5-advanced/3-context.rst
index 97f48ec..0c19f08 100644
--- a/docs/5-advanced/3-context.rst
+++ b/docs/user-guide/5-advanced/3-context.rst
@@ -8,16 +8,26 @@ Context is an advanced feature of Seamless that allows you to customize the rend
The default context is created using the ``Context.standard`` method and has the following features in order:
+- **SocketIO Feature**: This feature allows you to connect between the client and server using SocketIO.
+
+ .. code-block:: python
+
+ from seamless.context import Context
+ from seamless.extra.state import SocketIOTransport
+
+ context = Context()
+ context.add_feature(SocketIOTransport)
+
- **Component Repository**: A repository for storing components - this handles the ``component`` event and allows
you to store and retrieve components after the initial render.
.. code-block:: python
from seamless.context import Context
- from seamless.extra.components import init_components
+ from seamless.extra.components import ComponentsFeature
context = Context()
- context.add_feature(init_components)
+ context.add_feature(ComponentsFeature)
- **Events Feature**: This feature allows you to connect between HTML events and Python functions.
@@ -34,14 +44,14 @@ The default context is created using the ``Context.standard`` method and has the
.. code-block:: python
from seamless.context import Context
- from seamless.extra.state import init_state
+ from seamless.extra.state import StateFeature
context = Context()
- context.add_feature(init_state)
+ context.add_feature(StateFeature)
The standard context also comes with the following :ref:`property transformers` in order:
-- **Class Transformer**: Changes the ``class_name`` property key to ``class`` and converts
+- **Class Transformer**: Changes the ``classes`` property key to ``class`` and converts
the value to a string if it is a list.
.. code-block:: python
@@ -52,7 +62,7 @@ The standard context also comes with the following :ref:`property transformers
` except for ``class_name``.
+- **Simple Transformer**: Converts the properties in :ref:`this list` except for ``classes``.
.. code-block:: python
@@ -114,4 +124,4 @@ The standard context also comes with the following :ref:`property transformers