diff --git a/CHANGELOG.md b/CHANGELOG.md index 439508e..8f62237 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,106 +19,121 @@ Don't forget to remove deprecated code on each major release! ## [Unreleased] +### Added + +- Support for ReactPy v2.x (beta). The initial URL is now sourced from the ReactPy executor (`use_connection().location`) instead of a JS-side `popstate` effect, removing a redundant network round-trip on first load. + ### Changed -- Bump required ReactPy version to `2.x` +- **Breaking:** Bumped minimum ReactPy version to `2.0.0b12`. +- **Breaking:** Migrated to Hatch `src/` layout — the package now lives at `src/reactpy_router/` (previously `reactpy_router/` at the repository root). +- **Breaking:** JavaScript build pipeline switched from `Rollup` (CommonJS) to `Bun` + `TypeScript`. The prebuilt bundle ships at `src/reactpy_router/static/bundle.js`. +- **Breaking:** Top-level re-exports trimmed. `Route`, `MatchedRoute`, `CompiledRoute`, `RouteState`, `ConversionInfo`, `ConverterMapping`, `Resolver`, and `Router` are no longer importable from `reactpy_router`. Import them from `reactpy_router.types` instead. +- **Breaking:** The `link` component no longer accepts the legacy underscore `class_name` attribute — use `className` only. +- **Breaking:** The internal `History` JavaScript callback was renamed from `onHistoryChangeCallback` to `onHistoryPreviousCallback` to reflect that it now fires only on browser history navigation events. +- `@reactpy/client` bumped to `^1.2.0`. +- The `uuid` route converter is now case-insensitive (previously matched only lowercase hex). + +### Removed + +- `StarletteResolver` and the `simple.py` / `core.py` modules that accompanied it (these were already removed in 2.0.0 in favor of `ReactPyResolver`; this release also drops the old `core.py`/`simple.py` modules). ## [2.0.0] - 2025-06-14 ### Added -- Support for custom routers. +- Support for custom routers. ### Changed -- Set maximum ReactPy version to `<2.0.0`. -- Set minimum ReactPy version to `1.1.0`. -- `link` element now calculates URL changes using the client. -- Refactoring related to `reactpy>=1.1.0` changes. -- Changed ReactPy-Router's method of waiting for the initial URL to be deterministic. -- Rename `StarletteResolver` to `ReactPyResolver`. +- Set maximum ReactPy version to `<2.0.0`. +- Set minimum ReactPy version to `1.1.0`. +- `link` element now calculates URL changes using the client. +- Refactoring related to `reactpy>=1.1.0` changes. +- Changed ReactPy-Router's method of waiting for the initial URL to be deterministic. +- Rename `StarletteResolver` to `ReactPyResolver`. ### Removed -- `StarletteResolver` is removed in favor of `ReactPyResolver`. +- `StarletteResolver` is removed in favor of `ReactPyResolver`. ### Fixed -- Fixed bug where `link` element sometimes would sometimes not retrieve the correct `href` attribute. +- Fixed bug where `link` element sometimes would sometimes not retrieve the correct `href` attribute. ## [1.0.3] - 2024-11-21 ### Fixed -- Fix behavior where the page would be rendered twice on initial load +- Fix behavior where the page would be rendered twice on initial load ## [1.0.2] - 2024-10-24 ### Fixed -- Fix python `wheel` missing `bundle.js` file. +- Fix python `wheel` missing `bundle.js` file. ## [1.0.1] - 2024-10-24 ### Changed -- JavaScript bundle is now created using [`Bun`](https://bun.sh/). -- Python package is now built using [`Hatch`](https://hatch.pypa.io/). +- JavaScript bundle is now created using [`Bun`](https://bun.sh/). +- Python package is now built using [`Hatch`](https://hatch.pypa.io/). ## [1.0.0] - 2024-10-18 ### Changed -- Rename `use_query` to `use_search_params`. -- Rename `simple.router` to `browser_router`. -- Rename `SimpleResolver` to `StarletteResolver`. -- Rename `CONVERSION_TYPES` to `CONVERTERS`. -- Change "Match Any" syntax from a star `*` to `{name:any}`. -- Rewrite `reactpy_router.link` to be a server-side component. -- Simplified top-level exports that are available within `reactpy_router.*`. +- Rename `use_query` to `use_search_params`. +- Rename `simple.router` to `browser_router`. +- Rename `SimpleResolver` to `StarletteResolver`. +- Rename `CONVERSION_TYPES` to `CONVERTERS`. +- Change "Match Any" syntax from a star `*` to `{name:any}`. +- Rewrite `reactpy_router.link` to be a server-side component. +- Simplified top-level exports that are available within `reactpy_router.*`. ### Added -- Add debug log message for when there are no router matches. -- Add slug as a supported type. -- Add `reactpy_router.navigate` component that will force the client to navigate to a new URL (when rendered). -- New error for ReactPy router elements being used outside router context. -- Configurable/inheritable `Resolver` base class. +- Add debug log message for when there are no router matches. +- Add slug as a supported type. +- Add `reactpy_router.navigate` component that will force the client to navigate to a new URL (when rendered). +- New error for ReactPy router elements being used outside router context. +- Configurable/inheritable `Resolver` base class. ### Fixed -- Fix bug where changing routes could cause render failure due to key identity. -- Fix bug where "Match Any" pattern wouldn't work when used in complex or nested paths. -- Fix bug where `link` elements could not have `@component` type children. -- Fix bug where the ReactPy would not detect the current URL after a reconnection. -- Fix bug where `ctrl` + `click` on a `link` element would not open in a new tab. -- Fix test suite on Windows machines. +- Fix bug where changing routes could cause render failure due to key identity. +- Fix bug where "Match Any" pattern wouldn't work when used in complex or nested paths. +- Fix bug where `link` elements could not have `@component` type children. +- Fix bug where the ReactPy would not detect the current URL after a reconnection. +- Fix bug where `ctrl` + `click` on a `link` element would not open in a new tab. +- Fix test suite on Windows machines. ## [0.1.1] - 2023-12-13 ### Fixed -- Fixed relative navigation. +- Fixed relative navigation. ## [0.1.0] - 2023-06-16 ### Added -- Automatically handle client-side history changes. +- Automatically handle client-side history changes. ## [0.0.1] - 2023-05-10 ### Added -- Add robust lint/testing. -- Upgrade `reactpy`. -- More robust routing with `starlette`. -- Initial draft of router compiler. +- Add robust lint/testing. +- Upgrade `reactpy`. +- More robust routing with `starlette`. +- Initial draft of router compiler. ### Changed -- Rename `configure` to `create_router`. -- Rename from `idom-router` to `reactpy-router`. +- Rename `configure` to `create_router`. +- Rename from `idom-router` to `reactpy-router`. [Unreleased]: https://github.com/reactive-python/reactpy-router/compare/2.0.0...HEAD [2.0.0]: https://github.com/reactive-python/reactpy-router/compare/1.0.3...2.0.0 diff --git a/README.md b/README.md index 24ad465..ee494ba 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # ReactPy Router

- - + + diff --git a/docs/src/about/contributing.md b/docs/src/about/contributing.md index fc4a234..fa1816d 100644 --- a/docs/src/about/contributing.md +++ b/docs/src/about/contributing.md @@ -3,7 +3,7 @@ If you plan to make code changes to this repository, you will need to install the following dependencies first: - [Git](https://git-scm.com/downloads) -- [Python 3.9+](https://www.python.org/downloads/) +- [Python 3.11+](https://www.python.org/downloads/) - [Hatch](https://hatch.pypa.io/latest/) - [Bun](https://bun.sh/) @@ -41,6 +41,7 @@ By utilizing `hatch`, the following commands are available to manage the develop | `hatch fmt --check` | Run all linters and formatters, but do not save fixes to the disk | | `hatch fmt --linter` | Run only linters | | `hatch fmt --formatter` | Run only formatters | +| `hatch run javascript:build` | Manually re-build the JavaScript bundle | | `hatch run javascript:check` | Run the JavaScript linter/formatter | | `hatch run javascript:fix` | Run the JavaScript linter/formatter and write fixes to disk | | `hatch run python:type_check` | Run the Python type checker | diff --git a/docs/src/assets/css/admonition.css b/docs/src/assets/css/admonition.css index 8b3f06e..24b33a3 100644 --- a/docs/src/assets/css/admonition.css +++ b/docs/src/assets/css/admonition.css @@ -1,160 +1,160 @@ [data-md-color-scheme="slate"] { - --admonition-border-color: transparent; - --admonition-expanded-border-color: rgba(255, 255, 255, 0.1); - --note-bg-color: rgba(43, 110, 98, 0.2); - --terminal-bg-color: #0c0c0c; - --terminal-title-bg-color: #000; - --deep-dive-bg-color: rgba(43, 52, 145, 0.2); - --you-will-learn-bg-color: #353a45; - --pitfall-bg-color: rgba(182, 87, 0, 0.2); + --admonition-border-color: transparent; + --admonition-expanded-border-color: rgba(255, 255, 255, 0.1); + --note-bg-color: rgba(43, 110, 98, 0.2); + --terminal-bg-color: #0c0c0c; + --terminal-title-bg-color: #000; + --deep-dive-bg-color: rgba(43, 52, 145, 0.2); + --you-will-learn-bg-color: #353a45; + --pitfall-bg-color: rgba(182, 87, 0, 0.2); } [data-md-color-scheme="default"] { - --admonition-border-color: rgba(0, 0, 0, 0.08); - --admonition-expanded-border-color: var(--admonition-border-color); - --note-bg-color: rgb(244, 251, 249); - --terminal-bg-color: rgb(64, 71, 86); - --terminal-title-bg-color: rgb(35, 39, 47); - --deep-dive-bg-color: rgb(243, 244, 253); - --you-will-learn-bg-color: rgb(246, 247, 249); - --pitfall-bg-color: rgb(254, 245, 231); + --admonition-border-color: rgba(0, 0, 0, 0.08); + --admonition-expanded-border-color: var(--admonition-border-color); + --note-bg-color: rgb(244, 251, 249); + --terminal-bg-color: rgb(64, 71, 86); + --terminal-title-bg-color: rgb(35, 39, 47); + --deep-dive-bg-color: rgb(243, 244, 253); + --you-will-learn-bg-color: rgb(246, 247, 249); + --pitfall-bg-color: rgb(254, 245, 231); } .md-typeset details, .md-typeset .admonition { - border-color: var(--admonition-border-color) !important; - box-shadow: none; + border-color: var(--admonition-border-color) !important; + box-shadow: none; } .md-typeset :is(.admonition, details) { - margin: 0.55em 0; + margin: 0.55em 0; } .md-typeset .admonition { - font-size: 0.7rem; + font-size: 0.7rem; } .md-typeset .admonition:focus-within, .md-typeset details:focus-within { - box-shadow: none !important; + box-shadow: none !important; } .md-typeset details[open] { - border-color: var(--admonition-expanded-border-color) !important; + border-color: var(--admonition-expanded-border-color) !important; } -/* -Admonition: "summary" +/* +Admonition: "summary" React Name: "You will learn" */ .md-typeset .admonition.summary { - background: var(--you-will-learn-bg-color); - padding: 0.8rem 1.4rem; - border-radius: 0.8rem; + background: var(--you-will-learn-bg-color); + padding: 0.8rem 1.4rem; + border-radius: 0.8rem; } .md-typeset .summary .admonition-title { - font-size: 1rem; - background: transparent; - padding-left: 0.6rem; - padding-bottom: 0; + font-size: 1rem; + background: transparent; + padding-left: 0.6rem; + padding-bottom: 0; } .md-typeset .summary .admonition-title:before { - display: none; + display: none; } .md-typeset .admonition.summary { - border-color: #ffffff17 !important; + border-color: #ffffff17 !important; } -/* -Admonition: "abstract" +/* +Admonition: "abstract" React Name: "Note" */ .md-typeset .admonition.abstract { - background: var(--note-bg-color); - padding: 0.8rem 1.4rem; - border-radius: 0.8rem; + background: var(--note-bg-color); + padding: 0.8rem 1.4rem; + border-radius: 0.8rem; } .md-typeset .abstract .admonition-title { - font-size: 1rem; - background: transparent; - padding-bottom: 0; - color: rgb(68, 172, 153); + font-size: 1rem; + background: transparent; + padding-bottom: 0; + color: rgb(68, 172, 153); } .md-typeset .abstract .admonition-title:before { - font-size: 1.1rem; - background: rgb(68, 172, 153); + font-size: 1.1rem; + background: rgb(68, 172, 153); } -/* -Admonition: "warning" +/* +Admonition: "warning" React Name: "Pitfall" */ .md-typeset .admonition.warning { - background: var(--pitfall-bg-color); - padding: 0.8rem 1.4rem; - border-radius: 0.8rem; + background: var(--pitfall-bg-color); + padding: 0.8rem 1.4rem; + border-radius: 0.8rem; } .md-typeset .warning .admonition-title { - font-size: 1rem; - background: transparent; - padding-bottom: 0; - color: rgb(219, 125, 39); + font-size: 1rem; + background: transparent; + padding-bottom: 0; + color: rgb(219, 125, 39); } .md-typeset .warning .admonition-title:before { - font-size: 1.1rem; - background: rgb(219, 125, 39); + font-size: 1.1rem; + background: rgb(219, 125, 39); } -/* -Admonition: "info" +/* +Admonition: "info" React Name: "Deep Dive" */ .md-typeset .admonition.info { - background: var(--deep-dive-bg-color); - padding: 0.8rem 1.4rem; - border-radius: 0.8rem; + background: var(--deep-dive-bg-color); + padding: 0.8rem 1.4rem; + border-radius: 0.8rem; } .md-typeset .info .admonition-title { - font-size: 1rem; - background: transparent; - padding-bottom: 0; - color: rgb(136, 145, 236); + font-size: 1rem; + background: transparent; + padding-bottom: 0; + color: rgb(136, 145, 236); } .md-typeset .info .admonition-title:before { - font-size: 1.1rem; - background: rgb(136, 145, 236); + font-size: 1.1rem; + background: rgb(136, 145, 236); } -/* -Admonition: "example" +/* +Admonition: "example" React Name: "Terminal" */ .md-typeset .admonition.example { - background: var(--terminal-bg-color); - border-radius: 0.4rem; - overflow: hidden; - border: none; + background: var(--terminal-bg-color); + border-radius: 0.4rem; + overflow: hidden; + border: none; } .md-typeset .example .admonition-title { - background: var(--terminal-title-bg-color); - color: rgb(246, 247, 249); + background: var(--terminal-title-bg-color); + color: rgb(246, 247, 249); } .md-typeset .example .admonition-title:before { - background: rgb(246, 247, 249); + background: rgb(246, 247, 249); } .md-typeset .admonition.example code { - background: transparent; - color: #fff; - box-shadow: none; + background: transparent; + color: #fff; + box-shadow: none; } diff --git a/docs/src/assets/css/banner.css b/docs/src/assets/css/banner.css index 3739a73..b6d5a90 100644 --- a/docs/src/assets/css/banner.css +++ b/docs/src/assets/css/banner.css @@ -1,15 +1,15 @@ body[data-md-color-scheme="slate"] { - --md-banner-bg-color: rgb(55, 81, 78); - --md-banner-font-color: #fff; + --md-banner-bg-color: rgb(55, 81, 78); + --md-banner-font-color: #fff; } body[data-md-color-scheme="default"] { - --md-banner-bg-color: #ff9; - --md-banner-font-color: #000; + --md-banner-bg-color: #ff9; + --md-banner-font-color: #000; } .md-banner--warning { - background-color: var(--md-banner-bg-color); - color: var(--md-banner-font-color); - text-align: center; + background-color: var(--md-banner-bg-color); + color: var(--md-banner-font-color); + text-align: center; } diff --git a/docs/src/assets/css/button.css b/docs/src/assets/css/button.css index 8f71391..2563f6b 100644 --- a/docs/src/assets/css/button.css +++ b/docs/src/assets/css/button.css @@ -1,41 +1,44 @@ [data-md-color-scheme="slate"] { - --md-button-font-color: #fff; - --md-button-border-color: #404756; + --md-button-font-color: #fff; + --md-button-border-color: #404756; } [data-md-color-scheme="default"] { - --md-button-font-color: #000; - --md-button-border-color: #8d8d8d; + --md-button-font-color: #000; + --md-button-border-color: #8d8d8d; } .md-typeset .md-button { - border-width: 1px; - border-color: var(--md-button-border-color); - border-radius: 9999px; - color: var(--md-button-font-color); - transition: color 125ms, background 125ms, border-color 125ms, - transform 125ms; + border-width: 1px; + border-color: var(--md-button-border-color); + border-radius: 9999px; + color: var(--md-button-font-color); + transition: + color 125ms, + background 125ms, + border-color 125ms, + transform 125ms; } .md-typeset .md-button:focus, .md-typeset .md-button:hover { - border-color: var(--md-button-border-color); - color: var(--md-button-font-color); - background: rgba(78, 87, 105, 0.05); + border-color: var(--md-button-border-color); + color: var(--md-button-font-color); + background: rgba(78, 87, 105, 0.05); } .md-typeset .md-button.md-button--primary { - color: #fff; - border-color: transparent; - background: var(--reactpy-color-dark); + color: #fff; + border-color: transparent; + background: var(--reactpy-color-dark); } .md-typeset .md-button.md-button--primary:focus, .md-typeset .md-button.md-button--primary:hover { - border-color: transparent; - background: var(--reactpy-color-darker); + border-color: transparent; + background: var(--reactpy-color-darker); } .md-typeset .md-button:focus { - transform: scale(0.98); + transform: scale(0.98); } diff --git a/docs/src/assets/css/code.css b/docs/src/assets/css/code.css index c546549..706344e 100644 --- a/docs/src/assets/css/code.css +++ b/docs/src/assets/css/code.css @@ -1,82 +1,79 @@ :root { - --code-max-height: 17.25rem; - --md-code-backdrop: rgba(0, 0, 0, 0) 0px 0px 0px 0px, - rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.03) 0px 0.8px 2px 0px, - rgba(0, 0, 0, 0.047) 0px 2.7px 6.7px 0px, - rgba(0, 0, 0, 0.08) 0px 12px 30px 0px; + --code-max-height: 17.25rem; + --md-code-backdrop: rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0) 0px 0px 0px 0px, rgba(0, 0, 0, 0.03) 0px 0.8px 2px 0px, rgba(0, 0, 0, 0.047) 0px 2.7px 6.7px 0px, rgba(0, 0, 0, 0.08) 0px 12px 30px 0px; } [data-md-color-scheme="slate"] { - --md-code-hl-color: #ffffcf1c; - --md-code-bg-color: #16181d; - --md-code-hl-comment-color: hsla(var(--md-hue), 75%, 90%, 0.43); - --code-tab-color: rgb(52, 58, 70); - --md-code-hl-name-color: #aadafc; - --md-code-hl-string-color: hsl(21 49% 63% / 1); - --md-code-hl-keyword-color: hsl(289.67deg 35% 60%); - --md-code-hl-constant-color: hsl(213.91deg 68% 61%); - --md-code-hl-number-color: #bfd9ab; - --func-and-decorator-color: #dcdcae; - --module-import-color: #60c4ac; + --md-code-hl-color: #ffffcf1c; + --md-code-bg-color: #16181d; + --md-code-hl-comment-color: hsla(var(--md-hue), 75%, 90%, 0.43); + --code-tab-color: rgb(52, 58, 70); + --md-code-hl-name-color: #aadafc; + --md-code-hl-string-color: hsl(21 49% 63% / 1); + --md-code-hl-keyword-color: hsl(289.67deg 35% 60%); + --md-code-hl-constant-color: hsl(213.91deg 68% 61%); + --md-code-hl-number-color: #bfd9ab; + --func-and-decorator-color: #dcdcae; + --module-import-color: #60c4ac; } [data-md-color-scheme="default"] { - --md-code-hl-color: #ffffcf1c; - --md-code-bg-color: rgba(208, 211, 220, 0.4); - --md-code-fg-color: rgb(64, 71, 86); - --code-tab-color: #fff; - --func-and-decorator-color: var(--md-code-hl-function-color); - --module-import-color: #e153e5; + --md-code-hl-color: #ffffcf1c; + --md-code-bg-color: rgba(208, 211, 220, 0.4); + --md-code-fg-color: rgb(64, 71, 86); + --code-tab-color: #fff; + --func-and-decorator-color: var(--md-code-hl-function-color); + --module-import-color: #e153e5; } [data-md-color-scheme="default"] .md-typeset .highlight > pre > code, [data-md-color-scheme="default"] .md-typeset .highlight > table.highlighttable { - --md-code-bg-color: #fff; + --md-code-bg-color: #fff; } /* All code blocks */ .md-typeset pre > code { - max-height: var(--code-max-height); + max-height: var(--code-max-height); } /* Code blocks with no line number */ .md-typeset .highlight > pre > code { - border-radius: 16px; - max-height: var(--code-max-height); - box-shadow: var(--md-code-backdrop); + border-radius: 16px; + max-height: var(--code-max-height); + box-shadow: var(--md-code-backdrop); } /* Code blocks with line numbers */ .md-typeset .highlighttable .linenos { - max-height: var(--code-max-height); - overflow: hidden; + max-height: var(--code-max-height); + overflow: hidden; } .md-typeset .highlighttable { - box-shadow: var(--md-code-backdrop); - border-radius: 8px; - overflow: hidden; + box-shadow: var(--md-code-backdrop); + border-radius: 8px; + overflow: hidden; } /* Tabbed code blocks */ .md-typeset .tabbed-set { - box-shadow: var(--md-code-backdrop); - border-radius: 8px; - overflow: hidden; - border: 1px solid var(--md-default-fg-color--lightest); + box-shadow: var(--md-code-backdrop); + border-radius: 8px; + overflow: hidden; + border: 1px solid var(--md-default-fg-color--lightest); } .md-typeset .tabbed-set .tabbed-block { - overflow: hidden; + overflow: hidden; } .js .md-typeset .tabbed-set .tabbed-labels { - background: var(--code-tab-color); - margin: 0; - padding-left: 0.8rem; + background: var(--code-tab-color); + margin: 0; + padding-left: 0.8rem; } .md-typeset .tabbed-set .tabbed-labels > label { - font-weight: 400; - font-size: 0.7rem; - padding-top: 0.55em; - padding-bottom: 0.35em; + font-weight: 400; + font-size: 0.7rem; + padding-top: 0.55em; + padding-bottom: 0.35em; } .md-typeset .tabbed-set .highlighttable { - border-radius: 0; + border-radius: 0; } /* Code hightlighting colors */ @@ -86,26 +83,31 @@ .highlight .ne, .highlight .nn, .highlight .nv { - color: var(--module-import-color); + color: var(--module-import-color); } /* Function def name and decorator */ .highlight .nd, .highlight .nf { - color: var(--func-and-decorator-color); + color: var(--func-and-decorator-color); } /* None type */ .highlight .kc { - color: var(--md-code-hl-constant-color); + color: var(--md-code-hl-constant-color); } /* Keywords such as def and return */ .highlight .k { - color: var(--md-code-hl-constant-color); + color: var(--md-code-hl-constant-color); } /* HTML tags */ .highlight .nt { - color: var(--md-code-hl-constant-color); + color: var(--md-code-hl-constant-color); +} + +/* Copy text button */ +.md-code__nav { + background: transparent; } diff --git a/docs/src/assets/css/footer.css b/docs/src/assets/css/footer.css index b340828..2f9e268 100644 --- a/docs/src/assets/css/footer.css +++ b/docs/src/assets/css/footer.css @@ -1,33 +1,33 @@ [data-md-color-scheme="slate"] { - --md-footer-bg-color: var(--md-default-bg-color); - --md-footer-bg-color--dark: var(--md-default-bg-color); - --md-footer-border-color: var(--md-header-border-color); + --md-footer-bg-color: var(--md-default-bg-color); + --md-footer-bg-color--dark: var(--md-default-bg-color); + --md-footer-border-color: var(--md-header-border-color); } [data-md-color-scheme="default"] { - --md-footer-fg-color: var(--md-typeset-color); - --md-footer-fg-color--light: var(--md-typeset-color); - --md-footer-bg-color: var(--md-default-bg-color); - --md-footer-bg-color--dark: var(--md-default-bg-color); - --md-footer-border-color: var(--md-header-border-color); + --md-footer-fg-color: var(--md-typeset-color); + --md-footer-fg-color--light: var(--md-typeset-color); + --md-footer-bg-color: var(--md-default-bg-color); + --md-footer-bg-color--dark: var(--md-default-bg-color); + --md-footer-border-color: var(--md-header-border-color); } .md-footer { - border-top: 1px solid var(--md-footer-border-color); + border-top: 1px solid var(--md-footer-border-color); } .md-copyright { - width: 100%; + width: 100%; } .md-copyright__highlight { - width: 100%; + width: 100%; } .legal-footer-right { - float: right; + float: right; } .md-copyright__highlight div { - display: inline; + display: inline; } diff --git a/docs/src/assets/css/home.css b/docs/src/assets/css/home.css index c72e709..b13eec8 100644 --- a/docs/src/assets/css/home.css +++ b/docs/src/assets/css/home.css @@ -1,335 +1,297 @@ img.home-logo { - height: 120px; + height: 120px; } .home .row { - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - padding: 6rem 0.8rem; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + padding: 6rem 0.8rem; } .home .row:not(.first, .stripe) { - background: var(--row-bg-color); + background: var(--row-bg-color); } .home .row.stripe { - background: var(--row-stripe-bg-color); - border: 0 solid var(--stripe-border-color); - border-top-width: 1px; - border-bottom-width: 1px; + background: var(--row-stripe-bg-color); + border: 0 solid var(--stripe-border-color); + border-top-width: 1px; + border-bottom-width: 1px; } .home .row.first { - text-align: center; + text-align: center; } .home .row h1 { - max-width: 28rem; - line-height: 1.15; - font-weight: 500; - margin-bottom: 0.55rem; - margin-top: -1rem; + max-width: 28rem; + line-height: 1.15; + font-weight: 500; + margin-bottom: 0.55rem; + margin-top: -1rem; } .home .row.first h1 { - margin-top: 0.55rem; - margin-bottom: -0.75rem; + margin-top: 0.55rem; + margin-bottom: -0.75rem; } .home .row > p { - max-width: 35rem; - line-height: 1.5; - font-weight: 400; + max-width: 35rem; + line-height: 1.5; + font-weight: 400; } .home .row.first > p { - font-size: 32px; - font-weight: 500; + font-size: 32px; + font-weight: 500; } /* Code blocks */ .home .row .tabbed-set { - background: var(--home-tabbed-set-bg-color); - margin: 0; + background: var(--home-tabbed-set-bg-color); + margin: 0; } .home .row .tabbed-content { - padding: 20px 18px; - overflow-x: auto; + padding: 20px 18px; + overflow-x: auto; } .home .row .tabbed-content img { - user-select: none; - -moz-user-select: none; - -webkit-user-drag: none; - -webkit-user-select: none; - -ms-user-select: none; - max-width: 580px; + user-select: none; + -moz-user-select: none; + -webkit-user-drag: none; + -webkit-user-select: none; + -ms-user-select: none; + max-width: 580px; } .home .row .tabbed-content { - -webkit-filter: var(--code-block-filter); - filter: var(--code-block-filter); + -webkit-filter: var(--code-block-filter); + filter: var(--code-block-filter); } /* Code examples */ .home .example-container { - background: radial-gradient( - circle at 0% 100%, - rgb(41 84 147 / 11%) 0%, - rgb(22 89 189 / 4%) 70%, - rgb(48 99 175 / 0%) 80% - ), - radial-gradient( - circle at 100% 100%, - rgb(24 87 45 / 55%) 0%, - rgb(29 61 12 / 4%) 70%, - rgb(94 116 93 / 0%) 80% - ), - radial-gradient( - circle at 100% 0%, - rgba(54, 66, 84, 0.55) 0%, - rgb(102 111 125 / 4%) 70%, - rgba(54, 66, 84, 0) 80% - ), - radial-gradient( - circle at 0% 0%, - rgba(91, 114, 135, 0.55) 0%, - rgb(45 111 171 / 4%) 70%, - rgb(5 82 153 / 0%) 80% - ), - rgb(0, 0, 0) center center/cover no-repeat fixed; - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - align-items: center; - border-radius: 16px; - margin: 30px 0; - max-width: 100%; - grid-column-gap: 20px; - padding-left: 20px; - padding-right: 20px; + background: + radial-gradient(circle at 0% 100%, rgb(41 84 147 / 11%) 0%, rgb(22 89 189 / 4%) 70%, rgb(48 99 175 / 0%) 80%), + radial-gradient(circle at 100% 100%, rgb(24 87 45 / 55%) 0%, rgb(29 61 12 / 4%) 70%, rgb(94 116 93 / 0%) 80%), + radial-gradient(circle at 100% 0%, rgba(54, 66, 84, 0.55) 0%, rgb(102 111 125 / 4%) 70%, rgba(54, 66, 84, 0) 80%), + radial-gradient(circle at 0% 0%, rgba(91, 114, 135, 0.55) 0%, rgb(45 111 171 / 4%) 70%, rgb(5 82 153 / 0%) 80%), + rgb(0, 0, 0) center center/cover no-repeat fixed; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: center; + border-radius: 16px; + margin: 30px 0; + max-width: 100%; + grid-column-gap: 20px; + padding-left: 20px; + padding-right: 20px; } .home .demo .white-bg { - background: #fff; - border-radius: 16px; - display: flex; - flex-direction: column; - max-width: 590px; - min-width: -webkit-min-content; - min-width: -moz-min-content; - min-width: min-content; - row-gap: 1rem; - padding: 1rem; + background: #fff; + border-radius: 16px; + display: flex; + flex-direction: column; + max-width: 590px; + min-width: -webkit-min-content; + min-width: -moz-min-content; + min-width: min-content; + row-gap: 1rem; + padding: 1rem; } .home .demo .vid-row { - display: flex; - flex-direction: row; - -moz-column-gap: 12px; - column-gap: 12px; + display: flex; + flex-direction: row; + -moz-column-gap: 12px; + column-gap: 12px; } .home .demo { - color: #000; + color: #000; } .home .demo .vid-thumbnail { - background: radial-gradient( - circle at 0% 100%, - rgb(41 84 147 / 55%) 0%, - rgb(22 89 189 / 4%) 70%, - rgb(48 99 175 / 0%) 80% - ), - radial-gradient( - circle at 100% 100%, - rgb(24 63 87 / 55%) 0%, - rgb(29 61 12 / 4%) 70%, - rgb(94 116 93 / 0%) 80% - ), - radial-gradient( - circle at 100% 0%, - rgba(54, 66, 84, 0.55) 0%, - rgb(102 111 125 / 4%) 70%, - rgba(54, 66, 84, 0) 80% - ), - radial-gradient( - circle at 0% 0%, - rgba(91, 114, 135, 0.55) 0%, - rgb(45 111 171 / 4%) 70%, - rgb(5 82 153 / 0%) 80% - ), - rgb(0, 0, 0) center center/cover no-repeat fixed; - width: 9rem; - aspect-ratio: 16 / 9; - border-radius: 8px; - display: flex; - justify-content: center; - align-items: center; + background: + radial-gradient(circle at 0% 100%, rgb(41 84 147 / 55%) 0%, rgb(22 89 189 / 4%) 70%, rgb(48 99 175 / 0%) 80%), + radial-gradient(circle at 100% 100%, rgb(24 63 87 / 55%) 0%, rgb(29 61 12 / 4%) 70%, rgb(94 116 93 / 0%) 80%), + radial-gradient(circle at 100% 0%, rgba(54, 66, 84, 0.55) 0%, rgb(102 111 125 / 4%) 70%, rgba(54, 66, 84, 0) 80%), + radial-gradient(circle at 0% 0%, rgba(91, 114, 135, 0.55) 0%, rgb(45 111 171 / 4%) 70%, rgb(5 82 153 / 0%) 80%), + rgb(0, 0, 0) center center/cover no-repeat fixed; + width: 9rem; + aspect-ratio: 16 / 9; + border-radius: 8px; + display: flex; + justify-content: center; + align-items: center; } .home .demo .vid-text { - display: flex; - flex-direction: column; - justify-content: center; - align-items: flex-start; - width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + width: 100%; } .home .demo h2 { - font-size: 18px; - line-height: 1.375; - margin: 0; - text-align: left; - font-weight: 700; + font-size: 18px; + line-height: 1.375; + margin: 0; + text-align: left; + font-weight: 700; } .home .demo h3 { - font-size: 16px; - line-height: 1.25; - margin: 0; + font-size: 16px; + line-height: 1.25; + margin: 0; } .home .demo p { - font-size: 14px; - line-height: 1.375; - margin: 0; + font-size: 14px; + line-height: 1.375; + margin: 0; } .home .demo .browser-nav-url { - background: rgba(153, 161, 179, 0.2); - border-radius: 9999px; - font-size: 14px; - color: grey; - display: flex; - align-items: center; - justify-content: center; - -moz-column-gap: 5px; - column-gap: 5px; + background: rgba(153, 161, 179, 0.2); + border-radius: 9999px; + font-size: 14px; + color: grey; + display: flex; + align-items: center; + justify-content: center; + -moz-column-gap: 5px; + column-gap: 5px; } .home .demo .browser-navbar { - margin: -1rem; - margin-bottom: 0; - padding: 0.75rem 1rem; - border-bottom: 1px solid darkgrey; + margin: -1rem; + margin-bottom: 0; + padding: 0.75rem 1rem; + border-bottom: 1px solid darkgrey; } .home .demo .browser-viewport { - background: #fff; - border-radius: 16px; - display: flex; - flex-direction: column; - row-gap: 1rem; - height: 400px; - overflow-y: scroll; - margin: -1rem; - padding: 1rem; + background: #fff; + border-radius: 16px; + display: flex; + flex-direction: column; + row-gap: 1rem; + height: 400px; + overflow-y: scroll; + margin: -1rem; + padding: 1rem; } .home .demo .browser-viewport .search-header > h1 { - color: #000; - text-align: left; - font-size: 24px; - margin: 0; + color: #000; + text-align: left; + font-size: 24px; + margin: 0; } .home .demo .browser-viewport .search-header > p { - text-align: left; - font-size: 16px; - margin: 10px 0; + text-align: left; + font-size: 16px; + margin: 10px 0; } .home .demo .search-bar input { - width: 100%; - background: rgba(153, 161, 179, 0.2); - border-radius: 9999px; - padding-left: 40px; - padding-right: 40px; - height: 40px; - color: #000; + width: 100%; + background: rgba(153, 161, 179, 0.2); + border-radius: 9999px; + padding-left: 40px; + padding-right: 40px; + height: 40px; + color: #000; } .home .demo .search-bar svg { - height: 40px; - position: absolute; - transform: translateX(75%); + height: 40px; + position: absolute; + transform: translateX(75%); } .home .demo .search-bar { - position: relative; + position: relative; } /* Desktop Styling */ @media screen and (min-width: 60em) { - .home .row { - text-align: center; - } - .home .row > p { - font-size: 21px; - } - .home .row > h1 { - font-size: 52px; - } - .home .row .pop-left { - margin-left: -20px; - margin-right: 0; - margin-top: -20px; - margin-bottom: -20px; - } - .home .row .pop-right { - margin-left: 0px; - margin-right: 0px; - margin-top: -20px; - margin-bottom: -20px; - } + .home .row { + text-align: center; + } + .home .row > p { + font-size: 21px; + } + .home .row > h1 { + font-size: 52px; + } + .home .row .pop-left { + margin-left: -20px; + margin-right: 0; + margin-top: -20px; + margin-bottom: -20px; + } + .home .row .pop-right { + margin-left: 0px; + margin-right: 0px; + margin-top: -20px; + margin-bottom: -20px; + } } /* Mobile Styling */ @media screen and (max-width: 60em) { - .home .row { - padding: 4rem 0.8rem; - } - .home .row > h1, - .home .row > p { - padding-left: 1rem; - padding-right: 1rem; - } - .home .row.first { - padding-top: 2rem; - } - .home-btns { - width: 100%; - display: grid; - grid-gap: 0.5rem; - gap: 0.5rem; - } - .home .example-container { - display: flex; - flex-direction: column; - row-gap: 20px; - width: 100%; - justify-content: center; - border-radius: 0; - padding: 1rem 0; - } - .home .row { - padding-left: 0; - padding-right: 0; - } - .home .tabbed-set { - width: 100%; - border-radius: 0; - } - .home .demo { - width: 100%; - display: flex; - justify-content: center; - } - .home .demo > .white-bg { - width: 80%; - max-width: 80%; - } + .home .row { + padding: 4rem 0.8rem; + } + .home .row > h1, + .home .row > p { + padding-left: 1rem; + padding-right: 1rem; + } + .home .row.first { + padding-top: 2rem; + } + .home-btns { + width: 100%; + display: grid; + grid-gap: 0.5rem; + gap: 0.5rem; + } + .home .example-container { + display: flex; + flex-direction: column; + row-gap: 20px; + width: 100%; + justify-content: center; + border-radius: 0; + padding: 1rem 0; + } + .home .row { + padding-left: 0; + padding-right: 0; + } + .home .tabbed-set { + width: 100%; + border-radius: 0; + } + .home .demo { + width: 100%; + display: flex; + justify-content: center; + } + .home .demo > .white-bg { + width: 80%; + max-width: 80%; + } } diff --git a/docs/src/assets/css/main.css b/docs/src/assets/css/main.css index 6eefdf2..6ca94b1 100644 --- a/docs/src/assets/css/main.css +++ b/docs/src/assets/css/main.css @@ -1,85 +1,85 @@ /* Variable overrides */ :root { - --reactpy-color: #58b962; - --reactpy-color-dark: #42914a; - --reactpy-color-darker: #34743b; - --reactpy-color-opacity-10: rgba(88, 185, 98, 0.1); + --reactpy-color: #58b962; + --reactpy-color-dark: #42914a; + --reactpy-color-darker: #34743b; + --reactpy-color-opacity-10: rgba(88, 185, 98, 0.1); } [data-md-color-accent="red"] { - --md-primary-fg-color--light: var(--reactpy-color); - --md-primary-fg-color--dark: var(--reactpy-color-dark); + --md-primary-fg-color--light: var(--reactpy-color); + --md-primary-fg-color--dark: var(--reactpy-color-dark); } [data-md-color-scheme="slate"] { - --md-default-bg-color: rgb(35, 39, 47); - --md-default-bg-color--light: hsla(var(--md-hue), 15%, 16%, 0.54); - --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 16%, 0.26); - --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 16%, 0.07); - --md-primary-fg-color: var(--md-default-bg-color); - --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1); - --md-default-fg-color--light: #fff; - --md-typeset-a-color: var(--reactpy-color); - --md-accent-fg-color: var(--reactpy-color-dark); + --md-default-bg-color: rgb(35, 39, 47); + --md-default-bg-color--light: hsla(var(--md-hue), 15%, 16%, 0.54); + --md-default-bg-color--lighter: hsla(var(--md-hue), 15%, 16%, 0.26); + --md-default-bg-color--lightest: hsla(var(--md-hue), 15%, 16%, 0.07); + --md-primary-fg-color: var(--md-default-bg-color); + --md-default-fg-color: hsla(var(--md-hue), 75%, 95%, 1); + --md-default-fg-color--light: #fff; + --md-typeset-a-color: var(--reactpy-color); + --md-accent-fg-color: var(--reactpy-color-dark); } [data-md-color-scheme="default"] { - --md-primary-fg-color: var(--md-default-bg-color); - --md-default-fg-color--light: #000; - --md-default-fg-color--lighter: #0000007e; - --md-default-fg-color--lightest: #00000029; - --md-typeset-color: rgb(35, 39, 47); - --md-typeset-a-color: var(--reactpy-color); - --md-accent-fg-color: var(--reactpy-color-dark); + --md-primary-fg-color: var(--md-default-bg-color); + --md-default-fg-color--light: #000; + --md-default-fg-color--lighter: #0000007e; + --md-default-fg-color--lightest: #00000029; + --md-typeset-color: rgb(35, 39, 47); + --md-typeset-a-color: var(--reactpy-color); + --md-accent-fg-color: var(--reactpy-color-dark); } /* Font changes */ .md-typeset { - font-weight: 300; + font-weight: 300; } .md-typeset h1 { - font-weight: 600; - margin: 0; - font-size: 2.5em; + font-weight: 600; + margin: 0; + font-size: 2.5em; } .md-typeset h2 { - font-weight: 500; + font-weight: 500; } .md-typeset h3 { - font-weight: 400; + font-weight: 400; } /* Intro section styling */ p.intro { - font-size: 0.9rem; - font-weight: 500; + font-size: 0.9rem; + font-weight: 500; } /* Hide "Overview" jump selector */ h2#overview { - visibility: hidden; - height: 0; - margin: 0; - padding: 0; + visibility: hidden; + height: 0; + margin: 0; + padding: 0; } /* Reduce size of the outdated banner */ .md-banner__inner { - margin: 0.45rem auto; + margin: 0.45rem auto; } /* Desktop Styles */ @media screen and (min-width: 60em) { - /* Remove max width on desktop */ - .md-grid { - max-width: none; - } + /* Remove max width on desktop */ + .md-grid { + max-width: none; + } } /* Max size of page content */ .md-content { - max-width: 56rem; + max-width: 56rem; } diff --git a/docs/src/assets/css/navbar.css b/docs/src/assets/css/navbar.css index 33e8b14..8d47684 100644 --- a/docs/src/assets/css/navbar.css +++ b/docs/src/assets/css/navbar.css @@ -1,185 +1,185 @@ [data-md-color-scheme="slate"] { - --md-header-border-color: rgb(255 255 255 / 5%); - --md-version-bg-color: #ffffff0d; + --md-header-border-color: rgb(255 255 255 / 5%); + --md-version-bg-color: #ffffff0d; } [data-md-color-scheme="default"] { - --md-header-border-color: rgb(0 0 0 / 7%); - --md-version-bg-color: #ae58ee2e; + --md-header-border-color: rgb(0 0 0 / 7%); + --md-version-bg-color: #ae58ee2e; } .md-header { - border: 0 solid transparent; - border-bottom-width: 1px; + border: 0 solid transparent; + border-bottom-width: 1px; } .md-header--shadow { - box-shadow: none; - border-color: var(--md-header-border-color); - transition: border-color 0.35s cubic-bezier(0.1, 0.7, 0.1, 1); + box-shadow: none; + border-color: var(--md-header-border-color); + transition: border-color 0.35s cubic-bezier(0.1, 0.7, 0.1, 1); } /* Version selector */ .md-header__topic .md-ellipsis, .md-header__title [data-md-component="header-topic"] { - display: none; + display: none; } [dir="ltr"] .md-version__current { - margin: 0; + margin: 0; } .md-version__list { - margin: 0; - left: 0; - right: 0; - top: 2.5rem; + margin: 0; + left: 0; + right: 0; + top: 2.5rem; } .md-version { - background: var(--md-version-bg-color); - border-radius: 999px; - padding: 0 0.8rem; - margin: 0.3rem 0; - height: 1.8rem; - display: flex; - font-size: 0.7rem; + background: var(--md-version-bg-color); + border-radius: 999px; + padding: 0 0.8rem; + margin: 0.3rem 0; + height: 1.8rem; + display: flex; + font-size: 0.7rem; } /* Mobile Styling */ @media screen and (max-width: 60em) { - label.md-header__button.md-icon[for="__drawer"] { - order: 1; - } - .md-header__button.md-logo { - display: initial; - order: 2; - margin-right: auto; - } - .md-header__title { - order: 3; - } - .md-header__button[for="__search"] { - order: 4; - } - .md-header__option[data-md-component="palette"] { - order: 5; - } - .md-header__source { - display: initial; - order: 6; - } - .md-header__source .md-source__repository { - display: none; - } + label.md-header__button.md-icon[for="__drawer"] { + order: 1; + } + .md-header__button.md-logo { + display: initial; + order: 2; + margin-right: auto; + } + .md-header__title { + order: 3; + } + .md-header__button[for="__search"] { + order: 4; + } + .md-header__option[data-md-component="palette"] { + order: 5; + } + .md-header__source { + display: initial; + order: 6; + } + .md-header__source .md-source__repository { + display: none; + } } /* Desktop Styling */ @media screen and (min-width: 60em) { - /* Nav container */ - nav.md-header__inner { - display: contents; - } - header.md-header { - display: flex; - align-items: center; - } - - /* Logo */ - .md-header__button.md-logo { - order: 1; - padding-right: 0.4rem; - padding-top: 0; - padding-bottom: 0; - } - .md-header__button.md-logo img { - height: 2rem; - } - - /* Version selector */ - [dir="ltr"] .md-header__title { - order: 2; - margin: 0; - margin-right: 0.8rem; - margin-left: 0.2rem; - flex-grow: 0; - } - .md-header__topic { - position: relative; - } - .md-header__title--active .md-header__topic { - transform: none; - opacity: 1; - pointer-events: auto; - z-index: 4; - } - - /* Search */ - .md-search { - order: 3; - width: 100%; - margin-right: 0.6rem; - } - .md-search__inner { - width: 100%; - float: unset !important; - } - .md-search__form { - border-radius: 9999px; - } - [data-md-toggle="search"]:checked ~ .md-header .md-header__option { - max-width: unset; - opacity: unset; - transition: unset; - } - - /* Tabs */ - .md-tabs { - order: 4; - min-width: -webkit-fit-content; - min-width: -moz-fit-content; - min-width: fit-content; - width: -webkit-fit-content; - width: -moz-fit-content; - width: fit-content; - z-index: -1; - overflow: visible; - border: none !important; - } - li.md-tabs__item.md-tabs__item--active { - background: var(--reactpy-color-opacity-10); - border-radius: 9999px; - color: var(--md-typeset-a-color); - } - .md-tabs__link { - margin: 0; - } - .md-tabs__item { - height: 1.8rem; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - } - - /* Dark/Light Selector */ - .md-header__option[data-md-component="palette"] { - order: 5; - } - - /* GitHub info */ - .md-header__source { - order: 6; - margin-left: 0 !important; - } + /* Nav container */ + nav.md-header__inner { + display: contents; + } + header.md-header { + display: flex; + align-items: center; + } + + /* Logo */ + .md-header__button.md-logo { + order: 1; + padding-right: 0.4rem; + padding-top: 0; + padding-bottom: 0; + } + .md-header__button.md-logo img { + height: 2rem; + } + + /* Version selector */ + [dir="ltr"] .md-header__title { + order: 2; + margin: 0; + margin-right: 0.8rem; + margin-left: 0.2rem; + flex-grow: 0; + } + .md-header__topic { + position: relative; + } + .md-header__title--active .md-header__topic { + transform: none; + opacity: 1; + pointer-events: auto; + z-index: 4; + } + + /* Search */ + .md-search { + order: 3; + width: 100%; + margin-right: 0.6rem; + } + .md-search__inner { + width: 100%; + float: unset !important; + } + .md-search__form { + border-radius: 9999px; + } + [data-md-toggle="search"]:checked ~ .md-header .md-header__option { + max-width: unset; + opacity: unset; + transition: unset; + } + + /* Tabs */ + .md-tabs { + order: 4; + min-width: -webkit-fit-content; + min-width: -moz-fit-content; + min-width: fit-content; + width: -webkit-fit-content; + width: -moz-fit-content; + width: fit-content; + z-index: -1; + overflow: visible; + border: none !important; + } + li.md-tabs__item.md-tabs__item--active { + background: var(--reactpy-color-opacity-10); + border-radius: 9999px; + color: var(--md-typeset-a-color); + } + .md-tabs__link { + margin: 0; + } + .md-tabs__item { + height: 1.8rem; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + } + + /* Dark/Light Selector */ + .md-header__option[data-md-component="palette"] { + order: 5; + } + + /* GitHub info */ + .md-header__source { + order: 6; + margin-left: 0 !important; + } } /* Ultrawide Desktop Styles */ @media screen and (min-width: 1919px) { - .md-search { - order: 2; - width: 100%; - max-width: 34.4rem; - margin: 0 auto; - } + .md-search { + order: 2; + width: 100%; + max-width: 34.4rem; + margin: 0 auto; + } } diff --git a/docs/src/assets/css/sidebar.css b/docs/src/assets/css/sidebar.css index b6507d9..094ac43 100644 --- a/docs/src/assets/css/sidebar.css +++ b/docs/src/assets/css/sidebar.css @@ -1,104 +1,103 @@ :root { - --sizebar-font-size: 0.62rem; + --sizebar-font-size: 0.62rem; + --sizebar-font-size-2: 0.56rem; } .md-nav__link { - word-break: break-word; + word-break: break-word; +} + +/* Center the docs content */ +.md-content { + justify-self: center; + width: 100%; + margin: 0 auto; } /* Desktop Styling */ @media screen and (min-width: 76.1875em) { - /* Move the sidebar and TOC to the edge of the page */ - .md-main__inner.md-grid { - margin-left: 0; - margin-right: 0; - max-width: unset; - display: grid; - grid-template-columns: auto 1fr auto; - } - - .md-content { - justify-self: center; - width: 100%; - } - /* Made the sidebar buttons look React-like */ - .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link { - text-transform: uppercase; - } - - .md-nav__title[for="__toc"] { - text-transform: uppercase; - margin: 0.5rem; - } - - .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link { - color: rgb(133, 142, 159); - margin: 0.5rem; - } - - .md-nav__item .md-nav__link { - position: relative; - } - - .md-nav__link:is(:focus, :hover):not(.md-nav__link--active) { - color: unset; - } - - .md-nav__item - .md-nav__link:is(:focus, :hover):not(.md-nav__link--active):before { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 0.2; - z-index: -1; - background: grey; - } - - .md-nav__item .md-nav__link--active:before { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: -1; - background: var(--reactpy-color-opacity-10); - } - - .md-nav__link { - padding: 0.5rem 0.5rem 0.5rem 1rem; - margin: 0; - border-radius: 0 10px 10px 0; - font-weight: 500; - overflow: hidden; - font-size: var(--sizebar-font-size); - } + /* Move the sidebar and TOC to the edge of the page */ + .md-main__inner.md-grid { + margin-left: 0; + margin-right: 0; + max-width: unset; + display: grid; + grid-template-columns: auto 1fr auto; + } + [dir="ltr"] .md-nav--primary > .md-nav__list { + padding-left: 0; + } - .md-sidebar__scrollwrap { - margin: 0; - } + /* Btn section (title) */ + .md-nav--lifted > .md-nav__list > .md-nav__item--active > .md-nav__link { + text-transform: uppercase; + color: rgb(133, 142, 159); + background: transparent; + box-shadow: none; + } - [dir="ltr"] - .md-nav--lifted - .md-nav[data-md-level="1"] - > .md-nav__list - > .md-nav__item { - padding: 0; - } + /* Btn hover styling */ + .md-nav__item .md-nav__link { + position: relative; + } + .md-nav__link:is(:focus, :hover):not(.md-nav__link--active) { + color: unset; + } + .md-nav__item .md-nav__link:is(:focus, :hover):not(.md-nav__link--active):before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.2; + z-index: -1; + background: grey; + } - .md-nav__item--nested .md-nav__item .md-nav__item { - padding: 0; - } + /* Btn currently selected */ + .md-nav__item .md-nav__link--active:before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + background: var(--reactpy-color-opacity-10); + } - .md-nav__item--nested .md-nav__item .md-nav__item .md-nav__link { - font-weight: 300; - } + /* Btn shape & font */ + .md-nav__link { + padding: 0.5rem 0.5rem 0.5rem 1rem; + margin: 0; + border-radius: 0 10px 10px 0; + font-weight: 500; + overflow: hidden; + font-size: var(--sizebar-font-size); + } + .md-sidebar__scrollwrap { + margin: 0; + } + .md-nav__item--nested .md-nav__item .md-nav__item .md-nav__link { + font-weight: 300; + font-size: var(--sizebar-font-size-2); + } + [dir="ltr"] .md-nav--primary .md-nav__list { + padding: 0; + } + [dir="ltr"] .md-nav--lifted > .md-nav__list > .md-nav__item > .md-nav:not(.md-nav--secondary) { + margin-left: 0; + } + .md-nav { + margin: 0; + } + .md-nav__toggle ~ .md-nav { + grid-template-rows: minmax(0rem, 0fr); + } - .md-nav__item--nested .md-nav__item .md-nav__item .md-nav__link { - font-weight: 400; - padding-left: 1.25rem; - } + /* Collapse icon */ + .md-nav__icon:hover { + background: transparent; + } } diff --git a/docs/src/assets/css/table-of-contents.css b/docs/src/assets/css/table-of-contents.css index 6c94f06..b524a19 100644 --- a/docs/src/assets/css/table-of-contents.css +++ b/docs/src/assets/css/table-of-contents.css @@ -1,48 +1,61 @@ /* Table of Contents styling */ @media screen and (min-width: 60em) { - [data-md-component="sidebar"] .md-nav__title[for="__toc"] { - text-transform: uppercase; - margin: 0.5rem; - margin-left: 0; - font-size: var(--sizebar-font-size); - } - - [data-md-component="toc"] .md-nav__item .md-nav__link--active { - position: relative; - } - - [data-md-component="toc"] .md-nav__item .md-nav__link--active:before { - content: ""; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 0.15; - z-index: -1; - background: var(--md-typeset-a-color); - } - - [data-md-component="toc"] .md-nav__link { - padding: 0.5rem 0.5rem; - margin: 0; - border-radius: 10px 0 0 10px; - font-weight: 400; - } - - [data-md-component="toc"] - .md-nav__item - .md-nav__list - .md-nav__item - .md-nav__link { - padding-left: 1.25rem; - } - - [dir="ltr"] .md-sidebar__inner { - padding: 0; - } - - .md-nav__item { - padding: 0; - } + .md-nav__title[for="__toc"] { + text-transform: uppercase; + color: rgb(133, 142, 159); + } + [data-md-component="sidebar"] .md-nav__title[for="__toc"] { + text-transform: uppercase; + margin: 0.5rem; + margin-left: 0; + font-size: var(--sizebar-font-size); + } + + [data-md-component="toc"] .md-nav__item .md-nav__link--active { + position: relative; + } + + [data-md-component="toc"] .md-nav__item .md-nav__link--active:before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0.15; + z-index: -1; + background: var(--md-typeset-a-color); + } + + [data-md-component="toc"] .md-nav__link { + padding: 0.5rem 0.5rem; + padding-right: 0; + margin: 0; + border-radius: 10px 0 0 10px; + font-weight: 400; + } + + [dir="ltr"] .md-nav--secondary .md-nav__item > .md-nav__link { + margin-right: 0; + padding-right: 0.4rem; + } + + [data-md-component="toc"] .md-nav__item .md-nav__list .md-nav__item .md-nav__link { + padding-left: 1.25rem; + } + + [dir="ltr"] .md-sidebar__inner { + padding: 0; + } + + .md-nav__item { + padding: 0; + } +} + +/* Hide the toc as soon as the left sidebar is collapsed into the hamburger menu */ +@media screen and (max-width: 76.1875em) { + .md-sidebar--secondary:not([hidden]) { + display: none; + } } diff --git a/pyproject.toml b/pyproject.toml index c476f8e..a1a7fa7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,11 +28,7 @@ classifiers = [ "Environment :: Web Environment", "Typing :: Typed", ] -dependencies = [ - "reactpy[asgi]>=2.0.0b10, <3.0.0", - "typing_extensions", - "jsonpointer==3.*", -] +dependencies = ["reactpy[asgi]>=2.0.0b12, <3.0.0", "typing_extensions"] dynamic = ["version"] urls.Changelog = "https://reactive-python.github.io/reactpy-router/latest/about/changelog/" urls.Documentation = "https://reactive-python.github.io/reactpy-router/latest/" @@ -162,7 +158,8 @@ lint.extend-ignore = [ "PLR2004", # Magic value used in comparison "SIM115", # Use context handler for opening files "SLF001", # Private member accessed - "DOC201", # 'Returns:' section in docstring is missing + "DOC201", # 'Returns' section in docstring is missing + "DOC501", # `Error` section in docstring is missing ] lint.preview = true diff --git a/src/js/bun.lockb b/src/js/bun.lockb index a59a97a..a1513d4 100644 Binary files a/src/js/bun.lockb and b/src/js/bun.lockb differ diff --git a/src/js/package.json b/src/js/package.json index 53f9f21..d4b1ff8 100644 --- a/src/js/package.json +++ b/src/js/package.json @@ -9,6 +9,6 @@ "prettier": "^3.3.3" }, "dependencies": { - "@reactpy/client": "^1.0.3" + "@reactpy/client": "^1.2.0" } } diff --git a/src/js/src/components.ts b/src/js/src/components.ts index 1aaff62..a5affbe 100644 --- a/src/js/src/components.ts +++ b/src/js/src/components.ts @@ -22,11 +22,11 @@ export function bind(node: HTMLElement | Element | Node) { /** * History component that captures browser "history go back" actions and notifies the server. */ -export function History({ onHistoryChangeCallback }: HistoryProps): null { +export function History({ onHistoryPreviousCallback }: HistoryProps): null { // Tell the server about history "popstate" events React.useEffect(() => { const listener = () => { - onHistoryChangeCallback(createLocationObject()); + onHistoryPreviousCallback(createLocationObject()); }; // Register the event listener @@ -35,12 +35,6 @@ export function History({ onHistoryChangeCallback }: HistoryProps): null { // Delete the event listener when the component is unmounted return () => window.removeEventListener("popstate", listener); }); - - // Tell the server about the URL during the initial page load - React.useEffect(() => { - onHistoryChangeCallback(createLocationObject()); - return () => {}; - }, []); return null; } @@ -52,30 +46,46 @@ export function History({ onHistoryChangeCallback }: HistoryProps): null { */ export function Link({ onClickCallback, linkClass }: LinkProps): null { React.useEffect(() => { - // Event function that will tell the server about clicks + // Event function that will tell the server about clicks. + // Preserve the browser's default behavior (open in new tab/window) for + // modifier-clicks and middle-click — only plain left-clicks are routed + // through the SPA history handler. const handleClick = (event: Event) => { let click_event = event as MouseEvent; - if (!click_event.ctrlKey) { + const isPlainLeftClick = + click_event.button === 0 && + !click_event.ctrlKey && + !click_event.metaKey && + !click_event.shiftKey && + !click_event.altKey; + if (isPlainLeftClick) { event.preventDefault(); let to = (event.currentTarget as HTMLElement).getAttribute("href"); - pushState(to); - onClickCallback(createLocationObject()); + if (to) { + pushState(to); + onClickCallback(createLocationObject()); + } } }; - // Register the event listener - let link = document.querySelector(`.${linkClass}`); - if (link) { - link.addEventListener("click", handleClick); - } else { + // Register the event listener on every anchor sharing this link's class. + // A page may render multiple links that share the unique class (e.g. when + // the same `link` component is reused), so use querySelectorAll rather + // than querySelector to wire all of them. + const links = document.querySelectorAll(`.${linkClass}`); + if (links.length === 0) { console.warn(`Link component with class name ${linkClass} not found.`); + } else { + links.forEach((link) => { + link.addEventListener("click", handleClick); + }); } - // Delete the event listener when the component is unmounted + // Delete the event listeners when the component is unmounted return () => { - if (link) { + links.forEach((link) => { link.removeEventListener("click", handleClick); - } + }); }; }); return null; diff --git a/src/js/src/types.ts b/src/js/src/types.ts index 8b05409..13eb275 100644 --- a/src/js/src/types.ts +++ b/src/js/src/types.ts @@ -4,7 +4,7 @@ export interface ReactPyLocation { } export interface HistoryProps { - onHistoryChangeCallback: (location: ReactPyLocation) => void; + onHistoryPreviousCallback: (location: ReactPyLocation) => void; } export interface LinkProps { diff --git a/src/reactpy_router/__init__.py b/src/reactpy_router/__init__.py index add1e5d..5736183 100644 --- a/src/reactpy_router/__init__.py +++ b/src/reactpy_router/__init__.py @@ -1,4 +1,4 @@ -__version__ = "2.0.0" +__version__ = "3.0.0b1" from reactpy_router.components import link, navigate, route diff --git a/src/reactpy_router/components.py b/src/reactpy_router/components.py index d16af57..95f0340 100644 --- a/src/reactpy_router/components.py +++ b/src/reactpy_router/components.py @@ -49,13 +49,10 @@ def _link(attributes: dict[str, Any], *children: Any) -> VdomDict: set_location = _use_route_state().set_location if "className" in attributes: class_name = " ".join([attributes.pop("className"), class_name]) - if "class_name" in attributes: # pragma: no cover - # TODO: This can be removed when ReactPy stops supporting underscores in attribute names - class_name = " ".join([attributes.pop("class_name"), class_name]) if "href" in attributes and "to" not in attributes: attributes["to"] = attributes.pop("href") if "to" not in attributes: # pragma: no cover - msg = "The `to` attribute is required for the `Link` component." + msg = "The `to` attribute is required for the `link` component." raise ValueError(msg) to = attributes.pop("to") diff --git a/src/reactpy_router/converters.py b/src/reactpy_router/converters.py index 5fe1b5e..b55ba15 100644 --- a/src/reactpy_router/converters.py +++ b/src/reactpy_router/converters.py @@ -14,7 +14,7 @@ "func": str, }, "uuid": { - "regex": r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", + "regex": r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}", "func": uuid.UUID, }, "slug": { diff --git a/src/reactpy_router/hooks.py b/src/reactpy_router/hooks.py index 0431d11..725a8a9 100644 --- a/src/reactpy_router/hooks.py +++ b/src/reactpy_router/hooks.py @@ -38,7 +38,6 @@ def use_params() -> dict[str, Any]: A dictionary of the current URL's parameters. """ - # TODO: Check if this returns all parent params return _use_route_state().params @@ -64,6 +63,7 @@ def use_search_params( # TODO: In order to match `react-router`, this will need to return a tuple of the search params \ # and a function to update them. This is currently not possible without reactpy core having a \ # communication layer. + # https://github.com/reactive-python/reactpy/issues/975 return parse_qs( query_string, keep_blank_values=keep_blank_values, diff --git a/src/reactpy_router/routers.py b/src/reactpy_router/routers.py index 8b3032b..4868178 100644 --- a/src/reactpy_router/routers.py +++ b/src/reactpy_router/routers.py @@ -59,33 +59,33 @@ def router( User notice: This component typically should never be used. Instead, use `create_router` if creating a custom routing engine.""" - old_connection = use_connection() - location, set_location = use_state(cast("Location | None", None)) + initial = use_connection() + location, set_location = use_state(initial.location) resolvers = use_memo( lambda: tuple(map(resolver, _iter_routes(routes))), dependencies=(resolver, hash(routes)), ) - route_element = None - match = use_memo(lambda: _match_route(resolvers, location or old_connection.location)) + match = use_memo(lambda: _match_route(resolvers, location or initial.location)) if match: - # Skip rendering until ReactPy-Router knows what URL the page is on. - if location: - route_element = _route_state_context( - match.element, - value=RouteState(set_location, match.params), + if not location or not location.path: + msg = ( + "ReactPy-Router was unable to determine the current URL location.\n" + "Are you sure you are running this within the a ConnectionContext?" ) + raise RuntimeError(msg) - def on_history_change(event: dict[str, Any]) -> None: - """Callback function used within the JavaScript `History` component.""" + def on_history_previous(event: dict[str, Any]) -> None: + """Callback function used within the JavaScript `History` component that signifies + a history "go back" action.""" new_location = Location(**event) if location != new_location: set_location(new_location) return ConnectionContext( - History({"onHistoryChangeCallback": on_history_change}), # type: ignore[return-value] - route_element, - value=Connection(old_connection.scope, location or old_connection.location, old_connection.carrier), + History({"onHistoryPreviousCallback": on_history_previous}), # type: ignore[return-value] + _route_state_context(match.element, value=RouteState(set_location, match.params)), + value=Connection(initial.scope, location or initial.location, initial.carrier), ) return None diff --git a/tests/test_rendering.py b/tests/test_rendering.py index 6a78ecc..0c6890e 100644 --- a/tests/test_rendering.py +++ b/tests/test_rendering.py @@ -56,5 +56,3 @@ def not_found(): assert root_render_count == 3 assert home_page_render_count == 1 assert not_found_render_count == 2 - - assert True diff --git a/tests/test_resolver.py b/tests/test_resolver.py index ee172db..44b5001 100644 --- a/tests/test_resolver.py +++ b/tests/test_resolver.py @@ -44,12 +44,12 @@ def test_parse_path(): assert resolver.converter_mapping == {"b": int, "c": float} assert resolver.parse_path("/a/{b:int}/{c:float}/{d:uuid}/c") == re.compile( - r"^/a/(?P\d+)/(?P\d+(\.\d+)?)/(?P[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/c$" + r"^/a/(?P\d+)/(?P\d+(\.\d+)?)/(?P[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/c$" ) assert resolver.converter_mapping == {"b": int, "c": float, "d": uuid.UUID} assert resolver.parse_path("/a/{b:int}/{c:float}/{d:uuid}/{e:path}/c") == re.compile( - r"^/a/(?P\d+)/(?P\d+(\.\d+)?)/(?P[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})/(?P.+)/c$" + r"^/a/(?P\d+)/(?P\d+(\.\d+)?)/(?P[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})/(?P.+)/c$" ) assert resolver.converter_mapping == { "b": int, diff --git a/tests/test_router.py b/tests/test_router.py index f1922be..9349735 100644 --- a/tests/test_router.py +++ b/tests/test_router.py @@ -1,4 +1,3 @@ -import asyncio import os from typing import Any @@ -10,7 +9,6 @@ from reactpy_router import browser_router, link, navigate, route, use_params, use_search_params GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS", "").lower() == "true" -CLICK_DELAY = 250 if GITHUB_ACTIONS else 25 # Delay in miliseconds. pytestmark = pytest.mark.anyio @@ -94,7 +92,7 @@ def sample(): for link_selector in ["#root", "#a", "#b", "#c"]: link_ = await display.page.wait_for_selector(link_selector) - await link_.click(delay=CLICK_DELAY) + await link_.click() await display.page.wait_for_selector("#default") @@ -176,13 +174,12 @@ def sample(): for link_selector in link_selectors: link_ = await display.page.wait_for_selector(link_selector) - await link_.click(delay=CLICK_DELAY) + await link_.click() await display.page.wait_for_selector("#default") link_selectors.reverse() for link_selector in link_selectors: - await asyncio.sleep(CLICK_DELAY / 1000) await display.page.go_back() await display.page.wait_for_selector(link_selector) @@ -207,13 +204,12 @@ def sample(): for link_selector in selectors: link_ = await display.page.wait_for_selector(link_selector) - await link_.click(delay=CLICK_DELAY) + await link_.click() await display.page.wait_for_selector("#default") selectors.reverse() for link_selector in selectors: - await asyncio.sleep(CLICK_DELAY / 1000) await display.page.go_back() await display.page.wait_for_selector(link_selector) @@ -235,7 +231,7 @@ def sample(): await display.show(sample) await display.page.wait_for_selector("#root") link_ = await display.page.wait_for_selector("#root") - await link_.click(delay=CLICK_DELAY) + await link_.click() await display.page.wait_for_selector("#success") @@ -272,7 +268,7 @@ def sample(): await display.show(sample) link_ = await display.page.wait_for_selector("#root") - await link_.click(delay=CLICK_DELAY, modifiers=["Control"]) + await link_.click(modifiers=["Control"]) browser_context = browser.contexts[0] if len(browser_context.pages) == 1: new_page: Page = await browser_context.wait_for_event("page") @@ -300,9 +296,8 @@ def sample(): await display.show(sample) button = await display.page.wait_for_selector("button") - await button.click(delay=CLICK_DELAY) + await button.click() await display.page.wait_for_selector("#a") - await asyncio.sleep(CLICK_DELAY / 1000) await display.page.go_back() await display.page.wait_for_selector("button") @@ -327,11 +322,10 @@ def sample(): await display.show(sample) button = await display.page.wait_for_selector("#nav-a") - await button.click(delay=CLICK_DELAY) + await button.click() button = await display.page.wait_for_selector("#nav-b") - await button.click(delay=CLICK_DELAY) + await button.click() await display.page.wait_for_selector("#b") - await asyncio.sleep(CLICK_DELAY / 1000) await display.page.go_back() await display.page.wait_for_selector("#nav-a") @@ -355,11 +349,9 @@ def sample(): await display.show(sample) button = await display.page.wait_for_selector("#root-a") - await button.click(delay=CLICK_DELAY) + await button.click() button = await display.page.wait_for_selector("#nav-a") - await button.click(delay=CLICK_DELAY) - await asyncio.sleep(CLICK_DELAY / 1000) + await button.click() await display.page.wait_for_selector("#nav-a") - await asyncio.sleep(CLICK_DELAY / 1000) await display.page.go_back() await display.page.wait_for_selector("#root-a")