diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
index 7e988149..a5553200 100644
--- a/.github/pull_request_template.md
+++ b/.github/pull_request_template.md
@@ -1,8 +1,8 @@
## Description
-A summary of the changes.
+
-## Checklist:
+## Checklist
Please update this checklist as you complete each item:
@@ -11,4 +11,4 @@ Please update this checklist as you complete each item:
- [ ] Documentation has been updated, if necessary.
- [ ] GitHub Issues closed by this PR have been linked.
-By submitting this pull request you agree that all contributions comply with this project's open source license(s).
+By submitting this pull request I agree that all contributions comply with this project's open source license(s).
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 004520c7..9cbf92a1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -34,7 +34,9 @@ Using the following categories, list your changes in this order:
## [Unreleased]
-- Nothing (yet)!
+### Added
+
+- An "offline component" can now be displayed when the client disconnects from the server.
## [3.6.0] - 2024-01-10
@@ -103,9 +105,14 @@ Using the following categories, list your changes in this order:
- Prettier WebSocket URLs for components that do not have sessions.
- Template tag will now only validate `args`/`kwargs` if `settings.py:DEBUG` is enabled.
- Bumped the minimum `@reactpy/client` version to `0.3.1`
-- Bumped the minimum Django version to `4.2`.
- Use TypeScript instead of JavaScript for this repository.
- - Note: ReactPy-Django will continue bumping minimum Django requirements to versions that increase async support. This "latest-only" trend will continue until Django has all async features that ReactPy benefits from. After this point, ReactPy-Django will begin supporting all maintained Django versions.
+- Bumped the minimum Django version to `4.2`.
+
+???+ note "Django 4.2+ is required"
+
+ ReactPy-Django will continue bumping minimum Django requirements to versions that increase async support.
+
+ This "latest-only" trend will continue until Django has all async features that ReactPy benefits from. After this point, ReactPy-Django will begin supporting all maintained Django versions.
### Removed
diff --git a/README.md b/README.md
index 479b6ea3..93b16ea5 100644
--- a/README.md
+++ b/README.md
@@ -25,6 +25,7 @@
- [Distributed computing](https://reactive-python.github.io/reactpy-django/latest/reference/settings/#reactpy_default_hosts)
- [Performance enhancements](https://reactive-python.github.io/reactpy-django/latest/reference/settings/#performance-settings)
- [Customizable reconnection behavior](https://reactive-python.github.io/reactpy-django/latest/reference/settings/#stability-settings)
+- [Customizable disconnection behavior](https://reactive-python.github.io/reactpy-django/latest/reference/template-tag)
- [Multiple root components](https://reactive-python.github.io/reactpy-django/latest/reference/template-tag/)
- [Django view to ReactPy component conversion](https://reactive-python.github.io/reactpy-django/latest/reference/components/#view-to-component)
- [Django static file access](https://reactive-python.github.io/reactpy-django/latest/reference/components/#django-css)
diff --git a/docs/src/learn/add-reactpy-to-a-django-project.md b/docs/src/learn/add-reactpy-to-a-django-project.md
index 71b174c0..e1911ca7 100644
--- a/docs/src/learn/add-reactpy-to-a-django-project.md
+++ b/docs/src/learn/add-reactpy-to-a-django-project.md
@@ -117,7 +117,7 @@ The [next step](./your-first-component.md) will show you how to create your firs
Prefer a quick summary? Read the **At a Glance** section below.
-!!! info "At a Glance: Your First Component"
+!!! info "At a Glance"
**`my_app/components.py`**
diff --git a/docs/src/reference/settings.md b/docs/src/reference/settings.md
index db0171e5..b41fc402 100644
--- a/docs/src/reference/settings.md
+++ b/docs/src/reference/settings.md
@@ -88,9 +88,11 @@ Multiprocessing-safe database used by ReactPy, typically for session data.
If configuring this value, it is mandatory to enable our database router like such:
-```python linenums="0"
-DATABASE_ROUTERS = ["reactpy_django.database.Router", ...]
-```
+=== "settings.py"
+
+ ```python linenums="0"
+ DATABASE_ROUTERS = ["reactpy_django.database.Router", ...]
+ ```
---
@@ -145,7 +147,7 @@ Configures whether to pre-render your components via HTTP, which enables SEO com
During pre-rendering, there are some key differences in behavior:
1. Only the component's first render is pre-rendered.
-2. All `#!python connection` related hooks use HTTP.
+2. All [`connection` hooks](https://reactive-python.github.io/reactpy-django/latest/reference/hooks/#connection-hooks) will provide HTTP variants.
3. The component will be non-interactive until a WebSocket connection is formed.
diff --git a/docs/src/reference/template-tag.md b/docs/src/reference/template-tag.md
index 6321243b..53140c13 100644
--- a/docs/src/reference/template-tag.md
+++ b/docs/src/reference/template-tag.md
@@ -27,7 +27,8 @@ This template tag can be used to insert any number of ReactPy components onto yo
| `#!python class` | `#!python str | None` | The HTML class to apply to the top-level component div. | `#!python None` |
| `#!python key` | `#!python Any` | Force the component's root node to use a [specific key value](https://reactpy.dev/docs/guides/creating-interfaces/rendering-data/index.html#organizing-items-with-keys). Using `#!python key` within a template tag is effectively useless. | `#!python None` |
| `#!python host` | `#!python str | None` | The host to use for the ReactPy connections. If unset, the host will be automatically configured. Example values include: `localhost:8000`, `example.com`, `example.com/subdir` | `#!python None` |
- | `#!python prerender` | `#!python str` | If `#!python "True"`, the component will pre-rendered, which enables SEO compatibility and reduces perceived latency. | `#!python "False"` |
+ | `#!python prerender` | `#!python str` | If `#!python "true"`, the component will pre-rendered, which enables SEO compatibility and reduces perceived latency. | `#!python "false"` |
+ | `#!python offline` | `#!python str` | The dotted path to a component that will be displayed if your root component loses connection to the server. Keep in mind, this `offline` component will be non-interactive (hooks won't operate). | `#!python ""` |
| `#!python **kwargs` | `#!python Any` | The keyword arguments to provide to the component. | N/A |
**Returns**
@@ -62,27 +63,9 @@ This template tag can be used to insert any number of ReactPy components onto yo
{% include "../../python/template-tag-bad-view.py" %}
```
-
-
-??? question "Can I render components on a different server (distributed computing)?"
-
- Yes! By using the `#!python host` keyword argument, you can render components from a completely separate ASGI server.
+ _Note: If you decide to not follow this warning, you will need to use the [`register_component`](../reference/utils.md#register-component) function to manually register your components._
- === "my-template.html"
-
- ```jinja
- ...
- {% component "example_project.my_app.components.do_something" host="127.0.0.1:8001" %}
- ...
- ```
-
- This configuration most commonly involves you deploying multiple instances of your project. But, you can also create dedicated Django project(s) that only render specific ReactPy components if you wish.
-
- Here's a couple of things to keep in mind:
-
- 1. If your host address are completely separate ( `origin1.com != origin2.com` ) you will need to [configure CORS headers](https://pypi.org/project/django-cors-headers/) on your main application during deployment.
- 2. You will not need to register ReactPy WebSocket or HTTP paths on any applications that do not perform any component rendering.
- 3. Your component will only be able to access your template tag's `#!python *args`/`#!python **kwargs` if your applications share a common database.
+
@@ -109,7 +92,6 @@ This template tag can be used to insert any number of ReactPy components onto yo
Additionally, in scenarios where you are trying to create a Single Page Application (SPA) within Django, you will only have one component within your `#!html
` tag.
-
??? question "Can I use positional arguments instead of keyword arguments?"
@@ -127,4 +109,48 @@ This template tag can be used to insert any number of ReactPy components onto yo
{% include "../../python/template-tag-args-kwargs.py" %}
```
-
+??? question "Can I render components on a different server (distributed computing)?"
+
+ Yes! This is most commonly done through [`settings.py:REACTPY_HOSTS`](../reference/settings.md#reactpy_default_hosts). However, you can use the `#!python host` keyword to render components on a specific ASGI server.
+
+ === "my-template.html"
+
+ ```jinja
+ ...
+ {% component "example_project.my_app.components.do_something" host="127.0.0.1:8001" %}
+ ...
+ ```
+
+ This configuration most commonly involves you deploying multiple instances of your project. But, you can also create dedicated Django project(s) that only render specific ReactPy components if you wish.
+
+ Here's a couple of things to keep in mind:
+
+ 1. If your host address are completely separate ( `origin1.com != origin2.com` ) you will need to [configure CORS headers](https://pypi.org/project/django-cors-headers/) on your main application during deployment.
+ 2. You will not need to register ReactPy WebSocket or HTTP paths on any applications that do not perform any component rendering.
+ 3. Your component will only be able to access your template tag's `#!python *args`/`#!python **kwargs` if your applications share a common database.
+
+??? question "How do I pre-render components for SEO compatibility?"
+
+ This is most commonly done through [`settings.py:REACTPY_PRERENDER`](../reference/settings.md#reactpy_prerender). However, you can use the `#!python prerender` keyword to pre-render a specific component.
+
+ === "my-template.html"
+
+ ```jinja
+ ...
+ {% component "example_project.my_app.components.do_something" prerender="true" %}
+ ...
+ ```
+
+??? question "How do I show something when the client disconnects?"
+
+ You can use the `#!python offline` keyword to display a specific component when the client disconnects from the server.
+
+ === "my-template.html"
+
+ ```jinja
+ ...
+ {% component "example_project.my_app.components.do_something" offline="example_project.my_app.components.offline" %}
+ ...
+ ```
+
+ _Note: The `#!python offline` component will be non-interactive (hooks won't operate)._
diff --git a/src/js/package-lock.json b/src/js/package-lock.json
index f4ffe4f8..97e111f1 100644
--- a/src/js/package-lock.json
+++ b/src/js/package-lock.json
@@ -1,29 +1,29 @@
{
"name": "js",
- "lockfileVersion": 2,
+ "lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"@reactpy/client": "^0.3.1",
- "@rollup/plugin-typescript": "^11.1.2",
+ "@rollup/plugin-typescript": "^11.1.6",
"tslib": "^2.6.2"
},
"devDependencies": {
- "@rollup/plugin-commonjs": "^24.0.1",
- "@rollup/plugin-node-resolve": "^15.0.1",
- "@rollup/plugin-replace": "^5.0.2",
- "@types/react": "^17.0",
- "@types/react-dom": "^17.0",
- "prettier": "^3.0.2",
- "rollup": "^3.28.1",
- "typescript": "^4.9.5"
+ "@rollup/plugin-commonjs": "^25.0.7",
+ "@rollup/plugin-node-resolve": "^15.2.3",
+ "@rollup/plugin-replace": "^5.0.5",
+ "@types/react": "^18.2.48",
+ "@types/react-dom": "^18.2.18",
+ "prettier": "^3.2.3",
+ "rollup": "^4.9.5",
+ "typescript": "^5.3.3"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.4.14",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
- "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/@reactpy/client": {
@@ -40,9 +40,9 @@
}
},
"node_modules/@rollup/plugin-commonjs": {
- "version": "24.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
- "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==",
+ "version": "25.0.7",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-25.0.7.tgz",
+ "integrity": "sha512-nEvcR+LRjEjsaSsc4x3XZfCCvZIaSMenZu/OiwOKGN2UhQpAYI7ru7czFvyWbErlpoGjnSX3D5Ch5FcMA3kRWQ==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
@@ -50,13 +50,13 @@
"estree-walker": "^2.0.2",
"glob": "^8.0.3",
"is-reference": "1.2.1",
- "magic-string": "^0.27.0"
+ "magic-string": "^0.30.3"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
- "rollup": "^2.68.0||^3.0.0"
+ "rollup": "^2.68.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@@ -65,15 +65,15 @@
}
},
"node_modules/@rollup/plugin-node-resolve": {
- "version": "15.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz",
- "integrity": "sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==",
+ "version": "15.2.3",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz",
+ "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
"@types/resolve": "1.20.2",
"deepmerge": "^4.2.2",
- "is-builtin-module": "^3.2.0",
+ "is-builtin-module": "^3.2.1",
"is-module": "^1.0.0",
"resolve": "^1.22.1"
},
@@ -81,7 +81,7 @@
"node": ">=14.0.0"
},
"peerDependencies": {
- "rollup": "^2.78.0||^3.0.0"
+ "rollup": "^2.78.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@@ -90,19 +90,19 @@
}
},
"node_modules/@rollup/plugin-replace": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz",
- "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==",
+ "version": "5.0.5",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz",
+ "integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^5.0.1",
- "magic-string": "^0.27.0"
+ "magic-string": "^0.30.3"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
- "rollup": "^1.20.0||^2.0.0||^3.0.0"
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@@ -111,18 +111,18 @@
}
},
"node_modules/@rollup/plugin-typescript": {
- "version": "11.1.2",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.2.tgz",
- "integrity": "sha512-0ghSOCMcA7fl1JM+0gYRf+Q/HWyg+zg7/gDSc+fRLmlJWcW5K1I+CLRzaRhXf4Y3DRyPnnDo4M2ktw+a6JcDEg==",
+ "version": "11.1.6",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.6.tgz",
+ "integrity": "sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==",
"dependencies": {
- "@rollup/pluginutils": "^5.0.1",
+ "@rollup/pluginutils": "^5.1.0",
"resolve": "^1.22.1"
},
"engines": {
"node": ">=14.0.0"
},
"peerDependencies": {
- "rollup": "^2.14.0||^3.0.0",
+ "rollup": "^2.14.0||^3.0.0||^4.0.0",
"tslib": "*",
"typescript": ">=3.7.0"
},
@@ -136,9 +136,9 @@
}
},
"node_modules/@rollup/pluginutils": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
- "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
+ "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
"dependencies": {
"@types/estree": "^1.0.0",
"estree-walker": "^2.0.2",
@@ -148,7 +148,7 @@
"node": ">=14.0.0"
},
"peerDependencies": {
- "rollup": "^1.20.0||^2.0.0||^3.0.0"
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
},
"peerDependenciesMeta": {
"rollup": {
@@ -156,21 +156,190 @@
}
}
},
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.5.tgz",
+ "integrity": "sha512-idWaG8xeSRCfRq9KpRysDHJ/rEHBEXcHuJ82XY0yYFIWnLMjZv9vF/7DOq8djQ2n3Lk6+3qfSH8AqlmHlmi1MA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.5.tgz",
+ "integrity": "sha512-f14d7uhAMtsCGjAYwZGv6TwuS3IFaM4ZnGMUn3aCBgkcHAYErhV1Ad97WzBvS2o0aaDv4mVz+syiN0ElMyfBPg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.5.tgz",
+ "integrity": "sha512-ndoXeLx455FffL68OIUrVr89Xu1WLzAG4n65R8roDlCoYiQcGGg6MALvs2Ap9zs7AHg8mpHtMpwC8jBBjZrT/w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.5.tgz",
+ "integrity": "sha512-UmElV1OY2m/1KEEqTlIjieKfVwRg0Zwg4PLgNf0s3glAHXBN99KLpw5A5lrSYCa1Kp63czTpVll2MAqbZYIHoA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.5.tgz",
+ "integrity": "sha512-Q0LcU61v92tQB6ae+udZvOyZ0wfpGojtAKrrpAaIqmJ7+psq4cMIhT/9lfV6UQIpeItnq/2QDROhNLo00lOD1g==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.5.tgz",
+ "integrity": "sha512-dkRscpM+RrR2Ee3eOQmRWFjmV/payHEOrjyq1VZegRUa5OrZJ2MAxBNs05bZuY0YCtpqETDy1Ix4i/hRqX98cA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.5.tgz",
+ "integrity": "sha512-QaKFVOzzST2xzY4MAmiDmURagWLFh+zZtttuEnuNn19AiZ0T3fhPyjPPGwLNdiDT82ZE91hnfJsUiDwF9DClIQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.5.tgz",
+ "integrity": "sha512-HeGqmRJuyVg6/X6MpE2ur7GbymBPS8Np0S/vQFHDmocfORT+Zt76qu+69NUoxXzGqVP1pzaY6QIi0FJWLC3OPA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz",
+ "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.5.tgz",
+ "integrity": "sha512-ezyFUOwldYpj7AbkwyW9AJ203peub81CaAIVvckdkyH8EvhEIoKzaMFJj0G4qYJ5sw3BpqhFrsCc30t54HV8vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.5.tgz",
+ "integrity": "sha512-aHSsMnUw+0UETB0Hlv7B/ZHOGY5bQdwMKJSzGfDfvyhnpmVxLMGnQPGNE9wgqkLUs3+gbG1Qx02S2LLfJ5GaRQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.5.tgz",
+ "integrity": "sha512-AiqiLkb9KSf7Lj/o1U3SEP9Zn+5NuVKgFdRIZkvd4N0+bYrTOovVd0+LmYCPQGbocT4kvFyK+LXCDiXPBF3fyA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz",
+ "integrity": "sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@types/estree": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
- "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ=="
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
},
"node_modules/@types/prop-types": {
- "version": "15.7.5",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
- "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
+ "version": "15.7.11",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
+ "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
"dev": true
},
"node_modules/@types/react": {
- "version": "17.0.65",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.65.tgz",
- "integrity": "sha512-oxur785xZYHvnI7TRS61dXbkIhDPnGfsXKv0cNXR/0ml4SipRIFpSMzA7HMEfOywFwJ5AOnPrXYTEiTRUQeGlQ==",
+ "version": "18.2.48",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz",
+ "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==",
"dev": true,
"dependencies": {
"@types/prop-types": "*",
@@ -179,12 +348,12 @@
}
},
"node_modules/@types/react-dom": {
- "version": "17.0.20",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.20.tgz",
- "integrity": "sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA==",
+ "version": "18.2.18",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.18.tgz",
+ "integrity": "sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==",
"dev": true,
"dependencies": {
- "@types/react": "^17"
+ "@types/react": "*"
}
},
"node_modules/@types/resolve": {
@@ -194,9 +363,9 @@
"dev": true
},
"node_modules/@types/scheduler": {
- "version": "0.16.3",
- "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
- "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
+ "version": "0.16.8",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
+ "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"dev": true
},
"node_modules/balanced-match": {
@@ -233,15 +402,15 @@
"dev": true
},
"node_modules/csstype": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
- "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"dev": true
},
"node_modules/deepmerge": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
- "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
+ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"dev": true,
"engines": {
"node": ">=0.10.0"
@@ -272,9 +441,9 @@
"dev": true
},
"node_modules/fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"optional": true,
@@ -286,9 +455,12 @@
}
},
"node_modules/function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
},
"node_modules/glob": {
"version": "8.1.0",
@@ -309,15 +481,15 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/has": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
- "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "node_modules/hasown": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
+ "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==",
"dependencies": {
- "function-bind": "^1.1.1"
+ "function-bind": "^1.1.2"
},
"engines": {
- "node": ">= 0.4.0"
+ "node": ">= 0.4"
}
},
"node_modules/inflight": {
@@ -337,9 +509,9 @@
"dev": true
},
"node_modules/is-builtin-module": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.0.tgz",
- "integrity": "sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==",
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
+ "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
"dev": true,
"dependencies": {
"builtin-modules": "^3.3.0"
@@ -352,11 +524,11 @@
}
},
"node_modules/is-core-module": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
- "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
"dependencies": {
- "has": "^1.0.3"
+ "hasown": "^2.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -404,12 +576,12 @@
}
},
"node_modules/magic-string": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
- "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
+ "version": "0.30.5",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz",
+ "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==",
"dev": true,
"dependencies": {
- "@jridgewell/sourcemap-codec": "^1.4.13"
+ "@jridgewell/sourcemap-codec": "^1.4.15"
},
"engines": {
"node": ">=12"
@@ -462,9 +634,9 @@
}
},
"node_modules/prettier": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz",
- "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==",
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.3.tgz",
+ "integrity": "sha512-QNhUTBq+mqt1oH1dTfY3phOKNhcDdJkfttHI6u0kj7M2+c+7fmNKlgh2GhnHiqMcbxJ+a0j2igz/2jfl9QKLuw==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
@@ -504,11 +676,11 @@
}
},
"node_modules/resolve": {
- "version": "1.22.1",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
- "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dependencies": {
- "is-core-module": "^2.9.0",
+ "is-core-module": "^2.13.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
@@ -520,18 +692,34 @@
}
},
"node_modules/rollup": {
- "version": "3.28.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz",
- "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==",
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.5.tgz",
+ "integrity": "sha512-E4vQW0H/mbNMw2yLSqJyjtkHY9dslf/p0zuT1xehNRqUTBOFMqEjguDvqhXr7N7r/4ttb2jr4T41d3dncmIgbQ==",
"devOptional": true,
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
- "node": ">=14.18.0",
+ "node": ">=18.0.0",
"npm": ">=8.0.0"
},
"optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.9.5",
+ "@rollup/rollup-android-arm64": "4.9.5",
+ "@rollup/rollup-darwin-arm64": "4.9.5",
+ "@rollup/rollup-darwin-x64": "4.9.5",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.9.5",
+ "@rollup/rollup-linux-arm64-gnu": "4.9.5",
+ "@rollup/rollup-linux-arm64-musl": "4.9.5",
+ "@rollup/rollup-linux-riscv64-gnu": "4.9.5",
+ "@rollup/rollup-linux-x64-gnu": "4.9.5",
+ "@rollup/rollup-linux-x64-musl": "4.9.5",
+ "@rollup/rollup-win32-arm64-msvc": "4.9.5",
+ "@rollup/rollup-win32-ia32-msvc": "4.9.5",
+ "@rollup/rollup-win32-x64-msvc": "4.9.5",
"fsevents": "~2.3.2"
}
},
@@ -562,15 +750,15 @@
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/typescript": {
- "version": "4.9.5",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
- "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
+ "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
- "node": ">=4.2.0"
+ "node": ">=14.17"
}
},
"node_modules/wrappy": {
@@ -579,409 +767,5 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
}
- },
- "dependencies": {
- "@jridgewell/sourcemap-codec": {
- "version": "1.4.14",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
- "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==",
- "dev": true
- },
- "@reactpy/client": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.3.1.tgz",
- "integrity": "sha512-mvFwAvmRMgo7lTjkhkEJzBep6HX/wfm5BaNbtEMOUzto7G/h+z1AmqlOMXLH37DSI0iwfmCuNwy07EJM0JWZ0g==",
- "requires": {
- "event-to-object": "^0.1.2",
- "json-pointer": "^0.6.2"
- }
- },
- "@rollup/plugin-commonjs": {
- "version": "24.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
- "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==",
- "dev": true,
- "requires": {
- "@rollup/pluginutils": "^5.0.1",
- "commondir": "^1.0.1",
- "estree-walker": "^2.0.2",
- "glob": "^8.0.3",
- "is-reference": "1.2.1",
- "magic-string": "^0.27.0"
- }
- },
- "@rollup/plugin-node-resolve": {
- "version": "15.0.1",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz",
- "integrity": "sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==",
- "dev": true,
- "requires": {
- "@rollup/pluginutils": "^5.0.1",
- "@types/resolve": "1.20.2",
- "deepmerge": "^4.2.2",
- "is-builtin-module": "^3.2.0",
- "is-module": "^1.0.0",
- "resolve": "^1.22.1"
- }
- },
- "@rollup/plugin-replace": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz",
- "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==",
- "dev": true,
- "requires": {
- "@rollup/pluginutils": "^5.0.1",
- "magic-string": "^0.27.0"
- }
- },
- "@rollup/plugin-typescript": {
- "version": "11.1.2",
- "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-11.1.2.tgz",
- "integrity": "sha512-0ghSOCMcA7fl1JM+0gYRf+Q/HWyg+zg7/gDSc+fRLmlJWcW5K1I+CLRzaRhXf4Y3DRyPnnDo4M2ktw+a6JcDEg==",
- "requires": {
- "@rollup/pluginutils": "^5.0.1",
- "resolve": "^1.22.1"
- }
- },
- "@rollup/pluginutils": {
- "version": "5.0.2",
- "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.0.2.tgz",
- "integrity": "sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==",
- "requires": {
- "@types/estree": "^1.0.0",
- "estree-walker": "^2.0.2",
- "picomatch": "^2.3.1"
- }
- },
- "@types/estree": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz",
- "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ=="
- },
- "@types/prop-types": {
- "version": "15.7.5",
- "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
- "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==",
- "dev": true
- },
- "@types/react": {
- "version": "17.0.65",
- "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.65.tgz",
- "integrity": "sha512-oxur785xZYHvnI7TRS61dXbkIhDPnGfsXKv0cNXR/0ml4SipRIFpSMzA7HMEfOywFwJ5AOnPrXYTEiTRUQeGlQ==",
- "dev": true,
- "requires": {
- "@types/prop-types": "*",
- "@types/scheduler": "*",
- "csstype": "^3.0.2"
- }
- },
- "@types/react-dom": {
- "version": "17.0.20",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.20.tgz",
- "integrity": "sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA==",
- "dev": true,
- "requires": {
- "@types/react": "^17"
- }
- },
- "@types/resolve": {
- "version": "1.20.2",
- "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
- "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==",
- "dev": true
- },
- "@types/scheduler": {
- "version": "0.16.3",
- "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
- "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
- "dev": true
- },
- "balanced-match": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
- "dev": true
- },
- "brace-expansion": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
- "dev": true,
- "requires": {
- "balanced-match": "^1.0.0"
- }
- },
- "builtin-modules": {
- "version": "3.3.0",
- "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
- "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
- "dev": true
- },
- "commondir": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
- "dev": true
- },
- "csstype": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
- "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
- "dev": true
- },
- "deepmerge": {
- "version": "4.2.2",
- "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
- "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
- "dev": true
- },
- "estree-walker": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
- "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
- },
- "event-to-object": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/event-to-object/-/event-to-object-0.1.2.tgz",
- "integrity": "sha512-+fUmp1XOCZiYomwe5Zxp4IlchuZZfdVdjFUk5MbgRT4M+V2TEWKc0jJwKLCX/nxlJ6xM5VUb/ylzERh7YDCRrg==",
- "requires": {
- "json-pointer": "^0.6.2"
- }
- },
- "foreach": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
- "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg=="
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
- "dev": true
- },
- "fsevents": {
- "version": "2.3.2",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
- "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
- "dev": true,
- "optional": true
- },
- "function-bind": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
- "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
- },
- "glob": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
- "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
- "dev": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^5.0.1",
- "once": "^1.3.0"
- }
- },
- "has": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
- "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
- "requires": {
- "function-bind": "^1.1.1"
- }
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
- "dev": true,
- "requires": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "is-builtin-module": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.0.tgz",
- "integrity": "sha512-phDA4oSGt7vl1n5tJvTWooWWAsXLY+2xCnxNqvKhGEzujg+A43wPlPOyDg3C8XQHN+6k/JTQWJ/j0dQh/qr+Hw==",
- "dev": true,
- "requires": {
- "builtin-modules": "^3.3.0"
- }
- },
- "is-core-module": {
- "version": "2.11.0",
- "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
- "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
- "requires": {
- "has": "^1.0.3"
- }
- },
- "is-module": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
- "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==",
- "dev": true
- },
- "is-reference": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
- "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
- "dev": true,
- "requires": {
- "@types/estree": "*"
- }
- },
- "js-tokens": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
- "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "peer": true
- },
- "json-pointer": {
- "version": "0.6.2",
- "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz",
- "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==",
- "requires": {
- "foreach": "^2.0.4"
- }
- },
- "loose-envify": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
- "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "peer": true,
- "requires": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- }
- },
- "magic-string": {
- "version": "0.27.0",
- "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
- "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
- "dev": true,
- "requires": {
- "@jridgewell/sourcemap-codec": "^1.4.13"
- }
- },
- "minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
- "dev": true,
- "requires": {
- "brace-expansion": "^2.0.1"
- }
- },
- "object-assign": {
- "version": "4.1.1",
- "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
- "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "peer": true
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
- "dev": true,
- "requires": {
- "wrappy": "1"
- }
- },
- "path-parse": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
- "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="
- },
- "picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="
- },
- "prettier": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.2.tgz",
- "integrity": "sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==",
- "dev": true
- },
- "react": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz",
- "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==",
- "peer": true,
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
- }
- },
- "react-dom": {
- "version": "17.0.2",
- "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
- "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==",
- "peer": true,
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "scheduler": "^0.20.2"
- }
- },
- "resolve": {
- "version": "1.22.1",
- "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
- "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
- "requires": {
- "is-core-module": "^2.9.0",
- "path-parse": "^1.0.7",
- "supports-preserve-symlinks-flag": "^1.0.0"
- }
- },
- "rollup": {
- "version": "3.28.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.28.1.tgz",
- "integrity": "sha512-R9OMQmIHJm9znrU3m3cpE8uhN0fGdXiawME7aZIpQqvpS/85+Vt1Hq1/yVIcYfOmaQiHjvXkQAoJukvLpau6Yw==",
- "devOptional": true,
- "requires": {
- "fsevents": "~2.3.2"
- }
- },
- "scheduler": {
- "version": "0.20.2",
- "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
- "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
- "peer": true,
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
- }
- },
- "supports-preserve-symlinks-flag": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
- "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="
- },
- "tslib": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
- "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
- },
- "typescript": {
- "version": "4.9.5",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
- "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g=="
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
- "dev": true
- }
}
}
diff --git a/src/js/package.json b/src/js/package.json
index 0c61ec46..d4d177b2 100644
--- a/src/js/package.json
+++ b/src/js/package.json
@@ -1,27 +1,24 @@
{
- "description": "reactpy-django client",
- "main": "src/index.ts",
+ "description": "ReactPy-Django Client",
+ "main": "src/index.tsx",
"type": "module",
- "files": [
- "src/**/*.js"
- ],
"scripts": {
"build": "rollup --config",
"format": "prettier --ignore-path .gitignore --write ."
},
"devDependencies": {
- "@rollup/plugin-commonjs": "^24.0.1",
- "@rollup/plugin-node-resolve": "^15.0.1",
- "@rollup/plugin-replace": "^5.0.2",
- "@types/react": "^17.0",
- "@types/react-dom": "^17.0",
- "typescript": "^4.9.5",
- "prettier": "^3.0.2",
- "rollup": "^3.28.1"
+ "@rollup/plugin-commonjs": "^25.0.7",
+ "@rollup/plugin-node-resolve": "^15.2.3",
+ "@rollup/plugin-replace": "^5.0.5",
+ "@types/react": "^18.2.48",
+ "@types/react-dom": "^18.2.18",
+ "prettier": "^3.2.3",
+ "rollup": "^4.9.5",
+ "typescript": "^5.3.3"
},
"dependencies": {
"@reactpy/client": "^0.3.1",
- "@rollup/plugin-typescript": "^11.1.2",
+ "@rollup/plugin-typescript": "^11.1.6",
"tslib": "^2.6.2"
}
}
diff --git a/src/js/rollup.config.mjs b/src/js/rollup.config.mjs
index 79f93839..233de631 100644
--- a/src/js/rollup.config.mjs
+++ b/src/js/rollup.config.mjs
@@ -4,7 +4,7 @@ import replace from "@rollup/plugin-replace";
import typescript from "@rollup/plugin-typescript";
export default {
- input: "src/index.ts",
+ input: "src/index.tsx",
output: {
file: "../reactpy_django/static/reactpy_django/client.js",
format: "esm",
diff --git a/src/js/src/client.ts b/src/js/src/client.ts
index 6966a0f0..87a43155 100644
--- a/src/js/src/client.ts
+++ b/src/js/src/client.ts
@@ -12,6 +12,9 @@ export class ReactPyDjangoClient
{
urls: ReactPyUrls;
socket: { current?: WebSocket };
+ mountElement: HTMLElement | null = null;
+ prerenderElement: HTMLElement | null = null;
+ offlineElement: HTMLElement | null = null;
constructor(props: ReactPyDjangoClientProps) {
super();
@@ -22,7 +25,28 @@ export class ReactPyDjangoClient
onMessage: async ({ data }) =>
this.handleIncoming(JSON.parse(data)),
...props.reconnectOptions,
+ onClose: () => {
+ // If offlineElement exists, show it and hide the mountElement/prerenderElement
+ if (this.prerenderElement) {
+ this.prerenderElement.remove();
+ this.prerenderElement = null;
+ }
+ if (this.offlineElement) {
+ this.mountElement.hidden = true;
+ this.offlineElement.hidden = false;
+ }
+ },
+ onOpen: () => {
+ // If offlineElement exists, hide it and show the mountElement
+ if (this.offlineElement) {
+ this.offlineElement.hidden = true;
+ this.mountElement.hidden = false;
+ }
+ },
});
+ this.mountElement = props.mountElement;
+ this.prerenderElement = props.prerenderElement;
+ this.offlineElement = props.offlineElement;
}
sendMessage(message: any): void {
diff --git a/src/js/src/index.ts b/src/js/src/index.tsx
similarity index 72%
rename from src/js/src/index.ts
rename to src/js/src/index.tsx
index 97b20efd..7bb4bfd4 100644
--- a/src/js/src/index.ts
+++ b/src/js/src/index.tsx
@@ -1,5 +1,8 @@
-import { mount } from "./mount";
+
import { ReactPyDjangoClient } from "./client";
+import React from "react";
+import { render } from "react-dom";
+import { Layout } from "@reactpy/client/src/components";
export function mountComponent(
mountElement: HTMLElement,
@@ -59,8 +62,24 @@ export function mountComponent(
backoffMultiplier: reconnectBackoffMultiplier,
maxRetries: reconnectMaxRetries,
},
+ mountElement: mountElement,
+ prerenderElement: document.getElementById(
+ mountElement.id + "-prerender"
+ ),
+ offlineElement: document.getElementById(mountElement.id + "-offline"),
});
+
+ // Replace the prerender element with the real element on the first layout update
+ if (client.prerenderElement) {
+ client.onMessage("layout-update", ({ path, model }) => {
+ if (client.prerenderElement) {
+ client.prerenderElement.replaceWith(client.mountElement);
+ client.prerenderElement = null;
+ }
+ });
+ }
+
// Start rendering the component
- mount(mountElement, client);
+ render( , client.mountElement);
}
diff --git a/src/js/src/mount.tsx b/src/js/src/mount.tsx
deleted file mode 100644
index 4d7cdcb3..00000000
--- a/src/js/src/mount.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-import React from "react";
-import { render } from "react-dom";
-import { Layout } from "@reactpy/client/src/components";
-import { ReactPyDjangoClient } from "./client";
-
-export function mount(element: HTMLElement, client: ReactPyDjangoClient): void {
- const prerenderElement = document.getElementById(element.id + "-prerender");
- if (prerenderElement) {
- element.hidden = true;
- client.onMessage("layout-update", ({ path, model }) => {
- if (prerenderElement) {
- prerenderElement.replaceWith(element);
- element.hidden = false;
- }
- });
- }
- render( , element);
-}
diff --git a/src/js/src/types.ts b/src/js/src/types.ts
index b31276bc..3b7f3431 100644
--- a/src/js/src/types.ts
+++ b/src/js/src/types.ts
@@ -14,4 +14,7 @@ export type ReactPyUrls = {
export type ReactPyDjangoClientProps = {
urls: ReactPyUrls;
reconnectOptions: ReconnectOptions;
+ mountElement: HTMLElement | null;
+ prerenderElement: HTMLElement | null;
+ offlineElement: HTMLElement | null;
};
diff --git a/src/js/src/utils.ts b/src/js/src/utils.ts
index 56e231e2..0d38b94b 100644
--- a/src/js/src/utils.ts
+++ b/src/js/src/utils.ts
@@ -32,14 +32,14 @@ export function createReconnectingWebSocket(props: {
};
socket.current.onmessage = props.onMessage;
socket.current.onclose = () => {
+ if (props.onClose) {
+ props.onClose();
+ }
if (!everConnected) {
console.info("ReactPy failed to connect!");
return;
}
console.info("ReactPy disconnected!");
- if (props.onClose) {
- props.onClose();
- }
if (retries >= maxRetries) {
console.info("ReactPy connection max retries exhausted!");
return;
diff --git a/src/js/tsconfig.json b/src/js/tsconfig.json
index 7e5ec6cb..277a7d1a 100644
--- a/src/js/tsconfig.json
+++ b/src/js/tsconfig.json
@@ -1,9 +1,10 @@
{
"compilerOptions": {
- "target": "ES2017",
+ "target": "ES2022",
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
+ "allowSyntheticDefaultImports": true
},
"paths": {
"react": [
diff --git a/src/reactpy_django/decorators.py b/src/reactpy_django/decorators.py
index d06162a5..59c110b3 100644
--- a/src/reactpy_django/decorators.py
+++ b/src/reactpy_django/decorators.py
@@ -32,7 +32,7 @@ def auth_required(
warn(
"auth_required is deprecated and will be removed in the next major version. "
- "An equivalent to this decorator's default is @user_passes_test('is_active').",
+ "An equivalent to this decorator's default is @user_passes_test(lambda user: user.is_active).",
DeprecationWarning,
)
diff --git a/src/reactpy_django/exceptions.py b/src/reactpy_django/exceptions.py
index f52d0590..412d647f 100644
--- a/src/reactpy_django/exceptions.py
+++ b/src/reactpy_django/exceptions.py
@@ -6,6 +6,10 @@ class ComponentDoesNotExistError(AttributeError):
...
+class OfflineComponentMissing(ComponentDoesNotExistError):
+ ...
+
+
class InvalidHostError(ValueError):
...
diff --git a/src/reactpy_django/templates/reactpy/component.html b/src/reactpy_django/templates/reactpy/component.html
index 052dc517..e7ff1311 100644
--- a/src/reactpy_django/templates/reactpy/component.html
+++ b/src/reactpy_django/templates/reactpy/component.html
@@ -5,7 +5,6 @@
{% endif %}
{% if not reactpy_failure %}
-{% if reactpy_prerender_html %}{{ reactpy_prerender_html|safe }}
{% endif %}
+{% if reactpy_prerender_html %}{{ reactpy_prerender_html|safe }}
{% endif %}
+{% if reactpy_offline_html %}{{ reactpy_offline_html|safe }}
+{% endif %}
{% endif %}
diff --git a/src/reactpy_django/templatetags/reactpy.py b/src/reactpy_django/templatetags/reactpy.py
index 209c0ec8..8c175bc2 100644
--- a/src/reactpy_django/templatetags/reactpy.py
+++ b/src/reactpy_django/templatetags/reactpy.py
@@ -19,6 +19,7 @@
ComponentDoesNotExistError,
ComponentParamError,
InvalidHostError,
+ OfflineComponentMissing,
)
from reactpy_django.types import ComponentParams
from reactpy_django.utils import SyncLayout, validate_component_args
@@ -38,6 +39,7 @@ def component(
*args,
host: str | None = None,
prerender: str = str(config.REACTPY_PRERENDER),
+ offline: str = "",
**kwargs,
):
"""This tag is used to embed an existing ReactPy component into your HTML template.
@@ -55,6 +57,7 @@ def component(
Example values include: `localhost:8000`, `example.com`, `example.com/subdir`
prerender: Configures whether to pre-render this component, which \
enables SEO compatibility and reduces perceived latency.
+ offline: The dotted path to the component to render when the client is offline.
**kwargs: The keyword arguments to provide to the component.
Example ::
@@ -79,6 +82,7 @@ def component(
component_has_args = args or kwargs
user_component: ComponentConstructor | None = None
_prerender_html = ""
+ _offline_html = ""
# Validate the host
if host and config.REACTPY_DEBUG_MODE:
@@ -133,6 +137,22 @@ def component(
return failure_context(dotted_path, ComponentCarrierError(msg))
_prerender_html = prerender_component(user_component, args, kwargs, request)
+ # Fetch the offline component's HTML, if requested
+ if offline:
+ offline_component = config.REACTPY_REGISTERED_COMPONENTS.get(offline)
+ if not offline_component:
+ msg = f"Cannot render offline component '{offline}'. It is not registered as a component."
+ _logger.error(msg)
+ return failure_context(dotted_path, OfflineComponentMissing(msg))
+ if not request:
+ msg = (
+ "Cannot render an offline component without a HTTP request. Are you missing the "
+ "request context processor in settings.py:TEMPLATES['OPTIONS']['context_processors']?"
+ )
+ _logger.error(msg)
+ return failure_context(dotted_path, ComponentCarrierError(msg))
+ _offline_html = prerender_component(offline_component, [], {}, request)
+
# Return the template rendering context
return {
"reactpy_class": class_,
@@ -148,6 +168,7 @@ def component(
"reactpy_reconnect_backoff_multiplier": config.REACTPY_RECONNECT_BACKOFF_MULTIPLIER,
"reactpy_reconnect_max_retries": config.REACTPY_RECONNECT_MAX_RETRIES,
"reactpy_prerender_html": _prerender_html,
+ "reactpy_offline_html": _offline_html,
}
diff --git a/src/reactpy_django/utils.py b/src/reactpy_django/utils.py
index 10df2df9..624c4892 100644
--- a/src/reactpy_django/utils.py
+++ b/src/reactpy_django/utils.py
@@ -33,16 +33,18 @@
_logger = logging.getLogger(__name__)
_component_tag = r"(?Pcomponent)"
-_component_path = r"(?P\"[^\"'\s]+\"|'[^\"'\s]+')"
-_component_kwargs = r"(?P[\s\S]*?)"
+_component_path = r"""(?P"[^"'\s]+"|'[^"'\s]+')"""
+_component_offline_kwarg = (
+ rf"""(\s*offline\s*=\s*{_component_path.replace(r"", r"")})"""
+)
+_component_generic_kwarg = r"""(\s*.*?)"""
COMMENT_REGEX = re.compile(r"")
COMPONENT_REGEX = re.compile(
r"{%\s*"
+ _component_tag
+ r"\s*"
+ _component_path
- + r"\s*"
- + _component_kwargs
+ + rf"({_component_offline_kwarg}|{_component_generic_kwarg})*?"
+ r"\s*%}"
)
DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
@@ -198,11 +200,17 @@ def get_components(self, templates: set[str]) -> set[str]:
with open(template, "r", encoding="utf-8") as template_file:
clean_template = COMMENT_REGEX.sub("", template_file.read())
regex_iterable = COMPONENT_REGEX.finditer(clean_template)
- component_paths = [
- match.group("path").replace('"', "").replace("'", "")
- for match in regex_iterable
- ]
- components.update(component_paths)
+ new_components: list[str] = []
+ for match in regex_iterable:
+ new_components.append(
+ match.group("path").replace('"', "").replace("'", "")
+ )
+ offline_path = match.group("offline_path")
+ if offline_path:
+ new_components.append(
+ offline_path.replace('"', "").replace("'", "")
+ )
+ components.update(new_components)
if not components:
_logger.warning(
"\033[93m"
diff --git a/tests/test_app/offline/__init__.py b/tests/test_app/offline/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/tests/test_app/offline/components.py b/tests/test_app/offline/components.py
new file mode 100644
index 00000000..daa7238d
--- /dev/null
+++ b/tests/test_app/offline/components.py
@@ -0,0 +1,15 @@
+from reactpy import component, html
+
+
+@component
+def online():
+ return html.div(
+ {"id": "online"},
+ "This is the ONLINE component. "
+ "Shut down your webserver and check if the offline component appears.",
+ )
+
+
+@component
+def offline():
+ return html.div({"id": "offline"}, "Offline")
diff --git a/tests/test_app/offline/urls.py b/tests/test_app/offline/urls.py
new file mode 100644
index 00000000..c9b8a236
--- /dev/null
+++ b/tests/test_app/offline/urls.py
@@ -0,0 +1,7 @@
+from django.urls import path
+
+from .views import offline
+
+urlpatterns = [
+ path("offline/", offline),
+]
diff --git a/tests/test_app/offline/views.py b/tests/test_app/offline/views.py
new file mode 100644
index 00000000..ae33caed
--- /dev/null
+++ b/tests/test_app/offline/views.py
@@ -0,0 +1,5 @@
+from django.shortcuts import render
+
+
+def offline(request):
+ return render(request, "offline.html", {})
diff --git a/tests/test_app/templates/offline.html b/tests/test_app/templates/offline.html
new file mode 100644
index 00000000..e7c39106
--- /dev/null
+++ b/tests/test_app/templates/offline.html
@@ -0,0 +1,20 @@
+{% load static %} {% load reactpy %}
+
+
+
+
+
+
+
+
+ ReactPy
+
+
+
+ ReactPy Offline Test Page
+
+ {% component "test_app.offline.components.online" offline="test_app.offline.components.offline" %}
+
+
+
+
diff --git a/tests/test_app/tests/test_components.py b/tests/test_app/tests/test_components.py
index ca35cf50..61a0ca86 100644
--- a/tests/test_app/tests/test_components.py
+++ b/tests/test_app/tests/test_components.py
@@ -46,12 +46,18 @@ def setUpClass(cls):
cls._server_process.ready.wait()
cls._port = cls._server_process.port.value
- # Open the second server process
+ # Open the second server process, used for testing custom hosts
cls._server_process2 = cls.ProtocolServerProcess(cls.host, get_application)
cls._server_process2.start()
cls._server_process2.ready.wait()
cls._port2 = cls._server_process2.port.value
+ # Open the third server process, used for testing offline fallback
+ cls._server_process3 = cls.ProtocolServerProcess(cls.host, get_application)
+ cls._server_process3.start()
+ cls._server_process3.ready.wait()
+ cls._port3 = cls._server_process3.port.value
+
# Open a Playwright browser window
if sys.platform == "win32":
asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
@@ -67,9 +73,11 @@ def tearDownClass(cls):
# Close the Playwright browser
cls.playwright.stop()
- # Close the second server process
+ # Close the other server processes
cls._server_process2.terminate()
cls._server_process2.join()
+ cls._server_process3.terminate()
+ cls._server_process3.join()
# Repurposed from ChannelsLiveServerTestCase._post_teardown
cls._server_process.terminate()
@@ -600,3 +608,20 @@ def test_url_router(self):
finally:
new_page.close()
+
+ def test_offline_components(self):
+ new_page = self.browser.new_page()
+ try:
+ server3_url = self.live_server_url.replace(
+ str(self._port), str(self._port3)
+ )
+ new_page.goto(f"{server3_url}/offline/")
+ new_page.wait_for_selector("div:not([hidden]) > #online")
+ self.assertIsNotNone(new_page.query_selector("div[hidden] > #offline"))
+ self._server_process3.terminate()
+ self._server_process3.join()
+ new_page.wait_for_selector("div:not([hidden]) > #offline")
+ self.assertIsNotNone(new_page.query_selector("div[hidden] > #online"))
+
+ finally:
+ new_page.close()
diff --git a/tests/test_app/tests/test_regex.py b/tests/test_app/tests/test_regex.py
index 61b72e4a..07a0dbfd 100644
--- a/tests/test_app/tests/test_regex.py
+++ b/tests/test_app/tests/test_regex.py
@@ -1,5 +1,6 @@
-from django.test import TestCase
+import re
+from django.test import TestCase
from reactpy_django.utils import COMMENT_REGEX, COMPONENT_REGEX
@@ -28,6 +29,15 @@ def test_component_regex(self):
%}""", # noqa: W291
COMPONENT_REGEX,
)
+ self.assertRegex(r'{% component "my.component" my_object %}', COMPONENT_REGEX)
+ self.assertRegex(
+ r'{% component "my.component" class="example-cls" x=123 y=456 %}',
+ COMPONENT_REGEX,
+ )
+ self.assertRegex(
+ r'{% component "my.component" class = "example-cls" %}',
+ COMPONENT_REGEX,
+ )
# Fake component matches
self.assertNotRegex(r'{% not_a_real_thing "my.component" %}', COMPONENT_REGEX)
@@ -134,3 +144,26 @@ def test_comment_regex(self):
),
"",
)
+
+ def test_offline_component_regex(self):
+ regex = re.compile(COMPONENT_REGEX)
+ # Check if "offline_path" group is present and equals to "my_offline_path"
+ search = regex.search(
+ r'{% component "my.component" offline="my_offline_path" %}'
+ )
+ self.assertTrue(search["offline_path"] == '"my_offline_path"') # type: ignore
+
+ search = regex.search(
+ r'{% component "my.component" arg_1="1" offline="my_offline_path" arg_2="2" %}'
+ )
+ self.assertTrue(search["offline_path"] == '"my_offline_path"') # type: ignore
+
+ search = regex.search(
+ r'{% component "my.component" offline="my_offline_path" arg_2="2" %}'
+ )
+
+ self.assertTrue(search["offline_path"] == '"my_offline_path"') # type: ignore
+ search = regex.search(
+ r'{% component "my.component" arg_1="1" offline="my_offline_path" %}'
+ )
+ self.assertTrue(search["offline_path"] == '"my_offline_path"') # type: ignore
diff --git a/tests/test_app/urls.py b/tests/test_app/urls.py
index 50cc5999..d15a2817 100644
--- a/tests/test_app/urls.py
+++ b/tests/test_app/urls.py
@@ -30,6 +30,7 @@
path("", include("test_app.prerender.urls")),
path("", include("test_app.performance.urls")),
path("", include("test_app.router.urls")),
+ path("", include("test_app.offline.urls")),
path("reactpy/", include("reactpy_django.http.urls")),
path("admin/", admin.site.urls),
]