diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 000000000..3fc786dff
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+@maintainers
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index efa7d4918..21f192233 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,5 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Start a Discussion
- url: https://github.com/idom-team/idom/discussions
+ url: https://github.com/reactive-python/reactpy/discussions
about: Report issues, request features, ask questions, and share ideas
diff --git a/.github/ISSUE_TEMPLATE/issue-form.yml b/.github/ISSUE_TEMPLATE/issue-form.yml
index 342316ad2..3cad28d11 100644
--- a/.github/ISSUE_TEMPLATE/issue-form.yml
+++ b/.github/ISSUE_TEMPLATE/issue-form.yml
@@ -1,6 +1,6 @@
name: Plan a Task
description: Create a detailed plan of action (ONLY START AFTER DISCUSSION PLEASE đ).
-labels: ["flag: triage"]
+labels: ["flag-triage"]
body:
- type: textarea
attributes:
@@ -14,10 +14,3 @@ body:
description: Describe what ought to be done, and why that will address the reasons for action mentioned above.
validations:
required: false
-- type: textarea
- attributes:
- label: Work Items
- description: |
- An itemized list or detailed description of the work to be done to based on the proposed actions above.
- validations:
- required: false
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 000000000..a55532008
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,14 @@
+## Description
+
+
+
+## Checklist
+
+Please update this checklist as you complete each item:
+
+- [ ] Tests have been developed for bug fixes or new functionality.
+- [ ] The changelog has been updated, if necessary.
+- [ ] Documentation has been updated, if necessary.
+- [ ] GitHub Issues closed by this PR have been linked.
+
+By submitting this pull request I agree that all contributions comply with this project's open source license(s).
diff --git a/.github/workflows/.hatch-run.yml b/.github/workflows/.hatch-run.yml
new file mode 100644
index 000000000..1b21e4202
--- /dev/null
+++ b/.github/workflows/.hatch-run.yml
@@ -0,0 +1,59 @@
+name: hatch-run
+
+on:
+ workflow_call:
+ inputs:
+ job-name:
+ required: true
+ type: string
+ hatch-run:
+ required: true
+ type: string
+ runs-on-array:
+ required: false
+ type: string
+ default: '["ubuntu-latest"]'
+ python-version-array:
+ required: false
+ type: string
+ default: '["3.x"]'
+ node-registry-url:
+ required: false
+ type: string
+ default: ""
+ secrets:
+ node-auth-token:
+ required: false
+ pypi-username:
+ required: false
+ pypi-password:
+ required: false
+
+jobs:
+ hatch:
+ name: ${{ format(inputs.job-name, matrix.python-version, matrix.runs-on) }}
+ strategy:
+ matrix:
+ python-version: ${{ fromJson(inputs.python-version-array) }}
+ runs-on: ${{ fromJson(inputs.runs-on-array) }}
+ runs-on: ${{ matrix.runs-on }}
+ steps:
+ - uses: actions/checkout@v2
+ - uses: actions/setup-node@v2
+ with:
+ node-version: "14.x"
+ registry-url: ${{ inputs.node-registry-url }}
+ - name: Pin NPM Version
+ run: npm install -g npm@8.19.3
+ - name: Use Python ${{ matrix.python-version }}
+ uses: actions/setup-python@v2
+ with:
+ python-version: ${{ matrix.python-version }}
+ - name: Install Python Dependencies
+ run: pip install hatch poetry
+ - name: Run Scripts
+ env:
+ NODE_AUTH_TOKEN: ${{ secrets.node-auth-token }}
+ PYPI_USERNAME: ${{ secrets.pypi-username }}
+ PYPI_PASSWORD: ${{ secrets.pypi-password }}
+ run: hatch run ${{ inputs.hatch-run }}
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
new file mode 100644
index 000000000..d370ea129
--- /dev/null
+++ b/.github/workflows/check.yml
@@ -0,0 +1,48 @@
+name: check
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ branches:
+ - main
+ schedule:
+ - cron: "0 0 * * 0"
+
+jobs:
+ test-py-cov:
+ uses: ./.github/workflows/.hatch-run.yml
+ with:
+ job-name: "python-{0}"
+ hatch-run: "test-py"
+ lint-py:
+ uses: ./.github/workflows/.hatch-run.yml
+ with:
+ job-name: "python-{0}"
+ hatch-run: "lint-py"
+ test-py-matrix:
+ uses: ./.github/workflows/.hatch-run.yml
+ with:
+ job-name: "python-{0} {1}"
+ hatch-run: "test-py --no-cov"
+ runs-on-array: '["ubuntu-latest", "macos-latest", "windows-latest"]'
+ python-version-array: '["3.9", "3.10", "3.11"]'
+ test-docs:
+ uses: ./.github/workflows/.hatch-run.yml
+ with:
+ job-name: "python-{0}"
+ hatch-run: "test-docs"
+ # as of Dec 2023 lxml does have wheels for 3.12
+ # https://bugs.launchpad.net/lxml/+bug/2040440
+ python-version-array: '["3.11"]'
+ test-js:
+ uses: ./.github/workflows/.hatch-run.yml
+ with:
+ job-name: "{1}"
+ hatch-run: "test-js"
+ lint-js:
+ uses: ./.github/workflows/.hatch-run.yml
+ with:
+ job-name: "{1}"
+ hatch-run: "lint-js"
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index 830092f76..b4f77ee00 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -9,16 +9,16 @@
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
-name: "CodeQL"
+name: codeql
on:
push:
- branches: [ main ]
+ branches: [main]
pull_request:
# The branches below must be a subset of the branches above
- branches: [ main ]
+ branches: [main]
schedule:
- - cron: '43 3 * * 3'
+ - cron: "43 3 * * 3"
jobs:
analyze:
@@ -32,40 +32,40 @@ jobs:
strategy:
fail-fast: false
matrix:
- language: [ 'javascript', 'python' ]
+ language: ["javascript", "python"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
# Learn more:
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
steps:
- - name: Checkout repository
- uses: actions/checkout@v2
+ - name: Checkout repository
+ uses: actions/checkout@v2
- # Initializes the CodeQL tools for scanning.
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v1
- with:
- languages: ${{ matrix.language }}
- # If you wish to specify custom queries, you can do so here or in a config file.
- # By default, queries listed here will override any specified in a config file.
- # Prefix the list here with "+" to use these queries and those in the config file.
- # queries: ./path/to/local/query, your-org/your-repo/queries@main
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
- # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
- # If this step fails, then you should remove it and run the build manually (see below)
- - name: Autobuild
- uses: github/codeql-action/autobuild@v1
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
- # âšī¸ Command-line programs to run using the OS shell.
- # đ https://git.io/JvXDl
+ # âšī¸ Command-line programs to run using the OS shell.
+ # đ https://git.io/JvXDl
- # âī¸ If the Autobuild fails above, remove it and uncomment the following three lines
- # and modify them (or add more) to build your code if your project
- # uses a compiled language
+ # âī¸ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
- #- run: |
- # make bootstrap
- # make release
+ #- run: |
+ # make bootstrap
+ # make release
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml
index 3de3cff17..7337f505b 100644
--- a/.github/workflows/deploy-docs.yml
+++ b/.github/workflows/deploy-docs.yml
@@ -1,7 +1,7 @@
# This workflows will upload a Python Package using Twine when a release is created
# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
-name: Deploy Documentation
+name: deploy-docs
on:
push:
diff --git a/.github/workflows/publish-js.yml b/.github/workflows/publish-js.yml
deleted file mode 100644
index 8b9f1369e..000000000
--- a/.github/workflows/publish-js.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-# This workflows will upload a Javscript Package using NPM to npmjs.org when a release is created
-# For more information see: https://docs.github.com/en/actions/guides/publishing-nodejs-packages
-
-name: Publish Javascript
-
-on:
- release:
- types: [published]
-
-jobs:
- build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- # Setup .npmrc file to publish to npm
- - uses: actions/setup-node@v2
- with:
- node-version: "14.x"
- registry-url: "https://registry.npmjs.org"
- - name: Install Specific NPM Version
- run: npm install -g npm@8.3
- - name: Prepare Release
- working-directory: ./src/client
- run: |
- npm install -g npm@7.22.0
- npm install
- - name: Publish Release
- working-directory: ./src/client
- run: npm run publish
- env:
- NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTOMATION_TOKEN }}
diff --git a/.github/workflows/publish-py.yml b/.github/workflows/publish-py.yml
deleted file mode 100644
index 799f5d060..000000000
--- a/.github/workflows/publish-py.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-# This workflows will upload a Python Package using Twine when a release is created
-# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
-
-name: Publish Python
-
-on:
- release:
- types: [published]
-
-jobs:
- release-package:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: "14.x"
- - name: Install Specific NPM Version
- run: npm install -g npm@8.3
- - name: Set up Python
- uses: actions/setup-python@v1
- with:
- python-version: "3.x"
- - name: Install latest NPM
- run: |
- npm install -g npm@7.22.0
- npm --version
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install -r requirements/build-pkg.txt
- - name: Build and publish
- env:
- TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
- TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
- run: |
- python -m build --sdist --wheel --outdir dist .
- twine upload dist/*
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
new file mode 100644
index 000000000..e9271cbd5
--- /dev/null
+++ b/.github/workflows/publish.yml
@@ -0,0 +1,20 @@
+# This workflows will upload a Javscript Package using NPM to npmjs.org when a release is created
+# For more information see: https://docs.github.com/en/actions/guides/publishing-nodejs-packages
+
+name: publish
+
+on:
+ release:
+ types: [published]
+
+jobs:
+ publish:
+ uses: ./.github/workflows/.hatch-run.yml
+ with:
+ job-name: "publish"
+ hatch-run: "publish"
+ node-registry-url: "https://registry.npmjs.org"
+ secrets:
+ node-auth-token: ${{ secrets.NODE_AUTH_TOKEN }}
+ pypi-username: ${{ secrets.PYPI_USERNAME }}
+ pypi-password: ${{ secrets.PYPI_PASSWORD }}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index 239281f79..000000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,87 +0,0 @@
-name: Test
-
-on:
- push:
- branches:
- - main
- pull_request:
- branches:
- - main
- schedule:
- - cron: "0 0 * * *"
-
-jobs:
- test-python-coverage:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: nanasess/setup-chromedriver@master
- - uses: actions/setup-node@v2
- with:
- node-version: "14.x"
- - name: Install Specific NPM Version
- run: npm install -g npm@8.3
- - name: Use Latest Python
- uses: actions/setup-python@v2
- with:
- python-version: "3.9"
- - name: Install Python Dependencies
- run: pip install -r requirements/nox-deps.txt
- - name: Run Tests
- env: { "CI": "true" }
- run: nox -s test_python_suite -- --headless --maxfail=3
- test-python-environments:
- runs-on: ${{ matrix.os }}
- strategy:
- matrix:
- python-version: ["3.7", "3.8", "3.9"]
- os: [ubuntu-latest, macos-latest, windows-latest]
- steps:
- - uses: actions/checkout@v2
- - uses: nanasess/setup-chromedriver@master
- - uses: actions/setup-node@v2
- with:
- node-version: "14.x"
- - name: Install Specific NPM Version
- run: npm install -g npm@8.3
- - name: Use Python ${{ matrix.python-version }}
- uses: actions/setup-python@v2
- with:
- python-version: ${{ matrix.python-version }}
- - name: Install Python Dependencies
- run: pip install -r requirements/nox-deps.txt
- - name: Run Tests
- env: { "CI": "true" }
- run: nox -s test_python --stop-on-first-error -- --headless --maxfail=3 --no-cov
- test-docs:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: "14.x"
- - name: Install Specific NPM Version
- run: npm install -g npm@8.3
- - name: Use Latest Python
- uses: actions/setup-python@v2
- with:
- python-version: "3.9"
- - name: Install Python Dependencies
- run: pip install -r requirements/nox-deps.txt
- - name: Run Tests
- env: { "CI": "true" }
- run: nox -s test_docs
- test-javascript:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
- - uses: actions/setup-node@v2
- with:
- node-version: "14.x"
- - name: Install Specific NPM Version
- run: npm install -g npm@8.3
- - name: Install Python Dependencies
- run: pip install -r requirements/nox-deps.txt
- - name: Run Tests
- env: { "CI": "true" }
- run: nox -s test_javascript
diff --git a/.gitignore b/.gitignore
index 75485ddbf..946bff43f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@
.jupyter
# --- Python ---
+.hatch
.venv
venv
MANIFEST
@@ -37,6 +38,5 @@ pip-wheel-metadata
.vscode
# --- JS ---
-
node_modules
-src/idom/client
+
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index f602709ee..0383cbb1d 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,18 +1,34 @@
repos:
- - repo: https://github.com/ambv/black
- rev: 22.1.0
+ - repo: local
hooks:
- - id: black
- - repo: https://github.com/PyCQA/flake8
- rev: 3.7.9
+ - id: lint-py-fix
+ name: Fix Python Lint
+ entry: hatch run lint-py
+ language: system
+ args: [--fix]
+ pass_filenames: false
+ files: \.py$
+ - repo: local
hooks:
- - id: flake8
- - repo: https://github.com/pycqa/isort
- rev: 5.6.3
+ - id: lint-js-fix
+ name: Fix JS Lint
+ entry: hatch run lint-js --fix
+ language: system
+ pass_filenames: false
+ files: \.(js|jsx|ts|tsx)$
+ - repo: local
hooks:
- - id: isort
- name: isort
- - repo: https://github.com/pre-commit/mirrors-prettier
- rev: "v2.5.1"
+ - id: lint-py-check
+ name: Check Python Lint
+ entry: hatch run lint-py
+ language: system
+ pass_filenames: false
+ files: \.py$
+ - repo: local
hooks:
- - id: prettier
+ - id: lint-js-check
+ name: Check JS Lint
+ entry: hatch run lint-py
+ language: system
+ pass_filenames: false
+ files: \.(js|jsx|ts|tsx)$
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
new file mode 100644
index 000000000..7471953dc
--- /dev/null
+++ b/.vscode/extensions.json
@@ -0,0 +1,12 @@
+{
+ "recommendations": [
+ "wholroyd.jinja",
+ "esbenp.prettier-vscode",
+ "ms-python.vscode-pylance",
+ "ms-python.python",
+ "charliermarsh.ruff",
+ "dbaeumer.vscode-eslint",
+ "ms-python.black-formatter",
+ "ms-python.mypy-type-checker"
+ ]
+}
diff --git a/LICENSE b/LICENSE
index 5caf76c93..060079c01 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2019-2022 Ryan S. Morshead
+Copyright (c) 2019 Ryan S. Morshead
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 39ea6e477..000000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,3 +0,0 @@
-recursive-include src/idom/client *
-recursive-include src/idom/web/templates *
-include src/idom/py.typed
diff --git a/README.md b/README.md
index c4a3de93c..83241e19a 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,72 @@
-# IDOM · [](https://github.com/idom-team/idom/actions?query=workflow%3ATest) [](https://pypi.python.org/pypi/idom) [](https://github.com/idom-team/idom/blob/main/LICENSE)
+# ReactPy
-IDOM is a Python web framework for building **interactive websites without needing a
-single line of Javascript**. These sites are built from small elements of functionality
-like buttons text and images. IDOM allows you to combine these elements into reusable
-"components" that can be composed together to create complex views.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-Ecosystem independence is also a core feature of IDOM. It can be added to existing
-applications built on a variety of sync and async web servers, as well as integrated
-with other frameworks like Django, Jupyter, and Plotly Dash. Not only does this mean
-you're free to choose what technology stack to run on, but on top of that, you can run
-the exact same components wherever you need them. For example, you can take a component
-originally developed in a Jupyter Notebook and embed it in your production application
-without changing anything about the component itself.
+
+[ReactPy](https://reactpy.dev/) is a library for building user interfaces in Python without Javascript. ReactPy interfaces are made from components that look and behave similar to those found in [ReactJS](https://reactjs.org/). Designed with simplicity in mind, ReactPy can be used by those without web development experience while also being powerful enough to grow with your ambitions.
+
+
# At a Glance
-To get a rough idea of how to write apps in IDOM, take a look at the tiny "hello
-world" application below:
+To get a rough idea of how to write apps in ReactPy, take a look at this tiny _Hello World_ application.
```python
-from idom import component, html, run
+from reactpy import component, html, run
@component
-def App():
+def hello_world():
return html.h1("Hello, World!")
-run(App)
+run(hello_world)
```
# Resources
-Follow the links below to find out more about this project
+Follow the links below to find out more about this project.
-- [Try it Now](https://mybinder.org/v2/gh/idom-team/idom-jupyter/main?urlpath=lab/tree/notebooks/introduction.ipynb) - check out IDOM in a Jupyter Notebook.
-- [Documentation](https://idom-docs.herokuapp.com/) - learn how to install, run, and use IDOM.
-- [Community Forum](https://github.com/idom-team/idom/discussions) - ask questions, share ideas, and show off projects.
-- [Contributor Guide](https://idom-docs.herokuapp.com/docs/developing-idom/contributor-guide.html) - see how you can help develop this project.
-- [Code of Conduct](https://github.com/idom-team/idom/blob/main/CODE_OF_CONDUCT.md) - standards for interacting with this community.
+- [Try ReactPy (Jupyter Notebook)](https://mybinder.org/v2/gh/reactive-python/reactpy-jupyter/main?urlpath=lab/tree/notebooks/introduction.ipynb)
+- [Documentation](https://reactpy.dev/)
+- [GitHub Discussions](https://github.com/reactive-python/reactpy/discussions)
+- [Discord](https://discord.gg/uNb5P4hA9X)
+- [Contributor Guide](https://reactpy.dev/docs/about/contributor-guide.html)
+- [Code of Conduct](https://github.com/reactive-python/reactpy/blob/main/CODE_OF_CONDUCT.md)
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 93d4c1ef0..000000000
--- a/VERSION
+++ /dev/null
@@ -1 +0,0 @@
-0.36.0
diff --git a/branding/ico/idom-logo-black-square-small.ico b/branding/ico/idom-logo-black-square-small.ico
deleted file mode 100644
index 7005d2938..000000000
Binary files a/branding/ico/idom-logo-black-square-small.ico and /dev/null differ
diff --git a/branding/ico/idom-logo-white-square-small.ico b/branding/ico/idom-logo-white-square-small.ico
deleted file mode 100644
index 6a5c139fb..000000000
Binary files a/branding/ico/idom-logo-white-square-small.ico and /dev/null differ
diff --git a/branding/ico/reactpy-logo.ico b/branding/ico/reactpy-logo.ico
new file mode 100644
index 000000000..62be5f5ba
Binary files /dev/null and b/branding/ico/reactpy-logo.ico differ
diff --git a/branding/png/idom-logo-black-square-small.png b/branding/png/idom-logo-black-square-small.png
deleted file mode 100644
index 7971eaa68..000000000
Binary files a/branding/png/idom-logo-black-square-small.png and /dev/null differ
diff --git a/branding/png/idom-logo-black-square.png b/branding/png/idom-logo-black-square.png
deleted file mode 100644
index 049f6fd55..000000000
Binary files a/branding/png/idom-logo-black-square.png and /dev/null differ
diff --git a/branding/png/idom-logo-black.png b/branding/png/idom-logo-black.png
deleted file mode 100644
index 9a78e48fc..000000000
Binary files a/branding/png/idom-logo-black.png and /dev/null differ
diff --git a/branding/png/idom-logo-white-square-small.png b/branding/png/idom-logo-white-square-small.png
deleted file mode 100644
index 957c58bfa..000000000
Binary files a/branding/png/idom-logo-white-square-small.png and /dev/null differ
diff --git a/branding/png/idom-logo-white-square.png b/branding/png/idom-logo-white-square.png
deleted file mode 100644
index f4dbb4db1..000000000
Binary files a/branding/png/idom-logo-white-square.png and /dev/null differ
diff --git a/branding/png/idom-logo-white.png b/branding/png/idom-logo-white.png
deleted file mode 100644
index 2ef265474..000000000
Binary files a/branding/png/idom-logo-white.png and /dev/null differ
diff --git a/branding/png/reactpy-logo-landscape-padded.png b/branding/png/reactpy-logo-landscape-padded.png
new file mode 100644
index 000000000..b2ba7a6d4
Binary files /dev/null and b/branding/png/reactpy-logo-landscape-padded.png differ
diff --git a/branding/png/reactpy-logo-landscape.png b/branding/png/reactpy-logo-landscape.png
new file mode 100644
index 000000000..1152fde84
Binary files /dev/null and b/branding/png/reactpy-logo-landscape.png differ
diff --git a/branding/png/reactpy-logo-padded.png b/branding/png/reactpy-logo-padded.png
new file mode 100644
index 000000000..46b8b8a07
Binary files /dev/null and b/branding/png/reactpy-logo-padded.png differ
diff --git a/branding/svg/idom-logo-backed-square.svg b/branding/svg/idom-logo-backed-square.svg
deleted file mode 100644
index 9e34b6808..000000000
--- a/branding/svg/idom-logo-backed-square.svg
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/branding/svg/idom-logo-backed.svg b/branding/svg/idom-logo-backed.svg
deleted file mode 100644
index 290fc7ed0..000000000
--- a/branding/svg/idom-logo-backed.svg
+++ /dev/null
@@ -1,61 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/branding/svg/idom-logo-square-small.svg b/branding/svg/idom-logo-square-small.svg
deleted file mode 100644
index eb36c7b11..000000000
--- a/branding/svg/idom-logo-square-small.svg
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/branding/svg/idom-logo-square.svg b/branding/svg/idom-logo-square.svg
deleted file mode 100644
index 354f961e4..000000000
--- a/branding/svg/idom-logo-square.svg
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/branding/svg/idom-logo.svg b/branding/svg/idom-logo.svg
deleted file mode 100644
index ffcee8d96..000000000
--- a/branding/svg/idom-logo.svg
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/branding/svg/reactpy-logo-landscape-padded.svg b/branding/svg/reactpy-logo-landscape-padded.svg
new file mode 100644
index 000000000..f9889b5fb
--- /dev/null
+++ b/branding/svg/reactpy-logo-landscape-padded.svg
@@ -0,0 +1,174 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ReactPy
+
+
diff --git a/branding/svg/reactpy-logo-landscape.svg b/branding/svg/reactpy-logo-landscape.svg
new file mode 100644
index 000000000..d4997a83d
--- /dev/null
+++ b/branding/svg/reactpy-logo-landscape.svg
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ReactPy
+
diff --git a/branding/svg/reactpy-logo-square-padded.svg b/branding/svg/reactpy-logo-square-padded.svg
new file mode 100644
index 000000000..601707bce
--- /dev/null
+++ b/branding/svg/reactpy-logo-square-padded.svg
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/branding/svg/reactpy-logo-square.svg b/branding/svg/reactpy-logo-square.svg
new file mode 100644
index 000000000..312fb87a5
--- /dev/null
+++ b/branding/svg/reactpy-logo-square.svg
@@ -0,0 +1,160 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/Dockerfile b/docs/Dockerfile
index 0a6cc87f2..7a5d49b7b 100644
--- a/docs/Dockerfile
+++ b/docs/Dockerfile
@@ -1,50 +1,42 @@
FROM python:3.9
-
WORKDIR /app/
+RUN apt-get update
+
# Install NodeJS
# --------------
-RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
-RUN apt-get install -yq nodejs build-essential
-RUN npm install -g npm@7.13.0
+RUN curl -SLO https://deb.nodesource.com/nsolid_setup_deb.sh
+RUN chmod 500 nsolid_setup_deb.sh
+RUN ./nsolid_setup_deb.sh 20
+RUN apt-get install nodejs -y
+
+# Install Poetry
+# --------------
+RUN pip install poetry
-# Create Python Venv
-# ------------------
+# Create/Activate Python Venv
+# ---------------------------
ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
RUN pip install --upgrade pip
-# Install IDOM
-# ------------
-ADD requirements ./requirements
-ADD src ./src
-ADD scripts ./scripts
-ADD setup.py ./
-ADD setup.cfg ./
-ADD MANIFEST.in ./
-ADD README.md ./
-
-RUN pip install -e .[all]
-
-# Add License
-# -----------
-Add LICENSE /app/
-
-# Build the Docs
-# --------------
-ADD docs/__init__.py ./docs/
-ADD docs/app.py ./docs/
-ADD docs/examples.py ./docs/
-ADD docs/source ./docs/source
-ADD branding ./branding
+# Copy Files
+# ----------
+COPY LICENSE ./
+COPY src ./src
+COPY docs ./docs
+COPY branding ./branding
-RUN pip install -r requirements/build-docs.txt
-RUN sphinx-build -W -b html docs/source docs/build
+# Install and Build Docs
+# ----------------------
+WORKDIR /app/docs
+RUN poetry install
+RUN sphinx-build -v -W -b html source build
# Define Entrypoint
# -----------------
ENV PORT 5000
-ENV IDOM_DEBUG_MODE=1
-ENV IDOM_CHECK_VDOM_SPEC=0
-CMD python scripts/run_docs.py
+ENV REACTPY_DEBUG_MODE=1
+ENV REACTPY_CHECK_VDOM_SPEC=0
+CMD python main.py
diff --git a/docs/Makefile b/docs/Makefile
deleted file mode 100644
index 69fe55ecf..000000000
--- a/docs/Makefile
+++ /dev/null
@@ -1,19 +0,0 @@
-# Minimal makefile for Sphinx documentation
-#
-
-# You can set these variables from the command line.
-SPHINXOPTS =
-SPHINXBUILD = sphinx-build
-SOURCEDIR = source
-BUILDDIR = build
-
-# Put it first so that "make" without argument is like "make help".
-help:
- @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
-
-.PHONY: help Makefile
-
-# Catch-all target: route all unknown targets to Sphinx using the new
-# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
-%: Makefile
- @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index 067adc0b8..1360bc825 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,14 +1,16 @@
-# IDOM's Documentation
+# ReactPy's Documentation
-We provide two main ways to run the docs. Both use `nox` which has a `noxfile.py` at the
-root of the repository. Running the docs with `nox -s docs` will start up an iteractive
-session which will rebuild the docs any time a file is modified. Using `nox -s
-docs_in_docker` on the other hand, will build a docker image and run the docs from
-there. The latter command mimics how the docs will behave in production. As such, if any
-changes to the core of the documentation are made (i.e. to non-`*.rst` files), then you
-should run a manual test of the documentation using the `docs_in_docker` session.
+We provide two main ways to run the docs. Both use
+[`nox`](https://pypi.org/project/nox/):
-If you with to build and run the docs by hand you need to perform two commands, each
+- `nox -s docs` - displays the docs and rebuilds when files are modified.
+- `nox -s docs-in-docker` - builds a docker image and runs the docs from there.
+
+If any changes to the core of the documentation are made (i.e. to non-`*.rst` files),
+then you should run a manual test of the documentation using the `docs_in_docker`
+session.
+
+If you wish to build and run the docs by hand you need to perform two commands, each
being run from the root of the repository:
- `sphinx-build -b html docs/source docs/build`
diff --git a/docs/__init__.py b/docs/__init__.py
deleted file mode 100644
index a6074c96f..000000000
--- a/docs/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .app import run
-
-
-__all__ = ["run"]
diff --git a/docs/app.py b/docs/app.py
deleted file mode 100644
index b065dd09e..000000000
--- a/docs/app.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import os
-from logging import getLogger
-from pathlib import Path
-
-from sanic import Sanic, response
-
-from idom.server.sanic import PerClientStateServer
-from idom.widgets import multiview
-
-from .examples import load_examples
-
-
-HERE = Path(__file__).parent
-IDOM_MODEL_SERVER_URL_PREFIX = "/_idom"
-
-logger = getLogger(__name__)
-
-
-IDOM_MODEL_SERVER_URL_PREFIX = "/_idom"
-
-
-def run():
- app = make_app()
-
- PerClientStateServer(
- make_examples_component(),
- {
- "redirect_root_to_index": False,
- "url_prefix": IDOM_MODEL_SERVER_URL_PREFIX,
- },
- app,
- )
-
- app.run(
- host="0.0.0.0",
- port=int(os.environ.get("PORT", 5000)),
- workers=int(os.environ.get("WEB_CONCURRENCY", 1)),
- debug=bool(int(os.environ.get("DEBUG", "0"))),
- )
-
-
-def make_app():
- app = Sanic(__name__)
-
- app.static("/docs", str(HERE / "build"))
-
- @app.route("/")
- async def forward_to_index(request):
- return response.redirect("/docs/index.html")
-
- return app
-
-
-def make_examples_component():
- mount, component = multiview()
-
- for example_name, example_component in load_examples():
- mount.add(example_name, example_component)
-
- return component
diff --git a/scripts/common/__init__.py b/docs/docs_app/__init__.py
similarity index 100%
rename from scripts/common/__init__.py
rename to docs/docs_app/__init__.py
diff --git a/docs/docs_app/app.py b/docs/docs_app/app.py
new file mode 100644
index 000000000..3fe4669ff
--- /dev/null
+++ b/docs/docs_app/app.py
@@ -0,0 +1,59 @@
+from logging import getLogger
+from pathlib import Path
+
+from sanic import Sanic, response
+
+from docs_app.examples import get_normalized_example_name, load_examples
+from reactpy import component
+from reactpy.backend.sanic import Options, configure, use_request
+from reactpy.core.types import ComponentConstructor
+
+THIS_DIR = Path(__file__).parent
+DOCS_DIR = THIS_DIR.parent
+DOCS_BUILD_DIR = DOCS_DIR / "build"
+
+REACTPY_MODEL_SERVER_URL_PREFIX = "/_reactpy"
+
+logger = getLogger(__name__)
+
+
+REACTPY_MODEL_SERVER_URL_PREFIX = "/_reactpy"
+
+
+@component
+def Example():
+ raw_view_id = use_request().get_args().get("view_id")
+ view_id = get_normalized_example_name(raw_view_id)
+ return _get_examples()[view_id]()
+
+
+def _get_examples():
+ if not _EXAMPLES:
+ _EXAMPLES.update(load_examples())
+ return _EXAMPLES
+
+
+def reload_examples():
+ _EXAMPLES.clear()
+ _EXAMPLES.update(load_examples())
+
+
+_EXAMPLES: dict[str, ComponentConstructor] = {}
+
+
+def make_app(name: str):
+ app = Sanic(name)
+
+ app.static("/docs", str(DOCS_BUILD_DIR))
+
+ @app.route("/")
+ async def forward_to_index(_):
+ return response.redirect("/docs/index.html")
+
+ configure(
+ app,
+ Example,
+ Options(url_prefix=REACTPY_MODEL_SERVER_URL_PREFIX),
+ )
+
+ return app
diff --git a/docs/docs_app/dev.py b/docs/docs_app/dev.py
new file mode 100644
index 000000000..5d661924d
--- /dev/null
+++ b/docs/docs_app/dev.py
@@ -0,0 +1,104 @@
+import asyncio
+import os
+import threading
+import time
+import webbrowser
+
+from sphinx_autobuild.cli import (
+ Server,
+ _get_build_args,
+ _get_ignore_handler,
+ find_free_port,
+ get_builder,
+ get_parser,
+)
+
+from docs_app.app import make_app, reload_examples
+from reactpy.backend.sanic import serve_development_app
+from reactpy.testing import clear_reactpy_web_modules_dir
+
+# these environment variable are used in custom Sphinx extensions
+os.environ["REACTPY_DOC_EXAMPLE_SERVER_HOST"] = "127.0.0.1:5555"
+os.environ["REACTPY_DOC_STATIC_SERVER_HOST"] = ""
+
+
+def wrap_builder(old_builder):
+ # This is the bit that we're injecting to get the example components to reload too
+
+ app = make_app("docs_dev_app")
+
+ thread_started = threading.Event()
+
+ def run_in_thread():
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ server_started = asyncio.Event()
+
+ async def set_thread_event_when_started():
+ await server_started.wait()
+ thread_started.set()
+
+ loop.run_until_complete(
+ asyncio.gather(
+ serve_development_app(app, "127.0.0.1", 5555, server_started),
+ set_thread_event_when_started(),
+ )
+ )
+
+ threading.Thread(target=run_in_thread, daemon=True).start()
+
+ thread_started.wait()
+
+ def new_builder():
+ clear_reactpy_web_modules_dir()
+ reload_examples()
+ old_builder()
+
+ return new_builder
+
+
+def main():
+ # Mostly copied from https://github.com/executablebooks/sphinx-autobuild/blob/b54fb08afc5112bfcda1d844a700c5a20cd6ba5e/src/sphinx_autobuild/cli.py
+ parser = get_parser()
+ args = parser.parse_args()
+
+ srcdir = os.path.realpath(args.sourcedir)
+ outdir = os.path.realpath(args.outdir)
+ if not os.path.exists(outdir):
+ os.makedirs(outdir)
+
+ server = Server()
+
+ build_args, pre_build_commands = _get_build_args(args)
+ builder = wrap_builder(
+ get_builder(
+ server.watcher,
+ build_args,
+ host=args.host,
+ port=args.port,
+ pre_build_commands=pre_build_commands,
+ )
+ )
+
+ ignore_handler = _get_ignore_handler(args)
+ server.watch(srcdir, builder, ignore=ignore_handler)
+ for dirpath in args.additional_watched_dirs:
+ real_dirpath = os.path.realpath(dirpath)
+ server.watch(real_dirpath, builder, ignore=ignore_handler)
+ server.watch(outdir, ignore=ignore_handler)
+
+ if not args.no_initial_build:
+ builder()
+
+ # Find the free port
+ portn = args.port or find_free_port()
+ if args.openbrowser is True:
+
+ def opener():
+ time.sleep(args.delay)
+ webbrowser.open(f"http://{args.host}:{args.port}/index.html")
+
+ threading.Thread(target=opener, daemon=True).start()
+
+ server.serve(port=portn, host=args.host, root=outdir)
diff --git a/docs/examples.py b/docs/docs_app/examples.py
similarity index 82%
rename from docs/examples.py
rename to docs/docs_app/examples.py
index aee0ef2d5..a71a0b111 100644
--- a/docs/examples.py
+++ b/docs/docs_app/examples.py
@@ -1,18 +1,18 @@
from __future__ import annotations
+from collections.abc import Iterator
from io import StringIO
from pathlib import Path
from traceback import format_exc
-from typing import Callable, Iterator
-
-import idom
-from idom import ComponentType
+from typing import Callable
+import reactpy
+from reactpy.types import ComponentType
HERE = Path(__file__)
-SOURCE_DIR = HERE.parent / "source"
+SOURCE_DIR = HERE.parent.parent / "source"
CONF_FILE = SOURCE_DIR / "conf.py"
-RUN_IDOM = idom.run
+RUN_ReactPy = reactpy.run
def load_examples() -> Iterator[tuple[str, Callable[[], ComponentType]]]:
@@ -23,7 +23,7 @@ def load_examples() -> Iterator[tuple[str, Callable[[], ComponentType]]]:
def all_example_names() -> set[str]:
names = set()
for file in _iter_example_files(SOURCE_DIR):
- path = file.parent if file.name == "app.py" else file
+ path = file.parent if file.name == "main.py" else file
names.add("/".join(path.relative_to(SOURCE_DIR).with_suffix("").parts))
return names
@@ -48,7 +48,7 @@ def get_main_example_file_by_name(
) -> Path:
path = _get_root_example_path_by_name(name, relative_to)
if path.is_dir():
- return path / "app.py"
+ return path / "main.py"
else:
return path.with_suffix(".py")
@@ -95,7 +95,7 @@ def capture_component(component_constructor):
nonlocal captured_component_constructor
captured_component_constructor = component_constructor
- idom.run = capture_component
+ reactpy.run = capture_component
try:
code = compile(file.read_text(), str(file), "exec")
exec(
@@ -109,20 +109,24 @@ def capture_component(component_constructor):
except Exception:
return _make_error_display(format_exc())
finally:
- idom.run = RUN_IDOM
+ reactpy.run = RUN_ReactPy
if captured_component_constructor is None:
return _make_example_did_not_run(str(file))
- @idom.component
+ @reactpy.component
def Wrapper():
- return idom.html.div(captured_component_constructor(), PrintView())
+ return reactpy.html.div(captured_component_constructor(), PrintView())
- @idom.component
+ @reactpy.component
def PrintView():
- text, set_text = idom.hooks.use_state(print_buffer.getvalue())
+ text, set_text = reactpy.hooks.use_state(print_buffer.getvalue())
print_buffer.set_callback(set_text)
- return idom.html.pre({"class": "printout"}, text) if text else idom.html.div()
+ return (
+ reactpy.html.pre({"class_name": "printout"}, text)
+ if text
+ else reactpy.html.div()
+ )
return Wrapper()
@@ -133,7 +137,7 @@ def _get_root_example_path_by_name(name: str, relative_to: str | Path | None) ->
rel_path = rel_path.parent if rel_path.is_file() else rel_path
else:
rel_path = SOURCE_DIR
- return rel_path.joinpath(*name.split("/"))
+ return rel_path.joinpath(*name.split("/")).resolve()
class _PrintBuffer:
@@ -144,7 +148,6 @@ def __init__(self, max_lines: int = 10):
def set_callback(self, function: Callable[[str], None]) -> None:
self._callback = function
- return None
def getvalue(self) -> str:
return "".join(self._lines)
@@ -159,16 +162,16 @@ def write(self, text: str) -> None:
def _make_example_did_not_run(example_name):
- @idom.component
+ @reactpy.component
def ExampleDidNotRun():
- return idom.html.code(f"Example {example_name} did not run")
+ return reactpy.html.code(f"Example {example_name} did not run")
return ExampleDidNotRun()
def _make_error_display(message):
- @idom.component
+ @reactpy.component
def ShowError():
- return idom.html.pre(message)
+ return reactpy.html.pre(message)
return ShowError()
diff --git a/docs/docs_app/prod.py b/docs/docs_app/prod.py
new file mode 100644
index 000000000..0acf12432
--- /dev/null
+++ b/docs/docs_app/prod.py
@@ -0,0 +1,14 @@
+import os
+
+from docs_app.app import make_app
+
+app = make_app("docs_prod_app")
+
+
+def main() -> None:
+ app.run(
+ host="0.0.0.0", # noqa: S104
+ port=int(os.environ.get("PORT", 5000)),
+ workers=int(os.environ.get("WEB_CONCURRENCY", 1)),
+ debug=bool(int(os.environ.get("DEBUG", "0"))),
+ )
diff --git a/docs/main.py b/docs/main.py
new file mode 100644
index 000000000..e3181f393
--- /dev/null
+++ b/docs/main.py
@@ -0,0 +1,9 @@
+import sys
+
+from docs_app import dev, prod
+
+if __name__ == "__main__":
+ if len(sys.argv) == 1:
+ prod.main()
+ else:
+ dev.main()
diff --git a/docs/make.bat b/docs/make.bat
deleted file mode 100644
index 543c6b13b..000000000
--- a/docs/make.bat
+++ /dev/null
@@ -1,35 +0,0 @@
-@ECHO OFF
-
-pushd %~dp0
-
-REM Command file for Sphinx documentation
-
-if "%SPHINXBUILD%" == "" (
- set SPHINXBUILD=sphinx-build
-)
-set SOURCEDIR=source
-set BUILDDIR=build
-
-if "%1" == "" goto help
-
-%SPHINXBUILD% >NUL 2>NUL
-if errorlevel 9009 (
- echo.
- echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
- echo.installed, then set the SPHINXBUILD environment variable to point
- echo.to the full path of the 'sphinx-build' executable. Alternatively you
- echo.may add the Sphinx directory to PATH.
- echo.
- echo.If you don't have Sphinx installed, grab it from
- echo.http://sphinx-doc.org/
- exit /b 1
-)
-
-%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-goto end
-
-:help
-%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS%
-
-:end
-popd
diff --git a/docs/poetry.lock b/docs/poetry.lock
new file mode 100644
index 000000000..8e1daef24
--- /dev/null
+++ b/docs/poetry.lock
@@ -0,0 +1,2269 @@
+# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand.
+
+[[package]]
+name = "aiofiles"
+version = "23.1.0"
+description = "File support for asyncio."
+optional = false
+python-versions = ">=3.7,<4.0"
+files = [
+ {file = "aiofiles-23.1.0-py3-none-any.whl", hash = "sha256:9312414ae06472eb6f1d163f555e466a23aed1c8f60c30cccf7121dba2e53eb2"},
+ {file = "aiofiles-23.1.0.tar.gz", hash = "sha256:edd247df9a19e0db16534d4baaf536d6609a43e1de5401d7a4c1c148753a1635"},
+]
+
+[[package]]
+name = "alabaster"
+version = "0.7.13"
+description = "A configurable sidebar-enabled Sphinx theme"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"},
+ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"},
+]
+
+[[package]]
+name = "anyio"
+version = "3.7.0"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "anyio-3.7.0-py3-none-any.whl", hash = "sha256:eddca883c4175f14df8aedce21054bfca3adb70ffe76a9f607aef9d7fa2ea7f0"},
+ {file = "anyio-3.7.0.tar.gz", hash = "sha256:275d9973793619a5374e1c89a4f4ad3f4b0a5510a2b5b939444bee8f4c4d37ce"},
+]
+
+[package.dependencies]
+exceptiongroup = {version = "*", markers = "python_version < \"3.11\""}
+idna = ">=2.8"
+sniffio = ">=1.1"
+
+[package.extras]
+doc = ["Sphinx (>=6.1.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme", "sphinxcontrib-jquery"]
+test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+trio = ["trio (<0.22)"]
+
+[[package]]
+name = "asgiref"
+version = "3.7.2"
+description = "ASGI specs, helper code, and adapters"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "asgiref-3.7.2-py3-none-any.whl", hash = "sha256:89b2ef2247e3b562a16eef663bc0e2e703ec6468e2fa8a5cd61cd449786d4f6e"},
+ {file = "asgiref-3.7.2.tar.gz", hash = "sha256:9e0ce3aa93a819ba5b45120216b23878cf6e8525eb3848653452b4192b92afed"},
+]
+
+[package.dependencies]
+typing-extensions = {version = ">=4", markers = "python_version < \"3.11\""}
+
+[package.extras]
+tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"]
+
+[[package]]
+name = "babel"
+version = "2.12.1"
+description = "Internationalization utilities"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Babel-2.12.1-py3-none-any.whl", hash = "sha256:b4246fb7677d3b98f501a39d43396d3cafdc8eadb045f4a31be01863f655c610"},
+ {file = "Babel-2.12.1.tar.gz", hash = "sha256:cc2d99999cd01d44420ae725a21c9e3711b3aadc7976d6147f622d8581963455"},
+]
+
+[[package]]
+name = "beautifulsoup4"
+version = "4.12.2"
+description = "Screen-scraping library"
+optional = false
+python-versions = ">=3.6.0"
+files = [
+ {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"},
+ {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"},
+]
+
+[package.dependencies]
+soupsieve = ">1.2"
+
+[package.extras]
+html5lib = ["html5lib"]
+lxml = ["lxml"]
+
+[[package]]
+name = "certifi"
+version = "2023.5.7"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "certifi-2023.5.7-py3-none-any.whl", hash = "sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"},
+ {file = "certifi-2023.5.7.tar.gz", hash = "sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7"},
+]
+
+[[package]]
+name = "charset-normalizer"
+version = "3.1.0"
+description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "charset-normalizer-3.1.0.tar.gz", hash = "sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-win32.whl", hash = "sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448"},
+ {file = "charset_normalizer-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-win32.whl", hash = "sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909"},
+ {file = "charset_normalizer-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974"},
+ {file = "charset_normalizer-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-win32.whl", hash = "sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0"},
+ {file = "charset_normalizer-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-win32.whl", hash = "sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1"},
+ {file = "charset_normalizer-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b"},
+ {file = "charset_normalizer-3.1.0-py3-none-any.whl", hash = "sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d"},
+]
+
+[[package]]
+name = "click"
+version = "8.1.3"
+description = "Composable command line interface toolkit"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
+ {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "colorlog"
+version = "6.7.0"
+description = "Add colours to the output of Python's logging module."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "colorlog-6.7.0-py2.py3-none-any.whl", hash = "sha256:0d33ca236784a1ba3ff9c532d4964126d8a2c44f1f0cb1d2b0728196f512f662"},
+ {file = "colorlog-6.7.0.tar.gz", hash = "sha256:bd94bd21c1e13fac7bd3153f4bc3a7dc0eb0974b8bc2fdf1a989e474f6e582e5"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+
+[package.extras]
+development = ["black", "flake8", "mypy", "pytest", "types-colorama"]
+
+[[package]]
+name = "contourpy"
+version = "1.0.7"
+description = "Python library for calculating contours of 2D quadrilateral grids"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:95c3acddf921944f241b6773b767f1cbce71d03307270e2d769fd584d5d1092d"},
+ {file = "contourpy-1.0.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fc1464c97579da9f3ab16763c32e5c5d5bb5fa1ec7ce509a4ca6108b61b84fab"},
+ {file = "contourpy-1.0.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8acf74b5d383414401926c1598ed77825cd530ac7b463ebc2e4f46638f56cce6"},
+ {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c71fdd8f1c0f84ffd58fca37d00ca4ebaa9e502fb49825484da075ac0b0b803"},
+ {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f99e9486bf1bb979d95d5cffed40689cb595abb2b841f2991fc894b3452290e8"},
+ {file = "contourpy-1.0.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f4d8941a9564cda3f7fa6a6cd9b32ec575830780677932abdec7bcb61717b0"},
+ {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9e20e5a1908e18aaa60d9077a6d8753090e3f85ca25da6e25d30dc0a9e84c2c6"},
+ {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a877ada905f7d69b2a31796c4b66e31a8068b37aa9b78832d41c82fc3e056ddd"},
+ {file = "contourpy-1.0.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6381fa66866b0ea35e15d197fc06ac3840a9b2643a6475c8fff267db8b9f1e69"},
+ {file = "contourpy-1.0.7-cp310-cp310-win32.whl", hash = "sha256:3c184ad2433635f216645fdf0493011a4667e8d46b34082f5a3de702b6ec42e3"},
+ {file = "contourpy-1.0.7-cp310-cp310-win_amd64.whl", hash = "sha256:3caea6365b13119626ee996711ab63e0c9d7496f65641f4459c60a009a1f3e80"},
+ {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ed33433fc3820263a6368e532f19ddb4c5990855e4886088ad84fd7c4e561c71"},
+ {file = "contourpy-1.0.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:38e2e577f0f092b8e6774459317c05a69935a1755ecfb621c0a98f0e3c09c9a5"},
+ {file = "contourpy-1.0.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ae90d5a8590e5310c32a7630b4b8618cef7563cebf649011da80874d0aa8f414"},
+ {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130230b7e49825c98edf0b428b7aa1125503d91732735ef897786fe5452b1ec2"},
+ {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58569c491e7f7e874f11519ef46737cea1d6eda1b514e4eb5ac7dab6aa864d02"},
+ {file = "contourpy-1.0.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54d43960d809c4c12508a60b66cb936e7ed57d51fb5e30b513934a4a23874fae"},
+ {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:152fd8f730c31fd67fe0ffebe1df38ab6a669403da93df218801a893645c6ccc"},
+ {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9056c5310eb1daa33fc234ef39ebfb8c8e2533f088bbf0bc7350f70a29bde1ac"},
+ {file = "contourpy-1.0.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a9d7587d2fdc820cc9177139b56795c39fb8560f540bba9ceea215f1f66e1566"},
+ {file = "contourpy-1.0.7-cp311-cp311-win32.whl", hash = "sha256:4ee3ee247f795a69e53cd91d927146fb16c4e803c7ac86c84104940c7d2cabf0"},
+ {file = "contourpy-1.0.7-cp311-cp311-win_amd64.whl", hash = "sha256:5caeacc68642e5f19d707471890f037a13007feba8427eb7f2a60811a1fc1350"},
+ {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd7dc0e6812b799a34f6d12fcb1000539098c249c8da54f3566c6a6461d0dbad"},
+ {file = "contourpy-1.0.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0f9d350b639db6c2c233d92c7f213d94d2e444d8e8fc5ca44c9706cf72193772"},
+ {file = "contourpy-1.0.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e96a08b62bb8de960d3a6afbc5ed8421bf1a2d9c85cc4ea73f4bc81b4910500f"},
+ {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:031154ed61f7328ad7f97662e48660a150ef84ee1bc8876b6472af88bf5a9b98"},
+ {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e9ebb4425fc1b658e13bace354c48a933b842d53c458f02c86f371cecbedecc"},
+ {file = "contourpy-1.0.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb8f6d08ca7998cf59eaf50c9d60717f29a1a0a09caa46460d33b2924839dbd"},
+ {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6c180d89a28787e4b73b07e9b0e2dac7741261dbdca95f2b489c4f8f887dd810"},
+ {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b8d587cc39057d0afd4166083d289bdeff221ac6d3ee5046aef2d480dc4b503c"},
+ {file = "contourpy-1.0.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:769eef00437edf115e24d87f8926955f00f7704bede656ce605097584f9966dc"},
+ {file = "contourpy-1.0.7-cp38-cp38-win32.whl", hash = "sha256:62398c80ef57589bdbe1eb8537127321c1abcfdf8c5f14f479dbbe27d0322e66"},
+ {file = "contourpy-1.0.7-cp38-cp38-win_amd64.whl", hash = "sha256:57119b0116e3f408acbdccf9eb6ef19d7fe7baf0d1e9aaa5381489bc1aa56556"},
+ {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30676ca45084ee61e9c3da589042c24a57592e375d4b138bd84d8709893a1ba4"},
+ {file = "contourpy-1.0.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e927b3868bd1e12acee7cc8f3747d815b4ab3e445a28d2e5373a7f4a6e76ba1"},
+ {file = "contourpy-1.0.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:366a0cf0fc079af5204801786ad7a1c007714ee3909e364dbac1729f5b0849e5"},
+ {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89ba9bb365446a22411f0673abf6ee1fea3b2cf47b37533b970904880ceb72f3"},
+ {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b0bf0c30d432278793d2141362ac853859e87de0a7dee24a1cea35231f0d50"},
+ {file = "contourpy-1.0.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7281244c99fd7c6f27c1c6bfafba878517b0b62925a09b586d88ce750a016d2"},
+ {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b6d0f9e1d39dbfb3977f9dd79f156c86eb03e57a7face96f199e02b18e58d32a"},
+ {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7f6979d20ee5693a1057ab53e043adffa1e7418d734c1532e2d9e915b08d8ec2"},
+ {file = "contourpy-1.0.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5dd34c1ae752515318224cba7fc62b53130c45ac6a1040c8b7c1a223c46e8967"},
+ {file = "contourpy-1.0.7-cp39-cp39-win32.whl", hash = "sha256:c5210e5d5117e9aec8c47d9156d1d3835570dd909a899171b9535cb4a3f32693"},
+ {file = "contourpy-1.0.7-cp39-cp39-win_amd64.whl", hash = "sha256:60835badb5ed5f4e194a6f21c09283dd6e007664a86101431bf870d9e86266c4"},
+ {file = "contourpy-1.0.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ce41676b3d0dd16dbcfabcc1dc46090aaf4688fd6e819ef343dbda5a57ef0161"},
+ {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a011cf354107b47c58ea932d13b04d93c6d1d69b8b6dce885e642531f847566"},
+ {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31a55dccc8426e71817e3fe09b37d6d48ae40aae4ecbc8c7ad59d6893569c436"},
+ {file = "contourpy-1.0.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69f8ff4db108815addd900a74df665e135dbbd6547a8a69333a68e1f6e368ac2"},
+ {file = "contourpy-1.0.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efe99298ba37e37787f6a2ea868265465410822f7bea163edcc1bd3903354ea9"},
+ {file = "contourpy-1.0.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a1e97b86f73715e8670ef45292d7cc033548266f07d54e2183ecb3c87598888f"},
+ {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc331c13902d0f50845099434cd936d49d7a2ca76cb654b39691974cb1e4812d"},
+ {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24847601071f740837aefb730e01bd169fbcaa610209779a78db7ebb6e6a7051"},
+ {file = "contourpy-1.0.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abf298af1e7ad44eeb93501e40eb5a67abbf93b5d90e468d01fc0c4451971afa"},
+ {file = "contourpy-1.0.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:64757f6460fc55d7e16ed4f1de193f362104285c667c112b50a804d482777edd"},
+ {file = "contourpy-1.0.7.tar.gz", hash = "sha256:d8165a088d31798b59e91117d1f5fc3df8168d8b48c4acc10fc0df0d0bdbcc5e"},
+]
+
+[package.dependencies]
+numpy = ">=1.16"
+
+[package.extras]
+bokeh = ["bokeh", "chromedriver", "selenium"]
+docs = ["furo", "sphinx-copybutton"]
+mypy = ["contourpy[bokeh]", "docutils-stubs", "mypy (==0.991)", "types-Pillow"]
+test = ["Pillow", "matplotlib", "pytest"]
+test-no-images = ["pytest"]
+
+[[package]]
+name = "cycler"
+version = "0.11.0"
+description = "Composable style cycles"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"},
+ {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"},
+]
+
+[[package]]
+name = "docutils"
+version = "0.17.1"
+description = "Docutils -- Python Documentation Utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+files = [
+ {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"},
+ {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"},
+]
+
+[[package]]
+name = "exceptiongroup"
+version = "1.1.1"
+description = "Backport of PEP 654 (exception groups)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "exceptiongroup-1.1.1-py3-none-any.whl", hash = "sha256:232c37c63e4f682982c8b6459f33a8981039e5fb8756b2074364e5055c498c9e"},
+ {file = "exceptiongroup-1.1.1.tar.gz", hash = "sha256:d484c3090ba2889ae2928419117447a14daf3c1231d5e30d0aae34f354f01785"},
+]
+
+[package.extras]
+test = ["pytest (>=6)"]
+
+[[package]]
+name = "fastapi"
+version = "0.96.0"
+description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "fastapi-0.96.0-py3-none-any.whl", hash = "sha256:b8e11fe81e81eab4e1504209917338e0b80f783878a42c2b99467e5e1019a1e9"},
+ {file = "fastapi-0.96.0.tar.gz", hash = "sha256:71232d47c2787446991c81c41c249f8a16238d52d779c0e6b43927d3773dbe3c"},
+]
+
+[package.dependencies]
+pydantic = ">=1.6.2,<1.7 || >1.7,<1.7.1 || >1.7.1,<1.7.2 || >1.7.2,<1.7.3 || >1.7.3,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0"
+starlette = ">=0.27.0,<0.28.0"
+
+[package.extras]
+all = ["email-validator (>=1.1.1)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
+dev = ["pre-commit (>=2.17.0,<3.0.0)", "ruff (==0.0.138)", "uvicorn[standard] (>=0.12.0,<0.21.0)"]
+doc = ["mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-markdownextradata-plugin (>=0.1.7,<0.3.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pyyaml (>=5.3.1,<7.0.0)", "typer-cli (>=0.0.13,<0.0.14)", "typer[all] (>=0.6.1,<0.8.0)"]
+test = ["anyio[trio] (>=3.2.1,<4.0.0)", "black (==23.1.0)", "coverage[toml] (>=6.5.0,<8.0)", "databases[sqlite] (>=0.3.2,<0.7.0)", "email-validator (>=1.1.1,<2.0.0)", "flask (>=1.1.2,<3.0.0)", "httpx (>=0.23.0,<0.24.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.982)", "orjson (>=3.2.1,<4.0.0)", "passlib[bcrypt] (>=1.7.2,<2.0.0)", "peewee (>=3.13.3,<4.0.0)", "pytest (>=7.1.3,<8.0.0)", "python-jose[cryptography] (>=3.3.0,<4.0.0)", "python-multipart (>=0.0.5,<0.0.7)", "pyyaml (>=5.3.1,<7.0.0)", "ruff (==0.0.138)", "sqlalchemy (>=1.3.18,<1.4.43)", "types-orjson (==3.6.2)", "types-ujson (==5.7.0.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0,<6.0.0)"]
+
+[[package]]
+name = "fastjsonschema"
+version = "2.17.1"
+description = "Fastest Python implementation of JSON schema"
+optional = false
+python-versions = "*"
+files = [
+ {file = "fastjsonschema-2.17.1-py3-none-any.whl", hash = "sha256:4b90b252628ca695280924d863fe37234eebadc29c5360d322571233dc9746e0"},
+ {file = "fastjsonschema-2.17.1.tar.gz", hash = "sha256:f4eeb8a77cef54861dbf7424ac8ce71306f12cbb086c45131bcba2c6a4f726e3"},
+]
+
+[package.extras]
+devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"]
+
+[[package]]
+name = "flask"
+version = "2.1.3"
+description = "A simple framework for building complex web applications."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Flask-2.1.3-py3-none-any.whl", hash = "sha256:9013281a7402ad527f8fd56375164f3aa021ecfaff89bfe3825346c24f87e04c"},
+ {file = "Flask-2.1.3.tar.gz", hash = "sha256:15972e5017df0575c3d6c090ba168b6db90259e620ac8d7ea813a396bad5b6cb"},
+]
+
+[package.dependencies]
+click = ">=8.0"
+importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""}
+itsdangerous = ">=2.0"
+Jinja2 = ">=3.0"
+Werkzeug = ">=2.0"
+
+[package.extras]
+async = ["asgiref (>=3.2)"]
+dotenv = ["python-dotenv"]
+
+[[package]]
+name = "flask-cors"
+version = "3.0.10"
+description = "A Flask extension adding a decorator for CORS support"
+optional = false
+python-versions = "*"
+files = [
+ {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"},
+ {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"},
+]
+
+[package.dependencies]
+Flask = ">=0.9"
+Six = "*"
+
+[[package]]
+name = "flask-sock"
+version = "0.6.0"
+description = "WebSocket support for Flask"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "flask-sock-0.6.0.tar.gz", hash = "sha256:435cf81bb497ac7622cd1dda554fbfa3e369e629daea0a1d21b73a24f1bd6229"},
+ {file = "flask_sock-0.6.0-py3-none-any.whl", hash = "sha256:593fffb186928080a5b5b03d717efc56dac2d5ed690ce6bfff333b3597a2f518"},
+]
+
+[package.dependencies]
+flask = ">=2"
+simple-websocket = ">=0.5.1"
+
+[[package]]
+name = "fonttools"
+version = "4.39.4"
+description = "Tools to manipulate font files"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "fonttools-4.39.4-py3-none-any.whl", hash = "sha256:106caf6167c4597556b31a8d9175a3fdc0356fdcd70ab19973c3b0d4c893c461"},
+ {file = "fonttools-4.39.4.zip", hash = "sha256:dba8d7cdb8e2bac1b3da28c5ed5960de09e59a2fe7e63bb73f5a59e57b0430d2"},
+]
+
+[package.extras]
+all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.0.0)", "xattr", "zopfli (>=0.1.4)"]
+graphite = ["lz4 (>=1.7.4.2)"]
+interpolatable = ["munkres", "scipy"]
+lxml = ["lxml (>=4.0,<5)"]
+pathops = ["skia-pathops (>=0.5.0)"]
+plot = ["matplotlib"]
+repacker = ["uharfbuzz (>=0.23.0)"]
+symfont = ["sympy"]
+type1 = ["xattr"]
+ufo = ["fs (>=2.2.0,<3)"]
+unicode = ["unicodedata2 (>=15.0.0)"]
+woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"]
+
+[[package]]
+name = "furo"
+version = "2022.4.7"
+description = "A clean customisable Sphinx documentation theme."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "furo-2022.4.7-py3-none-any.whl", hash = "sha256:7f3e3d2fb977483590f8ecb2c2cd511bd82661b79c18efb24de9558bc9cdf2d7"},
+ {file = "furo-2022.4.7.tar.gz", hash = "sha256:96204ab7cd047e4b6c523996e0279c4c629a8fc31f4f109b2efd470c17f49c80"},
+]
+
+[package.dependencies]
+beautifulsoup4 = "*"
+pygments = ">=2.7,<3.0"
+sphinx = ">=4.0,<5.0"
+
+[[package]]
+name = "greenlet"
+version = "2.0.2"
+description = "Lightweight in-process concurrent programming"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*"
+files = [
+ {file = "greenlet-2.0.2-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:bdfea8c661e80d3c1c99ad7c3ff74e6e87184895bbaca6ee8cc61209f8b9b85d"},
+ {file = "greenlet-2.0.2-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9d14b83fab60d5e8abe587d51c75b252bcc21683f24699ada8fb275d7712f5a9"},
+ {file = "greenlet-2.0.2-cp27-cp27m-win32.whl", hash = "sha256:6c3acb79b0bfd4fe733dff8bc62695283b57949ebcca05ae5c129eb606ff2d74"},
+ {file = "greenlet-2.0.2-cp27-cp27m-win_amd64.whl", hash = "sha256:283737e0da3f08bd637b5ad058507e578dd462db259f7f6e4c5c365ba4ee9343"},
+ {file = "greenlet-2.0.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d27ec7509b9c18b6d73f2f5ede2622441de812e7b1a80bbd446cb0633bd3d5ae"},
+ {file = "greenlet-2.0.2-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:30bcf80dda7f15ac77ba5af2b961bdd9dbc77fd4ac6105cee85b0d0a5fcf74df"},
+ {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26fbfce90728d82bc9e6c38ea4d038cba20b7faf8a0ca53a9c07b67318d46088"},
+ {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9190f09060ea4debddd24665d6804b995a9c122ef5917ab26e1566dcc712ceeb"},
+ {file = "greenlet-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75209eed723105f9596807495d58d10b3470fa6732dd6756595e89925ce2470"},
+ {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3a51c9751078733d88e013587b108f1b7a1fb106d402fb390740f002b6f6551a"},
+ {file = "greenlet-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:76ae285c8104046b3a7f06b42f29c7b73f77683df18c49ab5af7983994c2dd91"},
+ {file = "greenlet-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:2d4686f195e32d36b4d7cf2d166857dbd0ee9f3d20ae349b6bf8afc8485b3645"},
+ {file = "greenlet-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c4302695ad8027363e96311df24ee28978162cdcdd2006476c43970b384a244c"},
+ {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c48f54ef8e05f04d6eff74b8233f6063cb1ed960243eacc474ee73a2ea8573ca"},
+ {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a1846f1b999e78e13837c93c778dcfc3365902cfb8d1bdb7dd73ead37059f0d0"},
+ {file = "greenlet-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a06ad5312349fec0ab944664b01d26f8d1f05009566339ac6f63f56589bc1a2"},
+ {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:eff4eb9b7eb3e4d0cae3d28c283dc16d9bed6b193c2e1ace3ed86ce48ea8df19"},
+ {file = "greenlet-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5454276c07d27a740c5892f4907c86327b632127dd9abec42ee62e12427ff7e3"},
+ {file = "greenlet-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:7cafd1208fdbe93b67c7086876f061f660cfddc44f404279c1585bbf3cdc64c5"},
+ {file = "greenlet-2.0.2-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:910841381caba4f744a44bf81bfd573c94e10b3045ee00de0cbf436fe50673a6"},
+ {file = "greenlet-2.0.2-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:18a7f18b82b52ee85322d7a7874e676f34ab319b9f8cce5de06067384aa8ff43"},
+ {file = "greenlet-2.0.2-cp35-cp35m-win32.whl", hash = "sha256:03a8f4f3430c3b3ff8d10a2a86028c660355ab637cee9333d63d66b56f09d52a"},
+ {file = "greenlet-2.0.2-cp35-cp35m-win_amd64.whl", hash = "sha256:4b58adb399c4d61d912c4c331984d60eb66565175cdf4a34792cd9600f21b394"},
+ {file = "greenlet-2.0.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:703f18f3fda276b9a916f0934d2fb6d989bf0b4fb5a64825260eb9bfd52d78f0"},
+ {file = "greenlet-2.0.2-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:32e5b64b148966d9cccc2c8d35a671409e45f195864560829f395a54226408d3"},
+ {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2dd11f291565a81d71dab10b7033395b7a3a5456e637cf997a6f33ebdf06f8db"},
+ {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0f72c9ddb8cd28532185f54cc1453f2c16fb417a08b53a855c4e6a418edd099"},
+ {file = "greenlet-2.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd021c754b162c0fb55ad5d6b9d960db667faad0fa2ff25bb6e1301b0b6e6a75"},
+ {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:3c9b12575734155d0c09d6c3e10dbd81665d5c18e1a7c6597df72fd05990c8cf"},
+ {file = "greenlet-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:b9ec052b06a0524f0e35bd8790686a1da006bd911dd1ef7d50b77bfbad74e292"},
+ {file = "greenlet-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:dbfcfc0218093a19c252ca8eb9aee3d29cfdcb586df21049b9d777fd32c14fd9"},
+ {file = "greenlet-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:9f35ec95538f50292f6d8f2c9c9f8a3c6540bbfec21c9e5b4b751e0a7c20864f"},
+ {file = "greenlet-2.0.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5508f0b173e6aa47273bdc0a0b5ba055b59662ba7c7ee5119528f466585526b"},
+ {file = "greenlet-2.0.2-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:f82d4d717d8ef19188687aa32b8363e96062911e63ba22a0cff7802a8e58e5f1"},
+ {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9c59a2120b55788e800d82dfa99b9e156ff8f2227f07c5e3012a45a399620b7"},
+ {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2780572ec463d44c1d3ae850239508dbeb9fed38e294c68d19a24d925d9223ca"},
+ {file = "greenlet-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:937e9020b514ceedb9c830c55d5c9872abc90f4b5862f89c0887033ae33c6f73"},
+ {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:36abbf031e1c0f79dd5d596bfaf8e921c41df2bdf54ee1eed921ce1f52999a86"},
+ {file = "greenlet-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:18e98fb3de7dba1c0a852731c3070cf022d14f0d68b4c87a19cc1016f3bb8b33"},
+ {file = "greenlet-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:3f6ea9bd35eb450837a3d80e77b517ea5bc56b4647f5502cd28de13675ee12f7"},
+ {file = "greenlet-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:7492e2b7bd7c9b9916388d9df23fa49d9b88ac0640db0a5b4ecc2b653bf451e3"},
+ {file = "greenlet-2.0.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:b864ba53912b6c3ab6bcb2beb19f19edd01a6bfcbdfe1f37ddd1778abfe75a30"},
+ {file = "greenlet-2.0.2-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ba2956617f1c42598a308a84c6cf021a90ff3862eddafd20c3333d50f0edb45b"},
+ {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3a569657468b6f3fb60587e48356fe512c1754ca05a564f11366ac9e306526"},
+ {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8eab883b3b2a38cc1e050819ef06a7e6344d4a990d24d45bc6f2cf959045a45b"},
+ {file = "greenlet-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acd2162a36d3de67ee896c43effcd5ee3de247eb00354db411feb025aa319857"},
+ {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0bf60faf0bc2468089bdc5edd10555bab6e85152191df713e2ab1fcc86382b5a"},
+ {file = "greenlet-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b0ef99cdbe2b682b9ccbb964743a6aca37905fda5e0452e5ee239b1654d37f2a"},
+ {file = "greenlet-2.0.2-cp38-cp38-win32.whl", hash = "sha256:b80f600eddddce72320dbbc8e3784d16bd3fb7b517e82476d8da921f27d4b249"},
+ {file = "greenlet-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:4d2e11331fc0c02b6e84b0d28ece3a36e0548ee1a1ce9ddde03752d9b79bba40"},
+ {file = "greenlet-2.0.2-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:88d9ab96491d38a5ab7c56dd7a3cc37d83336ecc564e4e8816dbed12e5aaefc8"},
+ {file = "greenlet-2.0.2-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:561091a7be172ab497a3527602d467e2b3fbe75f9e783d8b8ce403fa414f71a6"},
+ {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:971ce5e14dc5e73715755d0ca2975ac88cfdaefcaab078a284fea6cfabf866df"},
+ {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be4ed120b52ae4d974aa40215fcdfde9194d63541c7ded40ee12eb4dda57b76b"},
+ {file = "greenlet-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94c817e84245513926588caf1152e3b559ff794d505555211ca041f032abbb6b"},
+ {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1a819eef4b0e0b96bb0d98d797bef17dc1b4a10e8d7446be32d1da33e095dbb8"},
+ {file = "greenlet-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7efde645ca1cc441d6dc4b48c0f7101e8d86b54c8530141b09fd31cef5149ec9"},
+ {file = "greenlet-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ea9872c80c132f4663822dd2a08d404073a5a9b5ba6155bea72fb2a79d1093b5"},
+ {file = "greenlet-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:db1a39669102a1d8d12b57de2bb7e2ec9066a6f2b3da35ae511ff93b01b5d564"},
+ {file = "greenlet-2.0.2.tar.gz", hash = "sha256:e7c8dc13af7db097bed64a051d2dd49e9f0af495c26995c00a9ee842690d34c0"},
+]
+
+[package.extras]
+docs = ["Sphinx", "docutils (<0.18)"]
+test = ["objgraph", "psutil"]
+
+[[package]]
+name = "h11"
+version = "0.14.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
+]
+
+[[package]]
+name = "html5tagger"
+version = "1.3.0"
+description = "Pythonic HTML generation/templating (no template files)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "html5tagger-1.3.0-py3-none-any.whl", hash = "sha256:ce14313515edffec8ed8a36c5890d023922641171b4e6e5774ad1a74998f5351"},
+ {file = "html5tagger-1.3.0.tar.gz", hash = "sha256:84fa3dfb49e5c83b79bbd856ab7b1de8e2311c3bb46a8be925f119e3880a8da9"},
+]
+
+[[package]]
+name = "httptools"
+version = "0.5.0"
+description = "A collection of framework independent HTTP protocol utils."
+optional = false
+python-versions = ">=3.5.0"
+files = [
+ {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8f470c79061599a126d74385623ff4744c4e0f4a0997a353a44923c0b561ee51"},
+ {file = "httptools-0.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e90491a4d77d0cb82e0e7a9cb35d86284c677402e4ce7ba6b448ccc7325c5421"},
+ {file = "httptools-0.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1d2357f791b12d86faced7b5736dea9ef4f5ecdc6c3f253e445ee82da579449"},
+ {file = "httptools-0.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f90cd6fd97c9a1b7fe9215e60c3bd97336742a0857f00a4cb31547bc22560c2"},
+ {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5230a99e724a1bdbbf236a1b58d6e8504b912b0552721c7c6b8570925ee0ccde"},
+ {file = "httptools-0.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3a47a34f6015dd52c9eb629c0f5a8a5193e47bf2a12d9a3194d231eaf1bc451a"},
+ {file = "httptools-0.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:24bb4bb8ac3882f90aa95403a1cb48465de877e2d5298ad6ddcfdebec060787d"},
+ {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e67d4f8734f8054d2c4858570cc4b233bf753f56e85217de4dfb2495904cf02e"},
+ {file = "httptools-0.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7e5eefc58d20e4c2da82c78d91b2906f1a947ef42bd668db05f4ab4201a99f49"},
+ {file = "httptools-0.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0297822cea9f90a38df29f48e40b42ac3d48a28637368f3ec6d15eebefd182f9"},
+ {file = "httptools-0.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:557be7fbf2bfa4a2ec65192c254e151684545ebab45eca5d50477d562c40f986"},
+ {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:54465401dbbec9a6a42cf737627fb0f014d50dc7365a6b6cd57753f151a86ff0"},
+ {file = "httptools-0.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4d9ebac23d2de960726ce45f49d70eb5466725c0087a078866043dad115f850f"},
+ {file = "httptools-0.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:e8a34e4c0ab7b1ca17b8763613783e2458e77938092c18ac919420ab8655c8c1"},
+ {file = "httptools-0.5.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f659d7a48401158c59933904040085c200b4be631cb5f23a7d561fbae593ec1f"},
+ {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef1616b3ba965cd68e6f759eeb5d34fbf596a79e84215eeceebf34ba3f61fdc7"},
+ {file = "httptools-0.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3625a55886257755cb15194efbf209584754e31d336e09e2ffe0685a76cb4b60"},
+ {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:72ad589ba5e4a87e1d404cc1cb1b5780bfcb16e2aec957b88ce15fe879cc08ca"},
+ {file = "httptools-0.5.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:850fec36c48df5a790aa735417dca8ce7d4b48d59b3ebd6f83e88a8125cde324"},
+ {file = "httptools-0.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f222e1e9d3f13b68ff8a835574eda02e67277d51631d69d7cf7f8e07df678c86"},
+ {file = "httptools-0.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3cb8acf8f951363b617a8420768a9f249099b92e703c052f9a51b66342eea89b"},
+ {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550059885dc9c19a072ca6d6735739d879be3b5959ec218ba3e013fd2255a11b"},
+ {file = "httptools-0.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a04fe458a4597aa559b79c7f48fe3dceabef0f69f562daf5c5e926b153817281"},
+ {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d0c1044bce274ec6711f0770fd2d5544fe392591d204c68328e60a46f88843b"},
+ {file = "httptools-0.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c6eeefd4435055a8ebb6c5cc36111b8591c192c56a95b45fe2af22d9881eee25"},
+ {file = "httptools-0.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5b65be160adcd9de7a7e6413a4966665756e263f0d5ddeffde277ffeee0576a5"},
+ {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fe9c766a0c35b7e3d6b6939393c8dfdd5da3ac5dec7f971ec9134f284c6c36d6"},
+ {file = "httptools-0.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:85b392aba273566c3d5596a0a490978c085b79700814fb22bfd537d381dd230c"},
+ {file = "httptools-0.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5e3088f4ed33947e16fd865b8200f9cfae1144f41b64a8cf19b599508e096bc"},
+ {file = "httptools-0.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c2a56b6aad7cc8f5551d8e04ff5a319d203f9d870398b94702300de50190f63"},
+ {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9b571b281a19762adb3f48a7731f6842f920fa71108aff9be49888320ac3e24d"},
+ {file = "httptools-0.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa47ffcf70ba6f7848349b8a6f9b481ee0f7637931d91a9860a1838bfc586901"},
+ {file = "httptools-0.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:bede7ee075e54b9a5bde695b4fc8f569f30185891796b2e4e09e2226801d09bd"},
+ {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:64eba6f168803a7469866a9c9b5263a7463fa8b7a25b35e547492aa7322036b6"},
+ {file = "httptools-0.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4b098e4bb1174096a93f48f6193e7d9aa7071506a5877da09a783509ca5fff42"},
+ {file = "httptools-0.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9423a2de923820c7e82e18980b937893f4aa8251c43684fa1772e341f6e06887"},
+ {file = "httptools-0.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca1b7becf7d9d3ccdbb2f038f665c0f4857e08e1d8481cbcc1a86a0afcfb62b2"},
+ {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:50d4613025f15f4b11f1c54bbed4761c0020f7f921b95143ad6d58c151198142"},
+ {file = "httptools-0.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8ffce9d81c825ac1deaa13bc9694c0562e2840a48ba21cfc9f3b4c922c16f372"},
+ {file = "httptools-0.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:1af91b3650ce518d226466f30bbba5b6376dbd3ddb1b2be8b0658c6799dd450b"},
+ {file = "httptools-0.5.0.tar.gz", hash = "sha256:295874861c173f9101960bba332429bb77ed4dcd8cdf5cee9922eb00e4f6bc09"},
+]
+
+[package.extras]
+test = ["Cython (>=0.29.24,<0.30.0)"]
+
+[[package]]
+name = "idna"
+version = "3.4"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
+ {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
+]
+
+[[package]]
+name = "imagesize"
+version = "1.4.1"
+description = "Getting image size from png/jpeg/jpeg2000/gif file"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"},
+ {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"},
+]
+
+[[package]]
+name = "importlib-metadata"
+version = "6.6.0"
+description = "Read metadata from Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "importlib_metadata-6.6.0-py3-none-any.whl", hash = "sha256:43dd286a2cd8995d5eaef7fee2066340423b818ed3fd70adf0bad5f1fac53fed"},
+ {file = "importlib_metadata-6.6.0.tar.gz", hash = "sha256:92501cdf9cc66ebd3e612f1b4f0c0765dfa42f0fa38ffb319b6bd84dd675d705"},
+]
+
+[package.dependencies]
+zipp = ">=0.5"
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+perf = ["ipython"]
+testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
+
+[[package]]
+name = "importlib-resources"
+version = "5.12.0"
+description = "Read resources from Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"},
+ {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"},
+]
+
+[package.dependencies]
+zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
+[[package]]
+name = "itsdangerous"
+version = "2.1.2"
+description = "Safely pass data to untrusted environments and back."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"},
+ {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"},
+]
+
+[[package]]
+name = "jinja2"
+version = "3.1.2"
+description = "A very fast and expressive template engine."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
+ {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+]
+
+[package.dependencies]
+MarkupSafe = ">=2.0"
+
+[package.extras]
+i18n = ["Babel (>=2.7)"]
+
+[[package]]
+name = "jsonpatch"
+version = "1.32"
+description = "Apply JSON-Patches (RFC 6902)"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+files = [
+ {file = "jsonpatch-1.32-py2.py3-none-any.whl", hash = "sha256:26ac385719ac9f54df8a2f0827bb8253aa3ea8ab7b3368457bcdb8c14595a397"},
+ {file = "jsonpatch-1.32.tar.gz", hash = "sha256:b6ddfe6c3db30d81a96aaeceb6baf916094ffa23d7dd5fa2c13e13f8b6e600c2"},
+]
+
+[package.dependencies]
+jsonpointer = ">=1.9"
+
+[[package]]
+name = "jsonpointer"
+version = "2.3"
+description = "Identify specific nodes in a JSON document (RFC 6901)"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+files = [
+ {file = "jsonpointer-2.3-py2.py3-none-any.whl", hash = "sha256:51801e558539b4e9cd268638c078c6c5746c9ac96bc38152d443400e4f3793e9"},
+ {file = "jsonpointer-2.3.tar.gz", hash = "sha256:97cba51526c829282218feb99dab1b1e6bdf8efd1c43dc9d57be093c0d69c99a"},
+]
+
+[[package]]
+name = "kiwisolver"
+version = "1.4.4"
+description = "A fast implementation of the Cassowary constraint solver"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"},
+ {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"},
+ {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"},
+ {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"},
+ {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"},
+ {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"},
+ {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"},
+ {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"},
+ {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"},
+ {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"},
+ {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"},
+ {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"},
+ {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"},
+ {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"},
+ {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"},
+ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"},
+ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"},
+ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"},
+ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"},
+ {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"},
+ {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"},
+]
+
+[[package]]
+name = "livereload"
+version = "2.6.3"
+description = "Python LiveReload is an awesome tool for web developers"
+optional = false
+python-versions = "*"
+files = [
+ {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"},
+ {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"},
+]
+
+[package.dependencies]
+six = "*"
+tornado = {version = "*", markers = "python_version > \"2.7\""}
+
+[[package]]
+name = "lxml"
+version = "4.9.2"
+description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
+files = [
+ {file = "lxml-4.9.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:76cf573e5a365e790396a5cc2b909812633409306c6531a6877c59061e42c4f2"},
+ {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1f42b6921d0e81b1bcb5e395bc091a70f41c4d4e55ba99c6da2b31626c44892"},
+ {file = "lxml-4.9.2-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9f102706d0ca011de571de32c3247c6476b55bb6bc65a20f682f000b07a4852a"},
+ {file = "lxml-4.9.2-cp27-cp27m-win32.whl", hash = "sha256:8d0b4612b66ff5d62d03bcaa043bb018f74dfea51184e53f067e6fdcba4bd8de"},
+ {file = "lxml-4.9.2-cp27-cp27m-win_amd64.whl", hash = "sha256:4c8f293f14abc8fd3e8e01c5bd86e6ed0b6ef71936ded5bf10fe7a5efefbaca3"},
+ {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2899456259589aa38bfb018c364d6ae7b53c5c22d8e27d0ec7609c2a1ff78b50"},
+ {file = "lxml-4.9.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6749649eecd6a9871cae297bffa4ee76f90b4504a2a2ab528d9ebe912b101975"},
+ {file = "lxml-4.9.2-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:a08cff61517ee26cb56f1e949cca38caabe9ea9fbb4b1e10a805dc39844b7d5c"},
+ {file = "lxml-4.9.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:85cabf64adec449132e55616e7ca3e1000ab449d1d0f9d7f83146ed5bdcb6d8a"},
+ {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8340225bd5e7a701c0fa98284c849c9b9fc9238abf53a0ebd90900f25d39a4e4"},
+ {file = "lxml-4.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:1ab8f1f932e8f82355e75dda5413a57612c6ea448069d4fb2e217e9a4bed13d4"},
+ {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:699a9af7dffaf67deeae27b2112aa06b41c370d5e7633e0ee0aea2e0b6c211f7"},
+ {file = "lxml-4.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9cc34af337a97d470040f99ba4282f6e6bac88407d021688a5d585e44a23184"},
+ {file = "lxml-4.9.2-cp310-cp310-win32.whl", hash = "sha256:d02a5399126a53492415d4906ab0ad0375a5456cc05c3fc0fc4ca11771745cda"},
+ {file = "lxml-4.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:a38486985ca49cfa574a507e7a2215c0c780fd1778bb6290c21193b7211702ab"},
+ {file = "lxml-4.9.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c83203addf554215463b59f6399835201999b5e48019dc17f182ed5ad87205c9"},
+ {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:2a87fa548561d2f4643c99cd13131acb607ddabb70682dcf1dff5f71f781a4bf"},
+ {file = "lxml-4.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:d6b430a9938a5a5d85fc107d852262ddcd48602c120e3dbb02137c83d212b380"},
+ {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3efea981d956a6f7173b4659849f55081867cf897e719f57383698af6f618a92"},
+ {file = "lxml-4.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:df0623dcf9668ad0445e0558a21211d4e9a149ea8f5666917c8eeec515f0a6d1"},
+ {file = "lxml-4.9.2-cp311-cp311-win32.whl", hash = "sha256:da248f93f0418a9e9d94b0080d7ebc407a9a5e6d0b57bb30db9b5cc28de1ad33"},
+ {file = "lxml-4.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:3818b8e2c4b5148567e1b09ce739006acfaa44ce3156f8cbbc11062994b8e8dd"},
+ {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca989b91cf3a3ba28930a9fc1e9aeafc2a395448641df1f387a2d394638943b0"},
+ {file = "lxml-4.9.2-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:822068f85e12a6e292803e112ab876bc03ed1f03dddb80154c395f891ca6b31e"},
+ {file = "lxml-4.9.2-cp35-cp35m-win32.whl", hash = "sha256:be7292c55101e22f2a3d4d8913944cbea71eea90792bf914add27454a13905df"},
+ {file = "lxml-4.9.2-cp35-cp35m-win_amd64.whl", hash = "sha256:998c7c41910666d2976928c38ea96a70d1aa43be6fe502f21a651e17483a43c5"},
+ {file = "lxml-4.9.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:b26a29f0b7fc6f0897f043ca366142d2b609dc60756ee6e4e90b5f762c6adc53"},
+ {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:ab323679b8b3030000f2be63e22cdeea5b47ee0abd2d6a1dc0c8103ddaa56cd7"},
+ {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:689bb688a1db722485e4610a503e3e9210dcc20c520b45ac8f7533c837be76fe"},
+ {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f49e52d174375a7def9915c9f06ec4e569d235ad428f70751765f48d5926678c"},
+ {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:36c3c175d34652a35475a73762b545f4527aec044910a651d2bf50de9c3352b1"},
+ {file = "lxml-4.9.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a35f8b7fa99f90dd2f5dc5a9fa12332642f087a7641289ca6c40d6e1a2637d8e"},
+ {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:58bfa3aa19ca4c0f28c5dde0ff56c520fbac6f0daf4fac66ed4c8d2fb7f22e74"},
+ {file = "lxml-4.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc718cd47b765e790eecb74d044cc8d37d58562f6c314ee9484df26276d36a38"},
+ {file = "lxml-4.9.2-cp36-cp36m-win32.whl", hash = "sha256:d5bf6545cd27aaa8a13033ce56354ed9e25ab0e4ac3b5392b763d8d04b08e0c5"},
+ {file = "lxml-4.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:3ab9fa9d6dc2a7f29d7affdf3edebf6ece6fb28a6d80b14c3b2fb9d39b9322c3"},
+ {file = "lxml-4.9.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:05ca3f6abf5cf78fe053da9b1166e062ade3fa5d4f92b4ed688127ea7d7b1d03"},
+ {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:a5da296eb617d18e497bcf0a5c528f5d3b18dadb3619fbdadf4ed2356ef8d941"},
+ {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:04876580c050a8c5341d706dd464ff04fd597095cc8c023252566a8826505726"},
+ {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:c9ec3eaf616d67db0764b3bb983962b4f385a1f08304fd30c7283954e6a7869b"},
+ {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2a29ba94d065945944016b6b74e538bdb1751a1db6ffb80c9d3c2e40d6fa9894"},
+ {file = "lxml-4.9.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a82d05da00a58b8e4c0008edbc8a4b6ec5a4bc1e2ee0fb6ed157cf634ed7fa45"},
+ {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:223f4232855ade399bd409331e6ca70fb5578efef22cf4069a6090acc0f53c0e"},
+ {file = "lxml-4.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:d17bc7c2ccf49c478c5bdd447594e82692c74222698cfc9b5daae7ae7e90743b"},
+ {file = "lxml-4.9.2-cp37-cp37m-win32.whl", hash = "sha256:b64d891da92e232c36976c80ed7ebb383e3f148489796d8d31a5b6a677825efe"},
+ {file = "lxml-4.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a0a336d6d3e8b234a3aae3c674873d8f0e720b76bc1d9416866c41cd9500ffb9"},
+ {file = "lxml-4.9.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:da4dd7c9c50c059aba52b3524f84d7de956f7fef88f0bafcf4ad7dde94a064e8"},
+ {file = "lxml-4.9.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:821b7f59b99551c69c85a6039c65b75f5683bdc63270fec660f75da67469ca24"},
+ {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e5168986b90a8d1f2f9dc1b841467c74221bd752537b99761a93d2d981e04889"},
+ {file = "lxml-4.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:8e20cb5a47247e383cf4ff523205060991021233ebd6f924bca927fcf25cf86f"},
+ {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:13598ecfbd2e86ea7ae45ec28a2a54fb87ee9b9fdb0f6d343297d8e548392c03"},
+ {file = "lxml-4.9.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:880bbbcbe2fca64e2f4d8e04db47bcdf504936fa2b33933efd945e1b429bea8c"},
+ {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7d2278d59425777cfcb19735018d897ca8303abe67cc735f9f97177ceff8027f"},
+ {file = "lxml-4.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5344a43228767f53a9df6e5b253f8cdca7dfc7b7aeae52551958192f56d98457"},
+ {file = "lxml-4.9.2-cp38-cp38-win32.whl", hash = "sha256:925073b2fe14ab9b87e73f9a5fde6ce6392da430f3004d8b72cc86f746f5163b"},
+ {file = "lxml-4.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:9b22c5c66f67ae00c0199f6055705bc3eb3fcb08d03d2ec4059a2b1b25ed48d7"},
+ {file = "lxml-4.9.2-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5f50a1c177e2fa3ee0667a5ab79fdc6b23086bc8b589d90b93b4bd17eb0e64d1"},
+ {file = "lxml-4.9.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:090c6543d3696cbe15b4ac6e175e576bcc3f1ccfbba970061b7300b0c15a2140"},
+ {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:63da2ccc0857c311d764e7d3d90f429c252e83b52d1f8f1d1fe55be26827d1f4"},
+ {file = "lxml-4.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5b4545b8a40478183ac06c073e81a5ce4cf01bf1734962577cf2bb569a5b3bbf"},
+ {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2e430cd2824f05f2d4f687701144556646bae8f249fd60aa1e4c768ba7018947"},
+ {file = "lxml-4.9.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6804daeb7ef69e7b36f76caddb85cccd63d0c56dedb47555d2fc969e2af6a1a5"},
+ {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a6e441a86553c310258aca15d1c05903aaf4965b23f3bc2d55f200804e005ee5"},
+ {file = "lxml-4.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ca34efc80a29351897e18888c71c6aca4a359247c87e0b1c7ada14f0ab0c0fb2"},
+ {file = "lxml-4.9.2-cp39-cp39-win32.whl", hash = "sha256:6b418afe5df18233fc6b6093deb82a32895b6bb0b1155c2cdb05203f583053f1"},
+ {file = "lxml-4.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:f1496ea22ca2c830cbcbd473de8f114a320da308438ae65abad6bab7867fe38f"},
+ {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b264171e3143d842ded311b7dccd46ff9ef34247129ff5bf5066123c55c2431c"},
+ {file = "lxml-4.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0dc313ef231edf866912e9d8f5a042ddab56c752619e92dfd3a2c277e6a7299a"},
+ {file = "lxml-4.9.2-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:16efd54337136e8cd72fb9485c368d91d77a47ee2d42b057564aae201257d419"},
+ {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0f2b1e0d79180f344ff9f321327b005ca043a50ece8713de61d1cb383fb8ac05"},
+ {file = "lxml-4.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:7b770ed79542ed52c519119473898198761d78beb24b107acf3ad65deae61f1f"},
+ {file = "lxml-4.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:efa29c2fe6b4fdd32e8ef81c1528506895eca86e1d8c4657fda04c9b3786ddf9"},
+ {file = "lxml-4.9.2-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7e91ee82f4199af8c43d8158024cbdff3d931df350252288f0d4ce656df7f3b5"},
+ {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b23e19989c355ca854276178a0463951a653309fb8e57ce674497f2d9f208746"},
+ {file = "lxml-4.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:01d36c05f4afb8f7c20fd9ed5badca32a2029b93b1750f571ccc0b142531caf7"},
+ {file = "lxml-4.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7b515674acfdcadb0eb5d00d8a709868173acece5cb0be3dd165950cbfdf5409"},
+ {file = "lxml-4.9.2.tar.gz", hash = "sha256:2455cfaeb7ac70338b3257f41e21f0724f4b5b0c0e7702da67ee6c3640835b67"},
+]
+
+[package.extras]
+cssselect = ["cssselect (>=0.7)"]
+html5 = ["html5lib"]
+htmlsoup = ["BeautifulSoup4"]
+source = ["Cython (>=0.29.7)"]
+
+[[package]]
+name = "markupsafe"
+version = "2.0.1"
+description = "Safely add untrusted strings to HTML/XML markup."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
+ {file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
+ {file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
+ {file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
+ {file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
+ {file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
+ {file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
+]
+
+[[package]]
+name = "matplotlib"
+version = "3.7.1"
+description = "Python plotting package"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:95cbc13c1fc6844ab8812a525bbc237fa1470863ff3dace7352e910519e194b1"},
+ {file = "matplotlib-3.7.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:08308bae9e91aca1ec6fd6dda66237eef9f6294ddb17f0d0b3c863169bf82353"},
+ {file = "matplotlib-3.7.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:544764ba51900da4639c0f983b323d288f94f65f4024dc40ecb1542d74dc0500"},
+ {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:56d94989191de3fcc4e002f93f7f1be5da476385dde410ddafbb70686acf00ea"},
+ {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99bc9e65901bb9a7ce5e7bb24af03675cbd7c70b30ac670aa263240635999a4"},
+ {file = "matplotlib-3.7.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb7d248c34a341cd4c31a06fd34d64306624c8cd8d0def7abb08792a5abfd556"},
+ {file = "matplotlib-3.7.1-cp310-cp310-win32.whl", hash = "sha256:ce463ce590f3825b52e9fe5c19a3c6a69fd7675a39d589e8b5fbe772272b3a24"},
+ {file = "matplotlib-3.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:3d7bc90727351fb841e4d8ae620d2d86d8ed92b50473cd2b42ce9186104ecbba"},
+ {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:770a205966d641627fd5cf9d3cb4b6280a716522cd36b8b284a8eb1581310f61"},
+ {file = "matplotlib-3.7.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f67bfdb83a8232cb7a92b869f9355d677bce24485c460b19d01970b64b2ed476"},
+ {file = "matplotlib-3.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2bf092f9210e105f414a043b92af583c98f50050559616930d884387d0772aba"},
+ {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89768d84187f31717349c6bfadc0e0d8c321e8eb34522acec8a67b1236a66332"},
+ {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83111e6388dec67822e2534e13b243cc644c7494a4bb60584edbff91585a83c6"},
+ {file = "matplotlib-3.7.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a867bf73a7eb808ef2afbca03bcdb785dae09595fbe550e1bab0cd023eba3de0"},
+ {file = "matplotlib-3.7.1-cp311-cp311-win32.whl", hash = "sha256:fbdeeb58c0cf0595efe89c05c224e0a502d1aa6a8696e68a73c3efc6bc354304"},
+ {file = "matplotlib-3.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:c0bd19c72ae53e6ab979f0ac6a3fafceb02d2ecafa023c5cca47acd934d10be7"},
+ {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:6eb88d87cb2c49af00d3bbc33a003f89fd9f78d318848da029383bfc08ecfbfb"},
+ {file = "matplotlib-3.7.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:cf0e4f727534b7b1457898c4f4ae838af1ef87c359b76dcd5330fa31893a3ac7"},
+ {file = "matplotlib-3.7.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a561d23b91f30bccfd25429c3c706afe7d73a5cc64ef2dfaf2b2ac47c1a5dc"},
+ {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8704726d33e9aa8a6d5215044b8d00804561971163563e6e6591f9dcf64340cc"},
+ {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4cf327e98ecf08fcbb82685acaf1939d3338548620ab8dfa02828706402c34de"},
+ {file = "matplotlib-3.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:617f14ae9d53292ece33f45cba8503494ee199a75b44de7717964f70637a36aa"},
+ {file = "matplotlib-3.7.1-cp38-cp38-win32.whl", hash = "sha256:7c9a4b2da6fac77bcc41b1ea95fadb314e92508bf5493ceff058e727e7ecf5b0"},
+ {file = "matplotlib-3.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:14645aad967684e92fc349493fa10c08a6da514b3d03a5931a1bac26e6792bd1"},
+ {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:81a6b377ea444336538638d31fdb39af6be1a043ca5e343fe18d0f17e098770b"},
+ {file = "matplotlib-3.7.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:28506a03bd7f3fe59cd3cd4ceb2a8d8a2b1db41afede01f66c42561b9be7b4b7"},
+ {file = "matplotlib-3.7.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8c587963b85ce41e0a8af53b9b2de8dddbf5ece4c34553f7bd9d066148dc719c"},
+ {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8bf26ade3ff0f27668989d98c8435ce9327d24cffb7f07d24ef609e33d582439"},
+ {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:def58098f96a05f90af7e92fd127d21a287068202aa43b2a93476170ebd99e87"},
+ {file = "matplotlib-3.7.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f883a22a56a84dba3b588696a2b8a1ab0d2c3d41be53264115c71b0a942d8fdb"},
+ {file = "matplotlib-3.7.1-cp39-cp39-win32.whl", hash = "sha256:4f99e1b234c30c1e9714610eb0c6d2f11809c9c78c984a613ae539ea2ad2eb4b"},
+ {file = "matplotlib-3.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:3ba2af245e36990facf67fde840a760128ddd71210b2ab6406e640188d69d136"},
+ {file = "matplotlib-3.7.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3032884084f541163f295db8a6536e0abb0db464008fadca6c98aaf84ccf4717"},
+ {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a2cb34336110e0ed8bb4f650e817eed61fa064acbefeb3591f1b33e3a84fd96"},
+ {file = "matplotlib-3.7.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b867e2f952ed592237a1828f027d332d8ee219ad722345b79a001f49df0936eb"},
+ {file = "matplotlib-3.7.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:57bfb8c8ea253be947ccb2bc2d1bb3862c2bccc662ad1b4626e1f5e004557042"},
+ {file = "matplotlib-3.7.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:438196cdf5dc8d39b50a45cb6e3f6274edbcf2254f85fa9b895bf85851c3a613"},
+ {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:21e9cff1a58d42e74d01153360de92b326708fb205250150018a52c70f43c290"},
+ {file = "matplotlib-3.7.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75d4725d70b7c03e082bbb8a34639ede17f333d7247f56caceb3801cb6ff703d"},
+ {file = "matplotlib-3.7.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:97cc368a7268141afb5690760921765ed34867ffb9655dd325ed207af85c7529"},
+ {file = "matplotlib-3.7.1.tar.gz", hash = "sha256:7b73305f25eab4541bd7ee0b96d87e53ae9c9f1823be5659b806cd85786fe882"},
+]
+
+[package.dependencies]
+contourpy = ">=1.0.1"
+cycler = ">=0.10"
+fonttools = ">=4.22.0"
+importlib-resources = {version = ">=3.2.0", markers = "python_version < \"3.10\""}
+kiwisolver = ">=1.0.1"
+numpy = ">=1.20"
+packaging = ">=20.0"
+pillow = ">=6.2.0"
+pyparsing = ">=2.3.1"
+python-dateutil = ">=2.7"
+
+[[package]]
+name = "multidict"
+version = "6.0.4"
+description = "multidict implementation"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"},
+ {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"},
+ {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"},
+ {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"},
+ {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"},
+ {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"},
+ {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"},
+ {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"},
+ {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"},
+ {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"},
+ {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"},
+ {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"},
+ {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"},
+ {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"},
+ {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"},
+ {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"},
+ {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"},
+ {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"},
+ {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"},
+ {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"},
+ {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"},
+ {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"},
+ {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"},
+ {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"},
+ {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"},
+ {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"},
+ {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"},
+ {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"},
+ {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"},
+ {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"},
+ {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"},
+ {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"},
+ {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"},
+ {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"},
+]
+
+[[package]]
+name = "mypy-extensions"
+version = "1.0.0"
+description = "Type system extensions for programs checked with the mypy type checker."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
+ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
+]
+
+[[package]]
+name = "numpy"
+version = "1.24.3"
+description = "Fundamental package for array computing in Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "numpy-1.24.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3c1104d3c036fb81ab923f507536daedc718d0ad5a8707c6061cdfd6d184e570"},
+ {file = "numpy-1.24.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:202de8f38fc4a45a3eea4b63e2f376e5f2dc64ef0fa692838e31a808520efaf7"},
+ {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8535303847b89aa6b0f00aa1dc62867b5a32923e4d1681a35b5eef2d9591a463"},
+ {file = "numpy-1.24.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d926b52ba1367f9acb76b0df6ed21f0b16a1ad87c6720a1121674e5cf63e2b6"},
+ {file = "numpy-1.24.3-cp310-cp310-win32.whl", hash = "sha256:f21c442fdd2805e91799fbe044a7b999b8571bb0ab0f7850d0cb9641a687092b"},
+ {file = "numpy-1.24.3-cp310-cp310-win_amd64.whl", hash = "sha256:ab5f23af8c16022663a652d3b25dcdc272ac3f83c3af4c02eb8b824e6b3ab9d7"},
+ {file = "numpy-1.24.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9a7721ec204d3a237225db3e194c25268faf92e19338a35f3a224469cb6039a3"},
+ {file = "numpy-1.24.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6cc757de514c00b24ae8cf5c876af2a7c3df189028d68c0cb4eaa9cd5afc2bf"},
+ {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76e3f4e85fc5d4fd311f6e9b794d0c00e7002ec122be271f2019d63376f1d385"},
+ {file = "numpy-1.24.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1d3c026f57ceaad42f8231305d4653d5f05dc6332a730ae5c0bea3513de0950"},
+ {file = "numpy-1.24.3-cp311-cp311-win32.whl", hash = "sha256:c91c4afd8abc3908e00a44b2672718905b8611503f7ff87390cc0ac3423fb096"},
+ {file = "numpy-1.24.3-cp311-cp311-win_amd64.whl", hash = "sha256:5342cf6aad47943286afa6f1609cad9b4266a05e7f2ec408e2cf7aea7ff69d80"},
+ {file = "numpy-1.24.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7776ea65423ca6a15255ba1872d82d207bd1e09f6d0894ee4a64678dd2204078"},
+ {file = "numpy-1.24.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ae8d0be48d1b6ed82588934aaaa179875e7dc4f3d84da18d7eae6eb3f06c242c"},
+ {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecde0f8adef7dfdec993fd54b0f78183051b6580f606111a6d789cd14c61ea0c"},
+ {file = "numpy-1.24.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4749e053a29364d3452c034827102ee100986903263e89884922ef01a0a6fd2f"},
+ {file = "numpy-1.24.3-cp38-cp38-win32.whl", hash = "sha256:d933fabd8f6a319e8530d0de4fcc2e6a61917e0b0c271fded460032db42a0fe4"},
+ {file = "numpy-1.24.3-cp38-cp38-win_amd64.whl", hash = "sha256:56e48aec79ae238f6e4395886b5eaed058abb7231fb3361ddd7bfdf4eed54289"},
+ {file = "numpy-1.24.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4719d5aefb5189f50887773699eaf94e7d1e02bf36c1a9d353d9f46703758ca4"},
+ {file = "numpy-1.24.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0ec87a7084caa559c36e0a2309e4ecb1baa03b687201d0a847c8b0ed476a7187"},
+ {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea8282b9bcfe2b5e7d491d0bf7f3e2da29700cec05b49e64d6246923329f2b02"},
+ {file = "numpy-1.24.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:210461d87fb02a84ef243cac5e814aad2b7f4be953b32cb53327bb49fd77fbb4"},
+ {file = "numpy-1.24.3-cp39-cp39-win32.whl", hash = "sha256:784c6da1a07818491b0ffd63c6bbe5a33deaa0e25a20e1b3ea20cf0e43f8046c"},
+ {file = "numpy-1.24.3-cp39-cp39-win_amd64.whl", hash = "sha256:d5036197ecae68d7f491fcdb4df90082b0d4960ca6599ba2659957aafced7c17"},
+ {file = "numpy-1.24.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:352ee00c7f8387b44d19f4cada524586f07379c0d49270f87233983bc5087ca0"},
+ {file = "numpy-1.24.3-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1a7d6acc2e7524c9955e5c903160aa4ea083736fde7e91276b0e5d98e6332812"},
+ {file = "numpy-1.24.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:35400e6a8d102fd07c71ed7dcadd9eb62ee9a6e84ec159bd48c28235bbb0f8e4"},
+ {file = "numpy-1.24.3.tar.gz", hash = "sha256:ab344f1bf21f140adab8e47fdbc7c35a477dc01408791f8ba00d018dd0bc5155"},
+]
+
+[[package]]
+name = "packaging"
+version = "23.1"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "packaging-23.1-py3-none-any.whl", hash = "sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61"},
+ {file = "packaging-23.1.tar.gz", hash = "sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"},
+]
+
+[[package]]
+name = "pillow"
+version = "9.5.0"
+description = "Python Imaging Library (Fork)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Pillow-9.5.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:ace6ca218308447b9077c14ea4ef381ba0b67ee78d64046b3f19cf4e1139ad16"},
+ {file = "Pillow-9.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3d403753c9d5adc04d4694d35cf0391f0f3d57c8e0030aac09d7678fa8030aa"},
+ {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ba1b81ee69573fe7124881762bb4cd2e4b6ed9dd28c9c60a632902fe8db8b38"},
+ {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe7e1c262d3392afcf5071df9afa574544f28eac825284596ac6db56e6d11062"},
+ {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f36397bf3f7d7c6a3abdea815ecf6fd14e7fcd4418ab24bae01008d8d8ca15e"},
+ {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:252a03f1bdddce077eff2354c3861bf437c892fb1832f75ce813ee94347aa9b5"},
+ {file = "Pillow-9.5.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:85ec677246533e27770b0de5cf0f9d6e4ec0c212a1f89dfc941b64b21226009d"},
+ {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b416f03d37d27290cb93597335a2f85ed446731200705b22bb927405320de903"},
+ {file = "Pillow-9.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:1781a624c229cb35a2ac31cc4a77e28cafc8900733a864870c49bfeedacd106a"},
+ {file = "Pillow-9.5.0-cp310-cp310-win32.whl", hash = "sha256:8507eda3cd0608a1f94f58c64817e83ec12fa93a9436938b191b80d9e4c0fc44"},
+ {file = "Pillow-9.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:d3c6b54e304c60c4181da1c9dadf83e4a54fd266a99c70ba646a9baa626819eb"},
+ {file = "Pillow-9.5.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:7ec6f6ce99dab90b52da21cf0dc519e21095e332ff3b399a357c187b1a5eee32"},
+ {file = "Pillow-9.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:560737e70cb9c6255d6dcba3de6578a9e2ec4b573659943a5e7e4af13f298f5c"},
+ {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:96e88745a55b88a7c64fa49bceff363a1a27d9a64e04019c2281049444a571e3"},
+ {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d9c206c29b46cfd343ea7cdfe1232443072bbb270d6a46f59c259460db76779a"},
+ {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfcc2c53c06f2ccb8976fb5c71d448bdd0a07d26d8e07e321c103416444c7ad1"},
+ {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:a0f9bb6c80e6efcde93ffc51256d5cfb2155ff8f78292f074f60f9e70b942d99"},
+ {file = "Pillow-9.5.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8d935f924bbab8f0a9a28404422da8af4904e36d5c33fc6f677e4c4485515625"},
+ {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:fed1e1cf6a42577953abbe8e6cf2fe2f566daebde7c34724ec8803c4c0cda579"},
+ {file = "Pillow-9.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c1170d6b195555644f0616fd6ed929dfcf6333b8675fcca044ae5ab110ded296"},
+ {file = "Pillow-9.5.0-cp311-cp311-win32.whl", hash = "sha256:54f7102ad31a3de5666827526e248c3530b3a33539dbda27c6843d19d72644ec"},
+ {file = "Pillow-9.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfa4561277f677ecf651e2b22dc43e8f5368b74a25a8f7d1d4a3a243e573f2d4"},
+ {file = "Pillow-9.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:965e4a05ef364e7b973dd17fc765f42233415974d773e82144c9bbaaaea5d089"},
+ {file = "Pillow-9.5.0-cp312-cp312-win32.whl", hash = "sha256:22baf0c3cf0c7f26e82d6e1adf118027afb325e703922c8dfc1d5d0156bb2eeb"},
+ {file = "Pillow-9.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:432b975c009cf649420615388561c0ce7cc31ce9b2e374db659ee4f7d57a1f8b"},
+ {file = "Pillow-9.5.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:5d4ebf8e1db4441a55c509c4baa7a0587a0210f7cd25fcfe74dbbce7a4bd1906"},
+ {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:375f6e5ee9620a271acb6820b3d1e94ffa8e741c0601db4c0c4d3cb0a9c224bf"},
+ {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99eb6cafb6ba90e436684e08dad8be1637efb71c4f2180ee6b8f940739406e78"},
+ {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfaaf10b6172697b9bceb9a3bd7b951819d1ca339a5ef294d1f1ac6d7f63270"},
+ {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:763782b2e03e45e2c77d7779875f4432e25121ef002a41829d8868700d119392"},
+ {file = "Pillow-9.5.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:35f6e77122a0c0762268216315bf239cf52b88865bba522999dc38f1c52b9b47"},
+ {file = "Pillow-9.5.0-cp37-cp37m-win32.whl", hash = "sha256:aca1c196f407ec7cf04dcbb15d19a43c507a81f7ffc45b690899d6a76ac9fda7"},
+ {file = "Pillow-9.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:322724c0032af6692456cd6ed554bb85f8149214d97398bb80613b04e33769f6"},
+ {file = "Pillow-9.5.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:a0aa9417994d91301056f3d0038af1199eb7adc86e646a36b9e050b06f526597"},
+ {file = "Pillow-9.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8286396b351785801a976b1e85ea88e937712ee2c3ac653710a4a57a8da5d9c"},
+ {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c830a02caeb789633863b466b9de10c015bded434deb3ec87c768e53752ad22a"},
+ {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fbd359831c1657d69bb81f0db962905ee05e5e9451913b18b831febfe0519082"},
+ {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8fc330c3370a81bbf3f88557097d1ea26cd8b019d6433aa59f71195f5ddebbf"},
+ {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:7002d0797a3e4193c7cdee3198d7c14f92c0836d6b4a3f3046a64bd1ce8df2bf"},
+ {file = "Pillow-9.5.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:229e2c79c00e85989a34b5981a2b67aa079fd08c903f0aaead522a1d68d79e51"},
+ {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9adf58f5d64e474bed00d69bcd86ec4bcaa4123bfa70a65ce72e424bfb88ed96"},
+ {file = "Pillow-9.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:662da1f3f89a302cc22faa9f14a262c2e3951f9dbc9617609a47521c69dd9f8f"},
+ {file = "Pillow-9.5.0-cp38-cp38-win32.whl", hash = "sha256:6608ff3bf781eee0cd14d0901a2b9cc3d3834516532e3bd673a0a204dc8615fc"},
+ {file = "Pillow-9.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:e49eb4e95ff6fd7c0c402508894b1ef0e01b99a44320ba7d8ecbabefddcc5569"},
+ {file = "Pillow-9.5.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:482877592e927fd263028c105b36272398e3e1be3269efda09f6ba21fd83ec66"},
+ {file = "Pillow-9.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3ded42b9ad70e5f1754fb7c2e2d6465a9c842e41d178f262e08b8c85ed8a1d8e"},
+ {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c446d2245ba29820d405315083d55299a796695d747efceb5717a8b450324115"},
+ {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aca1152d93dcc27dc55395604dcfc55bed5f25ef4c98716a928bacba90d33a3"},
+ {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:608488bdcbdb4ba7837461442b90ea6f3079397ddc968c31265c1e056964f1ef"},
+ {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:60037a8db8750e474af7ffc9faa9b5859e6c6d0a50e55c45576bf28be7419705"},
+ {file = "Pillow-9.5.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:07999f5834bdc404c442146942a2ecadd1cb6292f5229f4ed3b31e0a108746b1"},
+ {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a127ae76092974abfbfa38ca2d12cbeddcdeac0fb71f9627cc1135bedaf9d51a"},
+ {file = "Pillow-9.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:489f8389261e5ed43ac8ff7b453162af39c3e8abd730af8363587ba64bb2e865"},
+ {file = "Pillow-9.5.0-cp39-cp39-win32.whl", hash = "sha256:9b1af95c3a967bf1da94f253e56b6286b50af23392a886720f563c547e48e964"},
+ {file = "Pillow-9.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:77165c4a5e7d5a284f10a6efaa39a0ae8ba839da344f20b111d62cc932fa4e5d"},
+ {file = "Pillow-9.5.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:833b86a98e0ede388fa29363159c9b1a294b0905b5128baf01db683672f230f5"},
+ {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaf305d6d40bd9632198c766fb64f0c1a83ca5b667f16c1e79e1661ab5060140"},
+ {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0852ddb76d85f127c135b6dd1f0bb88dbb9ee990d2cd9aa9e28526c93e794fba"},
+ {file = "Pillow-9.5.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:91ec6fe47b5eb5a9968c79ad9ed78c342b1f97a091677ba0e012701add857829"},
+ {file = "Pillow-9.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cb841572862f629b99725ebaec3287fc6d275be9b14443ea746c1dd325053cbd"},
+ {file = "Pillow-9.5.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:c380b27d041209b849ed246b111b7c166ba36d7933ec6e41175fd15ab9eb1572"},
+ {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c9af5a3b406a50e313467e3565fc99929717f780164fe6fbb7704edba0cebbe"},
+ {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5671583eab84af046a397d6d0ba25343c00cd50bce03787948e0fff01d4fd9b1"},
+ {file = "Pillow-9.5.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:84a6f19ce086c1bf894644b43cd129702f781ba5751ca8572f08aa40ef0ab7b7"},
+ {file = "Pillow-9.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:1e7723bd90ef94eda669a3c2c19d549874dd5badaeefabefd26053304abe5799"},
+ {file = "Pillow-9.5.0.tar.gz", hash = "sha256:bf548479d336726d7a0eceb6e767e179fbde37833ae42794602631a070d630f1"},
+]
+
+[package.extras]
+docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"]
+tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
+
+[[package]]
+name = "playwright"
+version = "1.34.0"
+description = "A high-level API to automate web browsers"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "playwright-1.34.0-py3-none-macosx_10_13_x86_64.whl", hash = "sha256:69bb9b3296e366a23a99277b4c7673cb54ce71a3f5d630f114f7701b61f98f25"},
+ {file = "playwright-1.34.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:402d946631c8458436e099d7731bbf54cf79c9e62e3acae0ea8421e72616926b"},
+ {file = "playwright-1.34.0-py3-none-macosx_11_0_universal2.whl", hash = "sha256:462251cda0fcbb273497d357dbe14b11e43ebceb0bac9b892beda041ff209aa9"},
+ {file = "playwright-1.34.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:a8ba124ea302596a03a66993cd500484fb255cbc10fe0757fa4d49f974267a80"},
+ {file = "playwright-1.34.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf0cb6aac49d24335fe361868aea72b11f276a95e7809f1a5d1c69b4120c46ac"},
+ {file = "playwright-1.34.0-py3-none-win32.whl", hash = "sha256:c50fef189d87243cc09ae0feb8e417fbe434359ccbcc863fb19ba06d46d31c33"},
+ {file = "playwright-1.34.0-py3-none-win_amd64.whl", hash = "sha256:42e16c930e1e910461f4c551a72fc1b900f37124431bf2b6a6d9ddae70042db4"},
+]
+
+[package.dependencies]
+greenlet = "2.0.2"
+pyee = "9.0.4"
+
+[[package]]
+name = "pydantic"
+version = "1.10.8"
+description = "Data validation and settings management using python type hints"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "pydantic-1.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1243d28e9b05003a89d72e7915fdb26ffd1d39bdd39b00b7dbe4afae4b557f9d"},
+ {file = "pydantic-1.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0ab53b609c11dfc0c060d94335993cc2b95b2150e25583bec37a49b2d6c6c3f"},
+ {file = "pydantic-1.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9613fadad06b4f3bc5db2653ce2f22e0de84a7c6c293909b48f6ed37b83c61f"},
+ {file = "pydantic-1.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df7800cb1984d8f6e249351139667a8c50a379009271ee6236138a22a0c0f319"},
+ {file = "pydantic-1.10.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0c6fafa0965b539d7aab0a673a046466d23b86e4b0e8019d25fd53f4df62c277"},
+ {file = "pydantic-1.10.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e82d4566fcd527eae8b244fa952d99f2ca3172b7e97add0b43e2d97ee77f81ab"},
+ {file = "pydantic-1.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:ab523c31e22943713d80d8d342d23b6f6ac4b792a1e54064a8d0cf78fd64e800"},
+ {file = "pydantic-1.10.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:666bdf6066bf6dbc107b30d034615d2627e2121506c555f73f90b54a463d1f33"},
+ {file = "pydantic-1.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:35db5301b82e8661fa9c505c800d0990bc14e9f36f98932bb1d248c0ac5cada5"},
+ {file = "pydantic-1.10.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90c1e29f447557e9e26afb1c4dbf8768a10cc676e3781b6a577841ade126b85"},
+ {file = "pydantic-1.10.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e766b4a8226e0708ef243e843105bf124e21331694367f95f4e3b4a92bbb3f"},
+ {file = "pydantic-1.10.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88f195f582851e8db960b4a94c3e3ad25692c1c1539e2552f3df7a9e972ef60e"},
+ {file = "pydantic-1.10.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:34d327c81e68a1ecb52fe9c8d50c8a9b3e90d3c8ad991bfc8f953fb477d42fb4"},
+ {file = "pydantic-1.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:d532bf00f381bd6bc62cabc7d1372096b75a33bc197a312b03f5838b4fb84edd"},
+ {file = "pydantic-1.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d5b8641c24886d764a74ec541d2fc2c7fb19f6da2a4001e6d580ba4a38f7878"},
+ {file = "pydantic-1.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1f6cb446470b7ddf86c2e57cd119a24959af2b01e552f60705910663af09a4"},
+ {file = "pydantic-1.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c33b60054b2136aef8cf190cd4c52a3daa20b2263917c49adad20eaf381e823b"},
+ {file = "pydantic-1.10.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1952526ba40b220b912cdc43c1c32bcf4a58e3f192fa313ee665916b26befb68"},
+ {file = "pydantic-1.10.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bb14388ec45a7a0dc429e87def6396f9e73c8c77818c927b6a60706603d5f2ea"},
+ {file = "pydantic-1.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:16f8c3e33af1e9bb16c7a91fc7d5fa9fe27298e9f299cff6cb744d89d573d62c"},
+ {file = "pydantic-1.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ced8375969673929809d7f36ad322934c35de4af3b5e5b09ec967c21f9f7887"},
+ {file = "pydantic-1.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93e6bcfccbd831894a6a434b0aeb1947f9e70b7468f274154d03d71fabb1d7c6"},
+ {file = "pydantic-1.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:191ba419b605f897ede9892f6c56fb182f40a15d309ef0142212200a10af4c18"},
+ {file = "pydantic-1.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:052d8654cb65174d6f9490cc9b9a200083a82cf5c3c5d3985db765757eb3b375"},
+ {file = "pydantic-1.10.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ceb6a23bf1ba4b837d0cfe378329ad3f351b5897c8d4914ce95b85fba96da5a1"},
+ {file = "pydantic-1.10.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f2e754d5566f050954727c77f094e01793bcb5725b663bf628fa6743a5a9108"},
+ {file = "pydantic-1.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:6a82d6cda82258efca32b40040228ecf43a548671cb174a1e81477195ed3ed56"},
+ {file = "pydantic-1.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e59417ba8a17265e632af99cc5f35ec309de5980c440c255ab1ca3ae96a3e0e"},
+ {file = "pydantic-1.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:84d80219c3f8d4cad44575e18404099c76851bc924ce5ab1c4c8bb5e2a2227d0"},
+ {file = "pydantic-1.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e4148e635994d57d834be1182a44bdb07dd867fa3c2d1b37002000646cc5459"},
+ {file = "pydantic-1.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12f7b0bf8553e310e530e9f3a2f5734c68699f42218bf3568ef49cd9b0e44df4"},
+ {file = "pydantic-1.10.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42aa0c4b5c3025483240a25b09f3c09a189481ddda2ea3a831a9d25f444e03c1"},
+ {file = "pydantic-1.10.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17aef11cc1b997f9d574b91909fed40761e13fac438d72b81f902226a69dac01"},
+ {file = "pydantic-1.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:66a703d1983c675a6e0fed8953b0971c44dba48a929a2000a493c3772eb61a5a"},
+ {file = "pydantic-1.10.8-py3-none-any.whl", hash = "sha256:7456eb22ed9aaa24ff3e7b4757da20d9e5ce2a81018c1b3ebd81a0b88a18f3b2"},
+ {file = "pydantic-1.10.8.tar.gz", hash = "sha256:1410275520dfa70effadf4c21811d755e7ef9bb1f1d077a21958153a92c8d9ca"},
+]
+
+[package.dependencies]
+typing-extensions = ">=4.2.0"
+
+[package.extras]
+dotenv = ["python-dotenv (>=0.10.4)"]
+email = ["email-validator (>=1.0.3)"]
+
+[[package]]
+name = "pyee"
+version = "9.0.4"
+description = "A port of node.js's EventEmitter to python."
+optional = false
+python-versions = "*"
+files = [
+ {file = "pyee-9.0.4-py2.py3-none-any.whl", hash = "sha256:9f066570130c554e9cc12de5a9d86f57c7ee47fece163bbdaa3e9c933cfbdfa5"},
+ {file = "pyee-9.0.4.tar.gz", hash = "sha256:2770c4928abc721f46b705e6a72b0c59480c4a69c9a83ca0b00bb994f1ea4b32"},
+]
+
+[package.dependencies]
+typing-extensions = "*"
+
+[[package]]
+name = "pygments"
+version = "2.15.1"
+description = "Pygments is a syntax highlighting package written in Python."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Pygments-2.15.1-py3-none-any.whl", hash = "sha256:db2db3deb4b4179f399a09054b023b6a586b76499d36965813c71aa8ed7b5fd1"},
+ {file = "Pygments-2.15.1.tar.gz", hash = "sha256:8ace4d3c1dd481894b2005f560ead0f9f19ee64fe983366be1a21e171d12775c"},
+]
+
+[package.extras]
+plugins = ["importlib-metadata"]
+
+[[package]]
+name = "pyparsing"
+version = "3.0.9"
+description = "pyparsing module - Classes and methods to define and execute parsing grammars"
+optional = false
+python-versions = ">=3.6.8"
+files = [
+ {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
+ {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
+]
+
+[package.extras]
+diagrams = ["jinja2", "railroad-diagrams"]
+
+[[package]]
+name = "python-dateutil"
+version = "2.8.2"
+description = "Extensions to the standard Python datetime module"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
+files = [
+ {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
+ {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
+]
+
+[package.dependencies]
+six = ">=1.5"
+
+[[package]]
+name = "python-dotenv"
+version = "1.0.0"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
+ {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
+[[package]]
+name = "pyyaml"
+version = "6.0"
+description = "YAML parser and emitter for Python"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
+ {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
+ {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
+ {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
+ {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
+ {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
+ {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
+ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
+ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
+ {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
+ {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
+ {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
+ {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
+ {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
+ {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
+ {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
+ {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
+ {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
+ {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
+ {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
+ {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
+ {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
+ {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
+ {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
+ {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
+ {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
+ {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
+ {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
+ {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
+ {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
+]
+
+[[package]]
+name = "reactpy"
+version = "1.0.0"
+description = "Reactive user interfaces with pure Python"
+optional = false
+python-versions = ">=3.9"
+files = []
+develop = false
+
+[package.dependencies]
+anyio = ">=3"
+asgiref = ">=3"
+colorlog = ">=6"
+fastapi = {version = ">=0.63.0", optional = true, markers = "extra == \"fastapi\""}
+fastjsonschema = ">=2.14.5"
+flask = {version = "*", optional = true, markers = "extra == \"flask\""}
+flask-cors = {version = "*", optional = true, markers = "extra == \"flask\""}
+flask-sock = {version = "*", optional = true, markers = "extra == \"flask\""}
+jsonpatch = ">=1.32"
+lxml = ">=4"
+markupsafe = {version = ">=1.1.1,<2.1", optional = true, markers = "extra == \"flask\""}
+mypy-extensions = ">=0.4.3"
+playwright = {version = "*", optional = true, markers = "extra == \"testing\""}
+requests = ">=2"
+sanic = {version = ">=21", optional = true, markers = "extra == \"sanic\""}
+sanic-cors = {version = "*", optional = true, markers = "extra == \"sanic\""}
+starlette = {version = ">=0.13.6", optional = true, markers = "extra == \"starlette\""}
+tornado = {version = "*", optional = true, markers = "extra == \"tornado\""}
+typing-extensions = ">=3.10"
+uvicorn = {version = ">=0.19.0", extras = ["standard"], optional = true, markers = "extra == \"fastapi\" or extra == \"sanic\" or extra == \"starlette\""}
+
+[package.extras]
+all = ["reactpy[fastapi,flask,sanic,starlette,testing,tornado]"]
+fastapi = ["fastapi (>=0.63.0)", "uvicorn[standard] (>=0.19.0)"]
+flask = ["flask", "flask-cors", "flask-sock", "markupsafe (>=1.1.1,<2.1)"]
+sanic = ["sanic (>=21)", "sanic-cors", "uvicorn[standard] (>=0.19.0)"]
+starlette = ["starlette (>=0.13.6)", "uvicorn[standard] (>=0.19.0)"]
+testing = ["playwright"]
+tornado = ["tornado"]
+
+[package.source]
+type = "directory"
+url = "../src/py/reactpy"
+
+[[package]]
+name = "requests"
+version = "2.31.0"
+description = "Python HTTP for Humans."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"},
+ {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"},
+]
+
+[package.dependencies]
+certifi = ">=2017.4.17"
+charset-normalizer = ">=2,<4"
+idna = ">=2.5,<4"
+urllib3 = ">=1.21.1,<3"
+
+[package.extras]
+socks = ["PySocks (>=1.5.6,!=1.5.7)"]
+use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
+
+[[package]]
+name = "sanic"
+version = "23.3.0"
+description = "A web server and web framework that's written to go fast. Build fast. Run fast."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sanic-23.3.0-py3-none-any.whl", hash = "sha256:7cafbd63da9c6c6d8aeb8cb4304addf8a274352ab812014386c63e55f474fbee"},
+ {file = "sanic-23.3.0.tar.gz", hash = "sha256:b80ebc5c38c983cb45ae5ecc7a669a54c823ec1dff297fbd5f817b1e9e9e49af"},
+]
+
+[package.dependencies]
+aiofiles = ">=0.6.0"
+html5tagger = ">=1.2.1"
+httptools = ">=0.0.10"
+multidict = ">=5.0,<7.0"
+sanic-routing = ">=22.8.0"
+tracerite = ">=1.0.0"
+ujson = {version = ">=1.35", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""}
+uvloop = {version = ">=0.15.0", markers = "sys_platform != \"win32\" and implementation_name == \"cpython\""}
+websockets = ">=10.0"
+
+[package.extras]
+all = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "cryptography", "docutils", "enum-tools[sphinx]", "flake8", "isort (>=5.0.0)", "m2r2", "mistune (<2.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"]
+dev = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "cryptography", "docutils", "flake8", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "towncrier", "tox", "types-ujson", "uvicorn (<0.15.0)"]
+docs = ["docutils", "enum-tools[sphinx]", "m2r2", "mistune (<2.0.0)", "pygments", "sphinx (>=2.1.2)", "sphinx-rtd-theme (>=0.4.3)"]
+ext = ["sanic-ext"]
+http3 = ["aioquic"]
+test = ["bandit", "beautifulsoup4", "black", "chardet (==3.*)", "coverage", "docutils", "flake8", "isort (>=5.0.0)", "mypy (>=0.901,<0.910)", "pygments", "pytest (==7.1.*)", "pytest-benchmark", "pytest-sanic", "sanic-testing (>=23.3.0)", "slotscheck (>=0.8.0,<1)", "types-ujson", "uvicorn (<0.15.0)"]
+
+[[package]]
+name = "sanic-cors"
+version = "2.2.0"
+description = "A Sanic extension adding a decorator for CORS support. Based on flask-cors by Cory Dolphin."
+optional = false
+python-versions = "*"
+files = [
+ {file = "Sanic-Cors-2.2.0.tar.gz", hash = "sha256:f8d7515da4c8b837871d422c66314c4b5704396a78894b59c50e26aa72a95873"},
+ {file = "Sanic_Cors-2.2.0-py2.py3-none-any.whl", hash = "sha256:c3b133ff1f0bb609a53db35f727f5c371dc4ebeb6be4cc2c37c19dd8b9301115"},
+]
+
+[package.dependencies]
+packaging = ">=21.3"
+sanic = ">=21.9.3"
+
+[[package]]
+name = "sanic-routing"
+version = "22.8.0"
+description = "Core routing component for Sanic"
+optional = false
+python-versions = "*"
+files = [
+ {file = "sanic-routing-22.8.0.tar.gz", hash = "sha256:305729b4e0bf01f074044a2a315ff401fa7eeffb009eec1d2c81d35e1038ddfc"},
+ {file = "sanic_routing-22.8.0-py3-none-any.whl", hash = "sha256:9a928ed9e19a36bc019223be90a5da0ab88cdd76b101e032510b6a7073c017e9"},
+]
+
+[[package]]
+name = "simple-websocket"
+version = "0.10.0"
+description = "Simple WebSocket server and client for Python"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "simple-websocket-0.10.0.tar.gz", hash = "sha256:82c0b0b1006d5490f09ff66392394d90dd758285635edad241e093e9a8abd3eb"},
+ {file = "simple_websocket-0.10.0-py3-none-any.whl", hash = "sha256:fc1bc56c393a187e7268f8ab99da1a8e8da9b5dfb7769a2f3b8dada00067745b"},
+]
+
+[package.dependencies]
+wsproto = "*"
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.0"
+description = "Sniff out which async library your code is running under"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
+ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
+]
+
+[[package]]
+name = "snowballstemmer"
+version = "2.2.0"
+description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms."
+optional = false
+python-versions = "*"
+files = [
+ {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"},
+ {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"},
+]
+
+[[package]]
+name = "soupsieve"
+version = "2.4.1"
+description = "A modern CSS selector implementation for Beautiful Soup."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"},
+ {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"},
+]
+
+[[package]]
+name = "sphinx"
+version = "4.5.0"
+description = "Python documentation generator"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "Sphinx-4.5.0-py3-none-any.whl", hash = "sha256:ebf612653238bcc8f4359627a9b7ce44ede6fdd75d9d30f68255c7383d3a6226"},
+ {file = "Sphinx-4.5.0.tar.gz", hash = "sha256:7bf8ca9637a4ee15af412d1a1d9689fec70523a68ca9bb9127c2f3eeb344e2e6"},
+]
+
+[package.dependencies]
+alabaster = ">=0.7,<0.8"
+babel = ">=1.3"
+colorama = {version = ">=0.3.5", markers = "sys_platform == \"win32\""}
+docutils = ">=0.14,<0.18"
+imagesize = "*"
+importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
+Jinja2 = ">=2.3"
+packaging = "*"
+Pygments = ">=2.0"
+requests = ">=2.5.0"
+snowballstemmer = ">=1.1"
+sphinxcontrib-applehelp = "*"
+sphinxcontrib-devhelp = "*"
+sphinxcontrib-htmlhelp = ">=2.0.0"
+sphinxcontrib-jsmath = "*"
+sphinxcontrib-qthelp = "*"
+sphinxcontrib-serializinghtml = ">=1.1.5"
+
+[package.extras]
+docs = ["sphinxcontrib-websupport"]
+lint = ["docutils-stubs", "flake8 (>=3.5.0)", "isort", "mypy (>=0.931)", "types-requests", "types-typed-ast"]
+test = ["cython", "html5lib", "pytest", "pytest-cov", "typed-ast"]
+
+[[package]]
+name = "sphinx-autobuild"
+version = "2021.3.14"
+description = "Rebuild Sphinx documentation on changes, with live-reload in the browser."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"},
+ {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"},
+]
+
+[package.dependencies]
+colorama = "*"
+livereload = "*"
+sphinx = "*"
+
+[package.extras]
+test = ["pytest", "pytest-cov"]
+
+[[package]]
+name = "sphinx-autodoc-typehints"
+version = "1.19.1"
+description = "Type hints (PEP 484) support for the Sphinx autodoc extension"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sphinx_autodoc_typehints-1.19.1-py3-none-any.whl", hash = "sha256:9be46aeeb1b315eb5df1f3a7cb262149895d16c7d7dcd77b92513c3c3a1e85e6"},
+ {file = "sphinx_autodoc_typehints-1.19.1.tar.gz", hash = "sha256:6c841db55e0e9be0483ff3962a2152b60e79306f4288d8c4e7e86ac84486a5ea"},
+]
+
+[package.dependencies]
+Sphinx = ">=4.5"
+
+[package.extras]
+testing = ["covdefaults (>=2.2)", "coverage (>=6.3)", "diff-cover (>=6.4)", "nptyping (>=2.1.2)", "pytest (>=7.1)", "pytest-cov (>=3)", "sphobjinv (>=2)", "typing-extensions (>=4.1)"]
+type-comments = ["typed-ast (>=1.5.2)"]
+
+[[package]]
+name = "sphinx-copybutton"
+version = "0.5.2"
+description = "Add a copy button to each of your code cells."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sphinx-copybutton-0.5.2.tar.gz", hash = "sha256:4cf17c82fb9646d1bc9ca92ac280813a3b605d8c421225fd9913154103ee1fbd"},
+ {file = "sphinx_copybutton-0.5.2-py3-none-any.whl", hash = "sha256:fb543fd386d917746c9a2c50360c7905b605726b9355cd26e9974857afeae06e"},
+]
+
+[package.dependencies]
+sphinx = ">=1.8"
+
+[package.extras]
+code-style = ["pre-commit (==2.12.1)"]
+rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"]
+
+[[package]]
+name = "sphinx-design"
+version = "0.4.1"
+description = "A sphinx extension for designing beautiful, view size responsive web components."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sphinx_design-0.4.1-py3-none-any.whl", hash = "sha256:23bf5705eb31296d4451f68b0222a698a8a84396ffe8378dfd9319ba7ab8efd9"},
+ {file = "sphinx_design-0.4.1.tar.gz", hash = "sha256:5b6418ba4a2dc3d83592ea0ff61a52a891fe72195a4c3a18b2fa1c7668ce4708"},
+]
+
+[package.dependencies]
+sphinx = ">=4,<7"
+
+[package.extras]
+code-style = ["pre-commit (>=2.12,<3.0)"]
+rtd = ["myst-parser (>=0.18.0,<2)"]
+testing = ["myst-parser (>=0.18.0,<2)", "pytest (>=7.1,<8.0)", "pytest-cov", "pytest-regressions"]
+theme-furo = ["furo (>=2022.06.04,<2022.07)"]
+theme-pydata = ["pydata-sphinx-theme (>=0.9.0,<0.10.0)"]
+theme-rtd = ["sphinx-rtd-theme (>=1.0,<2.0)"]
+theme-sbt = ["sphinx-book-theme (>=0.3.0,<0.4.0)"]
+
+[[package]]
+name = "sphinx-reredirects"
+version = "0.1.2"
+description = "Handles redirects for moved pages in Sphinx documentation projects"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "sphinx_reredirects-0.1.2-py3-none-any.whl", hash = "sha256:3a22161771aadd448bb608a4fe7277252182a337af53c18372b7104531d71489"},
+ {file = "sphinx_reredirects-0.1.2.tar.gz", hash = "sha256:a0e7213304759b01edc22f032f1715a1c61176fc8f167164e7a52b9feec9ac64"},
+]
+
+[package.dependencies]
+sphinx = "*"
+
+[[package]]
+name = "sphinx-resolve-py-references"
+version = "0.1.0"
+description = "Better python object resolution in Sphinx"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sphinx_resolve_py_references-0.1.0-py2.py3-none-any.whl", hash = "sha256:ccf44a6b62d75c3a568285f4e1815734088c1a7cab7bbb7935bb22fbf0d78bc2"},
+ {file = "sphinx_resolve_py_references-0.1.0.tar.gz", hash = "sha256:0f87c06b29ec128964aee2e40d170d1d3c0e5f4955b2618a89ca724f42385372"},
+]
+
+[package.dependencies]
+sphinx = "*"
+
+[[package]]
+name = "sphinxcontrib-applehelp"
+version = "1.0.4"
+description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"},
+ {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-devhelp"
+version = "1.0.2"
+description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"},
+ {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-htmlhelp"
+version = "2.0.1"
+description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"},
+ {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["html5lib", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-jsmath"
+version = "1.0.1"
+description = "A sphinx extension which renders display math in HTML via JavaScript"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"},
+ {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"},
+]
+
+[package.extras]
+test = ["flake8", "mypy", "pytest"]
+
+[[package]]
+name = "sphinxcontrib-qthelp"
+version = "1.0.3"
+description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"},
+ {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxcontrib-serializinghtml"
+version = "1.1.5"
+description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)."
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"},
+ {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"},
+]
+
+[package.extras]
+lint = ["docutils-stubs", "flake8", "mypy"]
+test = ["pytest"]
+
+[[package]]
+name = "sphinxext-opengraph"
+version = "0.8.2"
+description = "Sphinx Extension to enable OGP support"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sphinxext-opengraph-0.8.2.tar.gz", hash = "sha256:45a693b6704052c426576f0a1f630649c55b4188bc49eb63e9587e24a923db39"},
+ {file = "sphinxext_opengraph-0.8.2-py3-none-any.whl", hash = "sha256:6a05bdfe5176d9dd0a1d58a504f17118362ab976631213cd36fb44c4c40544c9"},
+]
+
+[package.dependencies]
+matplotlib = "*"
+sphinx = ">=4.0"
+
+[[package]]
+name = "starlette"
+version = "0.27.0"
+description = "The little ASGI library that shines."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "starlette-0.27.0-py3-none-any.whl", hash = "sha256:918416370e846586541235ccd38a474c08b80443ed31c578a418e2209b3eef91"},
+ {file = "starlette-0.27.0.tar.gz", hash = "sha256:6a6b0d042acb8d469a01eba54e9cda6cbd24ac602c4cd016723117d6a7e73b75"},
+]
+
+[package.dependencies]
+anyio = ">=3.4.0,<5"
+typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""}
+
+[package.extras]
+full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"]
+
+[[package]]
+name = "tornado"
+version = "6.3.2"
+description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed."
+optional = false
+python-versions = ">= 3.8"
+files = [
+ {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:c367ab6c0393d71171123ca5515c61ff62fe09024fa6bf299cd1339dc9456829"},
+ {file = "tornado-6.3.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:b46a6ab20f5c7c1cb949c72c1994a4585d2eaa0be4853f50a03b5031e964fc7c"},
+ {file = "tornado-6.3.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c2de14066c4a38b4ecbbcd55c5cc4b5340eb04f1c5e81da7451ef555859c833f"},
+ {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05615096845cf50a895026f749195bf0b10b8909f9be672f50b0fe69cba368e4"},
+ {file = "tornado-6.3.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b17b1cf5f8354efa3d37c6e28fdfd9c1c1e5122f2cb56dac121ac61baa47cbe"},
+ {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:29e71c847a35f6e10ca3b5c2990a52ce38b233019d8e858b755ea6ce4dcdd19d"},
+ {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_i686.whl", hash = "sha256:834ae7540ad3a83199a8da8f9f2d383e3c3d5130a328889e4cc991acc81e87a0"},
+ {file = "tornado-6.3.2-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6a0848f1aea0d196a7c4f6772197cbe2abc4266f836b0aac76947872cd29b411"},
+ {file = "tornado-6.3.2-cp38-abi3-win32.whl", hash = "sha256:7efcbcc30b7c654eb6a8c9c9da787a851c18f8ccd4a5a3a95b05c7accfa068d2"},
+ {file = "tornado-6.3.2-cp38-abi3-win_amd64.whl", hash = "sha256:0c325e66c8123c606eea33084976c832aa4e766b7dff8aedd7587ea44a604cdf"},
+ {file = "tornado-6.3.2.tar.gz", hash = "sha256:4b927c4f19b71e627b13f3db2324e4ae660527143f9e1f2e2fb404f3a187e2ba"},
+]
+
+[[package]]
+name = "tracerite"
+version = "1.1.0"
+description = "Human-readable HTML tracebacks for Python exceptions"
+optional = false
+python-versions = "*"
+files = [
+ {file = "tracerite-1.1.0-py3-none-any.whl", hash = "sha256:4cccac04db05eeeabda45e72b57199e147fa2f73cf64d89cfd625df321bd2ab6"},
+ {file = "tracerite-1.1.0.tar.gz", hash = "sha256:041dab8fd4bb405f73506293ac7438a2d311e5f9044378ba7d9a6540392f9e4b"},
+]
+
+[package.dependencies]
+html5tagger = ">=1.2.1"
+
+[[package]]
+name = "typing-extensions"
+version = "4.6.3"
+description = "Backported and Experimental Type Hints for Python 3.7+"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "typing_extensions-4.6.3-py3-none-any.whl", hash = "sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26"},
+ {file = "typing_extensions-4.6.3.tar.gz", hash = "sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5"},
+]
+
+[[package]]
+name = "ujson"
+version = "5.7.0"
+description = "Ultra fast JSON encoder and decoder for Python"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "ujson-5.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5eba5e69e4361ac3a311cf44fa71bc619361b6e0626768a494771aacd1c2f09b"},
+ {file = "ujson-5.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aae4d9e1b4c7b61780f0a006c897a4a1904f862fdab1abb3ea8f45bd11aa58f3"},
+ {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2e43ccdba1cb5c6d3448eadf6fc0dae7be6c77e357a3abc968d1b44e265866d"},
+ {file = "ujson-5.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54384ce4920a6d35fa9ea8e580bc6d359e3eb961fa7e43f46c78e3ed162d56ff"},
+ {file = "ujson-5.7.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24ad1aa7fc4e4caa41d3d343512ce68e41411fb92adf7f434a4d4b3749dc8f58"},
+ {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:afff311e9f065a8f03c3753db7011bae7beb73a66189c7ea5fcb0456b7041ea4"},
+ {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e80f0d03e7e8646fc3d79ed2d875cebd4c83846e129737fdc4c2532dbd43d9e"},
+ {file = "ujson-5.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:137831d8a0db302fb6828ee21c67ad63ac537bddc4376e1aab1c8573756ee21c"},
+ {file = "ujson-5.7.0-cp310-cp310-win32.whl", hash = "sha256:7df3fd35ebc14dafeea031038a99232b32f53fa4c3ecddb8bed132a43eefb8ad"},
+ {file = "ujson-5.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:af4639f684f425177d09ae409c07602c4096a6287027469157bfb6f83e01448b"},
+ {file = "ujson-5.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9b0f2680ce8a70f77f5d70aaf3f013d53e6af6d7058727a35d8ceb4a71cdd4e9"},
+ {file = "ujson-5.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a19fd8e7d8cc58a169bea99fed5666023adf707a536d8f7b0a3c51dd498abf"},
+ {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6abb8e6d8f1ae72f0ed18287245f5b6d40094e2656d1eab6d99d666361514074"},
+ {file = "ujson-5.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8cd622c069368d5074bd93817b31bdb02f8d818e57c29e206f10a1f9c6337dd"},
+ {file = "ujson-5.7.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14f9082669f90e18e64792b3fd0bf19f2b15e7fe467534a35ea4b53f3bf4b755"},
+ {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d7ff6ebb43bc81b057724e89550b13c9a30eda0f29c2f506f8b009895438f5a6"},
+ {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f7f241488879d91a136b299e0c4ce091996c684a53775e63bb442d1a8e9ae22a"},
+ {file = "ujson-5.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5593263a7fcfb934107444bcfba9dde8145b282de0ee9f61e285e59a916dda0f"},
+ {file = "ujson-5.7.0-cp311-cp311-win32.whl", hash = "sha256:26c2b32b489c393106e9cb68d0a02e1a7b9d05a07429d875c46b94ee8405bdb7"},
+ {file = "ujson-5.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:ed24406454bb5a31df18f0a423ae14beb27b28cdfa34f6268e7ebddf23da807e"},
+ {file = "ujson-5.7.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:18679484e3bf9926342b1c43a3bd640f93a9eeeba19ef3d21993af7b0c44785d"},
+ {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee295761e1c6c30400641f0a20d381633d7622633cdf83a194f3c876a0e4b7e"},
+ {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b738282e12a05f400b291966630a98d622da0938caa4bc93cf65adb5f4281c60"},
+ {file = "ujson-5.7.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00343501dbaa5172e78ef0e37f9ebd08040110e11c12420ff7c1f9f0332d939e"},
+ {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c0d1f7c3908357ee100aa64c4d1cf91edf99c40ac0069422a4fd5fd23b263263"},
+ {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a5d2f44331cf04689eafac7a6596c71d6657967c07ac700b0ae1c921178645da"},
+ {file = "ujson-5.7.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:16b2254a77b310f118717715259a196662baa6b1f63b1a642d12ab1ff998c3d7"},
+ {file = "ujson-5.7.0-cp37-cp37m-win32.whl", hash = "sha256:6faf46fa100b2b89e4db47206cf8a1ffb41542cdd34dde615b2fc2288954f194"},
+ {file = "ujson-5.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ff0004c3f5a9a6574689a553d1b7819d1a496b4f005a7451f339dc2d9f4cf98c"},
+ {file = "ujson-5.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:75204a1dd7ec6158c8db85a2f14a68d2143503f4bafb9a00b63fe09d35762a5e"},
+ {file = "ujson-5.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7312731c7826e6c99cdd3ac503cd9acd300598e7a80bcf41f604fee5f49f566c"},
+ {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b9dc5a90e2149643df7f23634fe202fed5ebc787a2a1be95cf23632b4d90651"},
+ {file = "ujson-5.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6a6961fc48821d84b1198a09516e396d56551e910d489692126e90bf4887d29"},
+ {file = "ujson-5.7.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b01a9af52a0d5c46b2c68e3f258fdef2eacaa0ce6ae3e9eb97983f5b1166edb6"},
+ {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b7316d3edeba8a403686cdcad4af737b8415493101e7462a70ff73dd0609eafc"},
+ {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4ee997799a23227e2319a3f8817ce0b058923dbd31904761b788dc8f53bd3e30"},
+ {file = "ujson-5.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:dda9aa4c33435147262cd2ea87c6b7a1ca83ba9b3933ff7df34e69fee9fced0c"},
+ {file = "ujson-5.7.0-cp38-cp38-win32.whl", hash = "sha256:bea8d30e362180aafecabbdcbe0e1f0b32c9fa9e39c38e4af037b9d3ca36f50c"},
+ {file = "ujson-5.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:c96e3b872bf883090ddf32cc41957edf819c5336ab0007d0cf3854e61841726d"},
+ {file = "ujson-5.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6411aea4c94a8e93c2baac096fbf697af35ba2b2ed410b8b360b3c0957a952d3"},
+ {file = "ujson-5.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3d3b3499c55911f70d4e074c626acdb79a56f54262c3c83325ffb210fb03e44d"},
+ {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:341f891d45dd3814d31764626c55d7ab3fd21af61fbc99d070e9c10c1190680b"},
+ {file = "ujson-5.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f242eec917bafdc3f73a1021617db85f9958df80f267db69c76d766058f7b19"},
+ {file = "ujson-5.7.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3af9f9f22a67a8c9466a32115d9073c72a33ae627b11de6f592df0ee09b98b6"},
+ {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a3d794afbf134df3056a813e5c8a935208cddeae975bd4bc0ef7e89c52f0ce0"},
+ {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:800bf998e78dae655008dd10b22ca8dc93bdcfcc82f620d754a411592da4bbf2"},
+ {file = "ujson-5.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b5ac3d5c5825e30b438ea92845380e812a476d6c2a1872b76026f2e9d8060fc2"},
+ {file = "ujson-5.7.0-cp39-cp39-win32.whl", hash = "sha256:cd90027e6d93e8982f7d0d23acf88c896d18deff1903dd96140613389b25c0dd"},
+ {file = "ujson-5.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:523ee146cdb2122bbd827f4dcc2a8e66607b3f665186bce9e4f78c9710b6d8ab"},
+ {file = "ujson-5.7.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e87cec407ec004cf1b04c0ed7219a68c12860123dfb8902ef880d3d87a71c172"},
+ {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bab10165db6a7994e67001733f7f2caf3400b3e11538409d8756bc9b1c64f7e8"},
+ {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b522be14a28e6ac1cf818599aeff1004a28b42df4ed4d7bc819887b9dac915fc"},
+ {file = "ujson-5.7.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7592f40175c723c032cdbe9fe5165b3b5903604f774ab0849363386e99e1f253"},
+ {file = "ujson-5.7.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ed22f9665327a981f288a4f758a432824dc0314e4195a0eaeb0da56a477da94d"},
+ {file = "ujson-5.7.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:adf445a49d9a97a5a4c9bb1d652a1528de09dd1c48b29f79f3d66cea9f826bf6"},
+ {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64772a53f3c4b6122ed930ae145184ebaed38534c60f3d859d8c3f00911eb122"},
+ {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35209cb2c13fcb9d76d249286105b4897b75a5e7f0efb0c0f4b90f222ce48910"},
+ {file = "ujson-5.7.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:90712dfc775b2c7a07d4d8e059dd58636bd6ff1776d79857776152e693bddea6"},
+ {file = "ujson-5.7.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0e4e8981c6e7e9e637e637ad8ffe948a09e5434bc5f52ecbb82b4b4cfc092bfb"},
+ {file = "ujson-5.7.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:581c945b811a3d67c27566539bfcb9705ea09cb27c4be0002f7a553c8886b817"},
+ {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d36a807a24c7d44f71686685ae6fbc8793d784bca1adf4c89f5f780b835b6243"},
+ {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b4257307e3662aa65e2644a277ca68783c5d51190ed9c49efebdd3cbfd5fa44"},
+ {file = "ujson-5.7.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea7423d8a2f9e160c5e011119741682414c5b8dce4ae56590a966316a07a4618"},
+ {file = "ujson-5.7.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c592eb91a5968058a561d358d0fef59099ed152cfb3e1cd14eee51a7a93879e"},
+ {file = "ujson-5.7.0.tar.gz", hash = "sha256:e788e5d5dcae8f6118ac9b45d0b891a0d55f7ac480eddcb7f07263f2bcf37b23"},
+]
+
+[[package]]
+name = "urllib3"
+version = "2.0.2"
+description = "HTTP library with thread-safe connection pooling, file post, and more."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "urllib3-2.0.2-py3-none-any.whl", hash = "sha256:d055c2f9d38dc53c808f6fdc8eab7360b6fdbbde02340ed25cfbcd817c62469e"},
+ {file = "urllib3-2.0.2.tar.gz", hash = "sha256:61717a1095d7e155cdb737ac7bb2f4324a858a1e2e6466f6d03ff630ca68d3cc"},
+]
+
+[package.extras]
+brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"]
+secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"]
+socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"]
+zstd = ["zstandard (>=0.18.0)"]
+
+[[package]]
+name = "uvicorn"
+version = "0.22.0"
+description = "The lightning-fast ASGI server."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "uvicorn-0.22.0-py3-none-any.whl", hash = "sha256:e9434d3bbf05f310e762147f769c9f21235ee118ba2d2bf1155a7196448bd996"},
+ {file = "uvicorn-0.22.0.tar.gz", hash = "sha256:79277ae03db57ce7d9aa0567830bbb51d7a612f54d6e1e3e92da3ef24c2c8ed8"},
+]
+
+[package.dependencies]
+click = ">=7.0"
+colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""}
+h11 = ">=0.8"
+httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""}
+python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
+pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""}
+uvloop = {version = ">=0.14.0,<0.15.0 || >0.15.0,<0.15.1 || >0.15.1", optional = true, markers = "(sys_platform != \"win32\" and sys_platform != \"cygwin\") and platform_python_implementation != \"PyPy\" and extra == \"standard\""}
+watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standard\""}
+websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""}
+
+[package.extras]
+standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"]
+
+[[package]]
+name = "uvloop"
+version = "0.17.0"
+description = "Fast implementation of asyncio event loop on top of libuv"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ce9f61938d7155f79d3cb2ffa663147d4a76d16e08f65e2c66b77bd41b356718"},
+ {file = "uvloop-0.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:68532f4349fd3900b839f588972b3392ee56042e440dd5873dfbbcd2cc67617c"},
+ {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0949caf774b9fcefc7c5756bacbbbd3fc4c05a6b7eebc7c7ad6f825b23998d6d"},
+ {file = "uvloop-0.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3d00b70ce95adce264462c930fbaecb29718ba6563db354608f37e49e09024"},
+ {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:a5abddb3558d3f0a78949c750644a67be31e47936042d4f6c888dd6f3c95f4aa"},
+ {file = "uvloop-0.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8efcadc5a0003d3a6e887ccc1fb44dec25594f117a94e3127954c05cf144d811"},
+ {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3378eb62c63bf336ae2070599e49089005771cc651c8769aaad72d1bd9385a7c"},
+ {file = "uvloop-0.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6aafa5a78b9e62493539456f8b646f85abc7093dd997f4976bb105537cf2635e"},
+ {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c686a47d57ca910a2572fddfe9912819880b8765e2f01dc0dd12a9bf8573e539"},
+ {file = "uvloop-0.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:864e1197139d651a76c81757db5eb199db8866e13acb0dfe96e6fc5d1cf45fc4"},
+ {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:2a6149e1defac0faf505406259561bc14b034cdf1d4711a3ddcdfbaa8d825a05"},
+ {file = "uvloop-0.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6708f30db9117f115eadc4f125c2a10c1a50d711461699a0cbfaa45b9a78e376"},
+ {file = "uvloop-0.17.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:23609ca361a7fc587031429fa25ad2ed7242941adec948f9d10c045bfecab06b"},
+ {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2deae0b0fb00a6af41fe60a675cec079615b01d68beb4cc7b722424406b126a8"},
+ {file = "uvloop-0.17.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45cea33b208971e87a31c17622e4b440cac231766ec11e5d22c76fab3bf9df62"},
+ {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:9b09e0f0ac29eee0451d71798878eae5a4e6a91aa275e114037b27f7db72702d"},
+ {file = "uvloop-0.17.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:dbbaf9da2ee98ee2531e0c780455f2841e4675ff580ecf93fe5c48fe733b5667"},
+ {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a4aee22ece20958888eedbad20e4dbb03c37533e010fb824161b4f05e641f738"},
+ {file = "uvloop-0.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:307958f9fc5c8bb01fad752d1345168c0abc5d62c1b72a4a8c6c06f042b45b20"},
+ {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ebeeec6a6641d0adb2ea71dcfb76017602ee2bfd8213e3fcc18d8f699c5104f"},
+ {file = "uvloop-0.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1436c8673c1563422213ac6907789ecb2b070f5939b9cbff9ef7113f2b531595"},
+ {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8887d675a64cfc59f4ecd34382e5b4f0ef4ae1da37ed665adba0c2badf0d6578"},
+ {file = "uvloop-0.17.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:3db8de10ed684995a7f34a001f15b374c230f7655ae840964d51496e2f8a8474"},
+ {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7d37dccc7ae63e61f7b96ee2e19c40f153ba6ce730d8ba4d3b4e9738c1dccc1b"},
+ {file = "uvloop-0.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cbbe908fda687e39afd6ea2a2f14c2c3e43f2ca88e3a11964b297822358d0e6c"},
+ {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d97672dc709fa4447ab83276f344a165075fd9f366a97b712bdd3fee05efae8"},
+ {file = "uvloop-0.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f1e507c9ee39c61bfddd79714e4f85900656db1aec4d40c6de55648e85c2799c"},
+ {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c092a2c1e736086d59ac8e41f9c98f26bbf9b9222a76f21af9dfe949b99b2eb9"},
+ {file = "uvloop-0.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:30babd84706115626ea78ea5dbc7dd8d0d01a2e9f9b306d24ca4ed5796c66ded"},
+ {file = "uvloop-0.17.0.tar.gz", hash = "sha256:0ddf6baf9cf11a1a22c71487f39f15b2cf78eb5bde7e5b45fbb99e8a9d91b9e1"},
+]
+
+[package.extras]
+dev = ["Cython (>=0.29.32,<0.30.0)", "Sphinx (>=4.1.2,<4.2.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)", "pytest (>=3.6.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
+docs = ["Sphinx (>=4.1.2,<4.2.0)", "sphinx-rtd-theme (>=0.5.2,<0.6.0)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
+test = ["Cython (>=0.29.32,<0.30.0)", "aiohttp", "flake8 (>=3.9.2,<3.10.0)", "mypy (>=0.800)", "psutil", "pyOpenSSL (>=22.0.0,<22.1.0)", "pycodestyle (>=2.7.0,<2.8.0)"]
+
+[[package]]
+name = "watchfiles"
+version = "0.19.0"
+description = "Simple, modern and high performance file watching and code reload in python."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "watchfiles-0.19.0-cp37-abi3-macosx_10_7_x86_64.whl", hash = "sha256:91633e64712df3051ca454ca7d1b976baf842d7a3640b87622b323c55f3345e7"},
+ {file = "watchfiles-0.19.0-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:b6577b8c6c8701ba8642ea9335a129836347894b666dd1ec2226830e263909d3"},
+ {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:18b28f6ad871b82df9542ff958d0c86bb0d8310bb09eb8e87d97318a3b5273af"},
+ {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fac19dc9cbc34052394dbe81e149411a62e71999c0a19e1e09ce537867f95ae0"},
+ {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09ea3397aecbc81c19ed7f025e051a7387feefdb789cf768ff994c1228182fda"},
+ {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0376deac92377817e4fb8f347bf559b7d44ff556d9bc6f6208dd3f79f104aaf"},
+ {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c75eff897786ee262c9f17a48886f4e98e6cfd335e011c591c305e5d083c056"},
+ {file = "watchfiles-0.19.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb5d45c4143c1dd60f98a16187fd123eda7248f84ef22244818c18d531a249d1"},
+ {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:79c533ff593db861ae23436541f481ec896ee3da4e5db8962429b441bbaae16e"},
+ {file = "watchfiles-0.19.0-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:3d7d267d27aceeeaa3de0dd161a0d64f0a282264d592e335fff7958cc0cbae7c"},
+ {file = "watchfiles-0.19.0-cp37-abi3-win32.whl", hash = "sha256:176a9a7641ec2c97b24455135d58012a5be5c6217fc4d5fef0b2b9f75dbf5154"},
+ {file = "watchfiles-0.19.0-cp37-abi3-win_amd64.whl", hash = "sha256:945be0baa3e2440151eb3718fd8846751e8b51d8de7b884c90b17d271d34cae8"},
+ {file = "watchfiles-0.19.0-cp37-abi3-win_arm64.whl", hash = "sha256:0089c6dc24d436b373c3c57657bf4f9a453b13767150d17284fc6162b2791911"},
+ {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_10_7_x86_64.whl", hash = "sha256:cae3dde0b4b2078f31527acff6f486e23abed307ba4d3932466ba7cdd5ecec79"},
+ {file = "watchfiles-0.19.0-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:7f3920b1285a7d3ce898e303d84791b7bf40d57b7695ad549dc04e6a44c9f120"},
+ {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9afd0d69429172c796164fd7fe8e821ade9be983f51c659a38da3faaaaac44dc"},
+ {file = "watchfiles-0.19.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68dce92b29575dda0f8d30c11742a8e2b9b8ec768ae414b54f7453f27bdf9545"},
+ {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_10_7_x86_64.whl", hash = "sha256:5569fc7f967429d4bc87e355cdfdcee6aabe4b620801e2cf5805ea245c06097c"},
+ {file = "watchfiles-0.19.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5471582658ea56fca122c0f0d0116a36807c63fefd6fdc92c71ca9a4491b6b48"},
+ {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b538014a87f94d92f98f34d3e6d2635478e6be6423a9ea53e4dd96210065e193"},
+ {file = "watchfiles-0.19.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20b44221764955b1e703f012c74015306fb7e79a00c15370785f309b1ed9aa8d"},
+ {file = "watchfiles-0.19.0.tar.gz", hash = "sha256:d9b073073e048081e502b6c6b0b88714c026a1a4c890569238d04aca5f9ca74b"},
+]
+
+[package.dependencies]
+anyio = ">=3.0.0"
+
+[[package]]
+name = "websockets"
+version = "11.0.3"
+description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac"},
+ {file = "websockets-11.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d"},
+ {file = "websockets-11.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f"},
+ {file = "websockets-11.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564"},
+ {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11"},
+ {file = "websockets-11.0.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca"},
+ {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54"},
+ {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4"},
+ {file = "websockets-11.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526"},
+ {file = "websockets-11.0.3-cp310-cp310-win32.whl", hash = "sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69"},
+ {file = "websockets-11.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f"},
+ {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb"},
+ {file = "websockets-11.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288"},
+ {file = "websockets-11.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d"},
+ {file = "websockets-11.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3"},
+ {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b"},
+ {file = "websockets-11.0.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6"},
+ {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97"},
+ {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf"},
+ {file = "websockets-11.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd"},
+ {file = "websockets-11.0.3-cp311-cp311-win32.whl", hash = "sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c"},
+ {file = "websockets-11.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8"},
+ {file = "websockets-11.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152"},
+ {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f"},
+ {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b"},
+ {file = "websockets-11.0.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb"},
+ {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007"},
+ {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0"},
+ {file = "websockets-11.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af"},
+ {file = "websockets-11.0.3-cp37-cp37m-win32.whl", hash = "sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f"},
+ {file = "websockets-11.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de"},
+ {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0"},
+ {file = "websockets-11.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae"},
+ {file = "websockets-11.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99"},
+ {file = "websockets-11.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa"},
+ {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86"},
+ {file = "websockets-11.0.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c"},
+ {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0"},
+ {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e"},
+ {file = "websockets-11.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788"},
+ {file = "websockets-11.0.3-cp38-cp38-win32.whl", hash = "sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74"},
+ {file = "websockets-11.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f"},
+ {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8"},
+ {file = "websockets-11.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd"},
+ {file = "websockets-11.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016"},
+ {file = "websockets-11.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61"},
+ {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b"},
+ {file = "websockets-11.0.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd"},
+ {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7"},
+ {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1"},
+ {file = "websockets-11.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311"},
+ {file = "websockets-11.0.3-cp39-cp39-win32.whl", hash = "sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128"},
+ {file = "websockets-11.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e"},
+ {file = "websockets-11.0.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf"},
+ {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5"},
+ {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998"},
+ {file = "websockets-11.0.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b"},
+ {file = "websockets-11.0.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb"},
+ {file = "websockets-11.0.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20"},
+ {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931"},
+ {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9"},
+ {file = "websockets-11.0.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280"},
+ {file = "websockets-11.0.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b"},
+ {file = "websockets-11.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82"},
+ {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c"},
+ {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d"},
+ {file = "websockets-11.0.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4"},
+ {file = "websockets-11.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602"},
+ {file = "websockets-11.0.3-py3-none-any.whl", hash = "sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6"},
+ {file = "websockets-11.0.3.tar.gz", hash = "sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016"},
+]
+
+[[package]]
+name = "werkzeug"
+version = "2.1.2"
+description = "The comprehensive WSGI web application library."
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "Werkzeug-2.1.2-py3-none-any.whl", hash = "sha256:72a4b735692dd3135217911cbeaa1be5fa3f62bffb8745c5215420a03dc55255"},
+ {file = "Werkzeug-2.1.2.tar.gz", hash = "sha256:1ce08e8093ed67d638d63879fd1ba3735817f7a80de3674d293f5984f25fb6e6"},
+]
+
+[package.extras]
+watchdog = ["watchdog"]
+
+[[package]]
+name = "wsproto"
+version = "1.2.0"
+description = "WebSockets state-machine based protocol implementation"
+optional = false
+python-versions = ">=3.7.0"
+files = [
+ {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"},
+ {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"},
+]
+
+[package.dependencies]
+h11 = ">=0.9.0,<1"
+
+[[package]]
+name = "zipp"
+version = "3.15.0"
+description = "Backport of pathlib-compatible object wrapper for zip files"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"},
+ {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
+testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.9"
+content-hash = "629118cfac10f1dab4c39c6ccd50bd69ca68a7fc05dd2baf1d020082d6b19e4e"
diff --git a/docs/pyproject.toml b/docs/pyproject.toml
new file mode 100644
index 000000000..f47b0e944
--- /dev/null
+++ b/docs/pyproject.toml
@@ -0,0 +1,23 @@
+[tool.poetry]
+name = "docs_app"
+version = "0.0.0"
+description = "docs"
+authors = ["rmorshea "]
+readme = "README.md"
+
+[tool.poetry.dependencies]
+python = "^3.9"
+reactpy = { path = "../src/py/reactpy", extras = ["starlette", "sanic", "fastapi", "flask", "tornado", "testing"], develop = false }
+furo = "2022.04.07"
+sphinx = "*"
+sphinx-autodoc-typehints = "*"
+sphinx-copybutton = "*"
+sphinx-autobuild = "*"
+sphinx-reredirects = "*"
+sphinx-design = "*"
+sphinx-resolve-py-references = "*"
+sphinxext-opengraph = "*"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
diff --git a/docs/source/_custom_js/README.md b/docs/source/_custom_js/README.md
index 8c77d450b..4d5d75dc2 100644
--- a/docs/source/_custom_js/README.md
+++ b/docs/source/_custom_js/README.md
@@ -1,4 +1,4 @@
-# Custom Javascript for IDOM's Docs
+# Custom Javascript for ReactPy's Docs
Build the javascript with
diff --git a/docs/source/_custom_js/package-lock.json b/docs/source/_custom_js/package-lock.json
index 49364d871..98cbb7014 100644
--- a/docs/source/_custom_js/package-lock.json
+++ b/docs/source/_custom_js/package-lock.json
@@ -1,14 +1,14 @@
{
- "name": "idom-docs-example-loader",
+ "name": "reactpy-docs-example-loader",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
- "name": "idom-docs-example-loader",
+ "name": "reactpy-docs-example-loader",
"version": "1.0.0",
"dependencies": {
- "idom-client-react": "file:../../../src/client/packages/idom-client-react"
+ "@reactpy/client": "file:../../../src/js/packages/@reactpy/client"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^21.0.1",
@@ -18,28 +18,74 @@
"rollup": "^2.35.1"
}
},
- "../../../src/client/packages/idom-client-react": {
- "version": "0.34.0",
+ "../../../src/client/packages/@reactpy/client": {
+ "version": "0.3.1",
+ "integrity": "sha512-pIK5eNwFSHKXg7ClpASWFVKyZDYxz59MSFpVaX/OqJFkrJaAxBuhKGXNTMXmuyWOL5Iyvb/ErwwDRxQRzMNkfQ==",
+ "extraneous": true,
"license": "MIT",
"dependencies": {
- "fast-json-patch": "^3.0.0-1",
- "htm": "^3.0.3"
+ "event-to-object": "^0.1.2",
+ "json-pointer": "^0.6.2"
},
"devDependencies": {
- "jsdom": "16.3.0",
- "lodash": "^4.17.21",
- "prettier": "^2.5.1",
- "uvu": "^0.5.1"
+ "@types/json-pointer": "^1.0.31",
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "typescript": "^4.9.5"
},
"peerDependencies": {
- "react": ">=16",
- "react-dom": ">=16"
+ "react": ">=16 <18",
+ "react-dom": ">=16 <18"
}
},
+ "../../../src/client/packages/client": {
+ "name": "@reactpy/client",
+ "version": "0.2.0",
+ "extraneous": true,
+ "license": "MIT",
+ "dependencies": {
+ "event-to-object": "^0.1.0",
+ "json-pointer": "^0.6.2"
+ },
+ "devDependencies": {
+ "@types/json-pointer": "^1.0.31",
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "prettier": "^3.0.0-alpha.6",
+ "typescript": "^4.9.5"
+ },
+ "peerDependencies": {
+ "react": ">=16 <18",
+ "react-dom": ">=16 <18"
+ }
+ },
+ "../../../src/js/packages/@reactpy/client": {
+ "version": "0.3.1",
+ "license": "MIT",
+ "dependencies": {
+ "event-to-object": "^0.1.2",
+ "json-pointer": "^0.6.2"
+ },
+ "devDependencies": {
+ "@types/json-pointer": "^1.0.31",
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "typescript": "^4.9.5"
+ },
+ "peerDependencies": {
+ "react": ">=16 <18",
+ "react-dom": ">=16 <18"
+ }
+ },
+ "node_modules/@reactpy/client": {
+ "resolved": "../../../src/js/packages/@reactpy/client",
+ "link": true
+ },
"node_modules/@rollup/plugin-commonjs": {
"version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz",
+ "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^3.1.0",
"commondir": "^1.0.1",
@@ -58,13 +104,15 @@
},
"node_modules/@rollup/plugin-commonjs/node_modules/estree-walker": {
"version": "2.0.2",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "dev": true
},
"node_modules/@rollup/plugin-node-resolve": {
"version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.1.tgz",
+ "integrity": "sha512-6QKtRevXLrmEig9UiMYt2fSvee9TyltGRfw+qSs6xjUnxwjOzTOqy+/Lpxsgjb8mJn1EQNbCDAvt89O4uzL5kw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^3.1.0",
"@types/resolve": "1.17.1",
@@ -82,16 +130,18 @@
},
"node_modules/@rollup/plugin-node-resolve/node_modules/@types/resolve": {
"version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+ "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@rollup/plugin-replace": {
"version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-3.0.0.tgz",
+ "integrity": "sha512-3c7JCbMuYXM4PbPWT4+m/4Y6U60SgsnDT/cCyAyUKwFHg7pTSfsSQzIpETha3a3ig6OdOKzZz87D9ZXIK3qsDg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@rollup/pluginutils": "^3.1.0",
"magic-string": "^0.25.7"
@@ -102,8 +152,9 @@
},
"node_modules/@rollup/pluginutils": {
"version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+ "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"dev": true,
- "license": "MIT",
"dependencies": {
"@types/estree": "0.0.39",
"estree-walker": "^1.0.1",
@@ -118,33 +169,39 @@
},
"node_modules/@rollup/pluginutils/node_modules/@types/estree": {
"version": "0.0.39",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
+ "dev": true
},
"node_modules/@rollup/pluginutils/node_modules/estree-walker": {
"version": "1.0.1",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
+ "dev": true
},
"node_modules/@types/estree": {
"version": "0.0.48",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz",
+ "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==",
+ "dev": true
},
"node_modules/@types/node": {
"version": "15.12.2",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
+ "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==",
+ "dev": true
},
"node_modules/balanced-match": {
"version": "1.0.2",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
},
"node_modules/brace-expansion": {
"version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -152,8 +209,9 @@
},
"node_modules/builtin-modules": {
"version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
+ "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=6"
},
@@ -163,36 +221,56 @@
},
"node_modules/commondir": {
"version": "1.0.1",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+ "dev": true
},
"node_modules/concat-map": {
"version": "0.0.1",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+ "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==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+ "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==",
"dev": true,
- "license": "ISC"
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
},
"node_modules/function-bind": {
"version": "1.1.1",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
},
"node_modules/glob": {
"version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
- "license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -210,8 +288,9 @@
},
"node_modules/has": {
"version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
- "license": "MIT",
"dependencies": {
"function-bind": "^1.1.1"
},
@@ -219,14 +298,11 @@
"node": ">= 0.4.0"
}
},
- "node_modules/idom-client-react": {
- "resolved": "../../../src/client/packages/idom-client-react",
- "link": true
- },
"node_modules/inflight": {
"version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
- "license": "ISC",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -234,13 +310,15 @@
},
"node_modules/inherits": {
"version": "2.0.4",
- "dev": true,
- "license": "ISC"
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
},
"node_modules/is-core-module": {
"version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
+ "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
"dev": true,
- "license": "MIT",
"dependencies": {
"has": "^1.0.3"
},
@@ -250,29 +328,33 @@
},
"node_modules/is-module": {
"version": "1.0.0",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
+ "dev": true
},
"node_modules/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,
- "license": "MIT",
"dependencies": {
"@types/estree": "*"
}
},
"node_modules/magic-string": {
"version": "0.25.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
+ "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
"dev": true,
- "license": "MIT",
"dependencies": {
"sourcemap-codec": "^1.4.4"
}
},
"node_modules/minimatch": {
"version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
- "license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -282,29 +364,33 @@
},
"node_modules/once": {
"version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
- "license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
},
"node_modules/picomatch": {
"version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
"dev": true,
- "license": "MIT",
"engines": {
"node": ">=8.6"
},
@@ -314,8 +400,9 @@
},
"node_modules/prettier": {
"version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz",
+ "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==",
"dev": true,
- "license": "MIT",
"bin": {
"prettier": "bin-prettier.js"
},
@@ -325,8 +412,9 @@
},
"node_modules/resolve": {
"version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
"dev": true,
- "license": "MIT",
"dependencies": {
"is-core-module": "^2.2.0",
"path-parse": "^1.0.6"
@@ -337,8 +425,9 @@
},
"node_modules/rollup": {
"version": "2.52.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.1.tgz",
+ "integrity": "sha512-/SPqz8UGnp4P1hq6wc9gdTqA2bXQXGx13TtoL03GBm6qGRI6Hm3p4Io7GeiHNLl0BsQAne1JNYY+q/apcY933w==",
"dev": true,
- "license": "MIT",
"bin": {
"rollup": "dist/bin/rollup"
},
@@ -351,18 +440,33 @@
},
"node_modules/sourcemap-codec": {
"version": "1.4.8",
- "dev": true,
- "license": "MIT"
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
+ "dev": true
},
"node_modules/wrappy": {
"version": "1.0.2",
- "dev": true,
- "license": "ISC"
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+ "dev": true
}
},
"dependencies": {
+ "@reactpy/client": {
+ "version": "file:../../../src/js/packages/@reactpy/client",
+ "requires": {
+ "@types/json-pointer": "^1.0.31",
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "event-to-object": "^0.1.2",
+ "json-pointer": "^0.6.2",
+ "typescript": "^4.9.5"
+ }
+ },
"@rollup/plugin-commonjs": {
"version": "21.0.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-21.0.1.tgz",
+ "integrity": "sha512-EA+g22lbNJ8p5kuZJUYyhhDK7WgJckW5g4pNN7n4mAFUM96VuwUnNT3xr2Db2iCZPI1pJPbGyfT5mS9T1dHfMg==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
@@ -376,12 +480,16 @@
"dependencies": {
"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==",
"dev": true
}
}
},
"@rollup/plugin-node-resolve": {
"version": "13.1.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.1.1.tgz",
+ "integrity": "sha512-6QKtRevXLrmEig9UiMYt2fSvee9TyltGRfw+qSs6xjUnxwjOzTOqy+/Lpxsgjb8mJn1EQNbCDAvt89O4uzL5kw==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
@@ -394,6 +502,8 @@
"dependencies": {
"@types/resolve": {
"version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
+ "integrity": "sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -403,6 +513,8 @@
},
"@rollup/plugin-replace": {
"version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-3.0.0.tgz",
+ "integrity": "sha512-3c7JCbMuYXM4PbPWT4+m/4Y6U60SgsnDT/cCyAyUKwFHg7pTSfsSQzIpETha3a3ig6OdOKzZz87D9ZXIK3qsDg==",
"dev": true,
"requires": {
"@rollup/pluginutils": "^3.1.0",
@@ -411,6 +523,8 @@
},
"@rollup/pluginutils": {
"version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz",
+ "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==",
"dev": true,
"requires": {
"@types/estree": "0.0.39",
@@ -420,28 +534,40 @@
"dependencies": {
"@types/estree": {
"version": "0.0.39",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
+ "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==",
"dev": true
},
"estree-walker": {
"version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz",
+ "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==",
"dev": true
}
}
},
"@types/estree": {
"version": "0.0.48",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.48.tgz",
+ "integrity": "sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew==",
"dev": true
},
"@types/node": {
"version": "15.12.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.2.tgz",
+ "integrity": "sha512-zjQ69G564OCIWIOHSXyQEEDpdpGl+G348RAKY0XXy9Z5kU9Vzv1GMNnkar/ZJ8dzXB3COzD9Mo9NtRZ4xfgUww==",
"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": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
@@ -450,30 +576,51 @@
},
"builtin-modules": {
"version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.2.0.tgz",
+ "integrity": "sha512-lGzLKcioL90C7wMczpkY0n/oART3MbBa8R9OFGE1rJxoVI86u4WAGfEk8Wjv10eKSyTHVGkSo3bvBylCEtk7LA==",
"dev": true
},
"commondir": {
"version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
"dev": true
},
"concat-map": {
"version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"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
},
"fs.realpath": {
"version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"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==",
"dev": true
},
"glob": {
"version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
@@ -486,24 +633,17 @@
},
"has": {
"version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"requires": {
"function-bind": "^1.1.1"
}
},
- "idom-client-react": {
- "version": "file:../../../src/client/packages/idom-client-react",
- "requires": {
- "fast-json-patch": "^3.0.0-1",
- "htm": "^3.0.3",
- "jsdom": "16.3.0",
- "lodash": "^4.17.21",
- "prettier": "^2.5.1",
- "uvu": "^0.5.1"
- }
- },
"inflight": {
"version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
@@ -512,10 +652,14 @@
},
"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-core-module": {
"version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz",
+ "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==",
"dev": true,
"requires": {
"has": "^1.0.3"
@@ -523,10 +667,14 @@
},
"is-module": {
"version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=",
"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": "*"
@@ -534,6 +682,8 @@
},
"magic-string": {
"version": "0.25.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz",
+ "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==",
"dev": true,
"requires": {
"sourcemap-codec": "^1.4.4"
@@ -541,6 +691,8 @@
},
"minimatch": {
"version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
@@ -548,6 +700,8 @@
},
"once": {
"version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
@@ -555,22 +709,32 @@
},
"path-is-absolute": {
"version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
},
"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==",
"dev": true
},
"picomatch": {
"version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
+ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
"dev": true
},
"prettier": {
"version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.3.1.tgz",
+ "integrity": "sha512-p+vNbgpLjif/+D+DwAZAbndtRrR0md0MwfmOVN9N+2RgyACMT+7tfaRnT+WDPkqnuVwleyuBIG2XBxKDme3hPA==",
"dev": true
},
"resolve": {
"version": "1.20.0",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
+ "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
"dev": true,
"requires": {
"is-core-module": "^2.2.0",
@@ -579,6 +743,8 @@
},
"rollup": {
"version": "2.52.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.52.1.tgz",
+ "integrity": "sha512-/SPqz8UGnp4P1hq6wc9gdTqA2bXQXGx13TtoL03GBm6qGRI6Hm3p4Io7GeiHNLl0BsQAne1JNYY+q/apcY933w==",
"dev": true,
"requires": {
"fsevents": "~2.3.2"
@@ -586,10 +752,14 @@
},
"sourcemap-codec": {
"version": "1.4.8",
+ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz",
+ "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
},
"wrappy": {
"version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
}
}
diff --git a/docs/source/_custom_js/package.json b/docs/source/_custom_js/package.json
index 633a43739..78d72b961 100644
--- a/docs/source/_custom_js/package.json
+++ b/docs/source/_custom_js/package.json
@@ -1,7 +1,7 @@
{
- "name": "idom-docs-example-loader",
+ "name": "reactpy-docs-example-loader",
"version": "1.0.0",
- "description": "simple javascript client for IDOM's documentation",
+ "description": "simple javascript client for ReactPy's documentation",
"main": "index.js",
"scripts": {
"build": "rollup --config",
@@ -15,6 +15,6 @@
"rollup": "^2.35.1"
},
"dependencies": {
- "idom-client-react": "file:../../../src/client/packages/idom-client-react"
+ "@reactpy/client": "file:../../../src/js/packages/@reactpy/client"
}
}
diff --git a/docs/source/_custom_js/src/index.js b/docs/source/_custom_js/src/index.js
index 1aa401123..505adedd0 100644
--- a/docs/source/_custom_js/src/index.js
+++ b/docs/source/_custom_js/src/index.js
@@ -1,42 +1,42 @@
-import { mountWithLayoutServer, LayoutServerInfo } from "idom-client-react";
+import { SimpleReactPyClient, mount } from "@reactpy/client";
let didMountDebug = false;
export function mountWidgetExample(
mountID,
viewID,
- idomServerHost,
- useActivateButton
+ reactpyServerHost,
+ useActivateButton,
) {
- let idomHost, idomPort;
- if (idomServerHost) {
- [idomHost, idomPort] = idomServerHost.split(":", 2);
+ let reactpyHost, reactpyPort;
+ if (reactpyServerHost) {
+ [reactpyHost, reactpyPort] = reactpyServerHost.split(":", 2);
} else {
- idomHost = window.location.hostname;
- idomPort = window.location.port;
+ reactpyHost = window.location.hostname;
+ reactpyPort = window.location.port;
}
- const serverInfo = new LayoutServerInfo({
- host: idomHost,
- port: idomPort,
- path: "/_idom/",
- query: `view_id=${viewID}`,
- secure: window.location.protocol == "https:",
+ const client = new SimpleReactPyClient({
+ serverLocation: {
+ url: `${window.location.protocol}//${reactpyHost}:${reactpyPort}`,
+ route: "/",
+ query: `?view_id=${viewID}`,
+ },
});
const mountEl = document.getElementById(mountID);
let isMounted = false;
triggerIfInViewport(mountEl, () => {
if (!isMounted) {
- activateView(mountEl, serverInfo, useActivateButton);
+ activateView(mountEl, client, useActivateButton);
isMounted = true;
}
});
}
-function activateView(mountEl, serverInfo, useActivateButton) {
+function activateView(mountEl, client, useActivateButton) {
if (!useActivateButton) {
- mountWithLayoutServer(mountEl, serverInfo);
+ mount(mountEl, client);
return;
}
@@ -51,7 +51,7 @@ function activateView(mountEl, serverInfo, useActivateButton) {
mountEl.setAttribute("class", "interactive widget-container");
mountWithLayoutServer(mountEl, serverInfo);
}
- })
+ }),
);
function fadeOutElementThenCallback(element, callback) {
@@ -86,8 +86,8 @@ function triggerIfInViewport(element, callback) {
},
{
root: null,
- threshold: 0.1, // set offset 0.1 means trigger if atleast 10% of element in viewport
- }
+ threshold: 0.1, // set offset 0.1 means trigger if at least 10% of element in viewport
+ },
);
observer.observe(element);
diff --git a/docs/source/_exts/async_doctest.py b/docs/source/_exts/async_doctest.py
index 7db4116d9..96024d488 100644
--- a/docs/source/_exts/async_doctest.py
+++ b/docs/source/_exts/async_doctest.py
@@ -6,18 +6,16 @@
from sphinx.ext.doctest import DocTestBuilder
from sphinx.ext.doctest import setup as doctest_setup
-
test_template = """
-import asyncio as __asyncio
+import asyncio as __test_template_asyncio
-async def __run():
+async def __test_template__main():
{test}
globals().update(locals())
-loop = __asyncio.get_event_loop()
-loop.run_until_complete(__run())
+__test_template_asyncio.run(__test_template__main())
"""
@@ -42,10 +40,8 @@ def test_runner(self) -> DocTestRunner:
@test_runner.setter
def test_runner(self, value: DocTestRunner) -> None:
self._test_runner = TestRunnerWrapper(value)
- return None
def setup(app: Sphinx) -> None:
doctest_setup(app)
app.add_builder(AsyncDoctestBuilder, override=True)
- return None
diff --git a/docs/source/_exts/autogen_api_docs.py b/docs/source/_exts/autogen_api_docs.py
index 96ee6e876..b95d85a99 100644
--- a/docs/source/_exts/autogen_api_docs.py
+++ b/docs/source/_exts/autogen_api_docs.py
@@ -1,15 +1,14 @@
from __future__ import annotations
import sys
+from collections.abc import Collection, Iterator
from pathlib import Path
-from typing import Collection, Iterator
from sphinx.application import Sphinx
-
HERE = Path(__file__).parent
SRC = HERE.parent.parent.parent / "src"
-PYTHON_PACKAGE = SRC / "idom"
+PYTHON_PACKAGE = SRC / "py" / "reactpy" / "reactpy"
AUTO_DIR = HERE.parent / "_auto"
AUTO_DIR.mkdir(exist_ok=True)
@@ -22,10 +21,12 @@
AUTODOC_TEMPLATE_WITH_MEMBERS = """\
.. automodule:: {module}
:members:
+ :ignore-module-all:
"""
AUTODOC_TEMPLATE_WITHOUT_MEMBERS = """\
.. automodule:: {module}
+ :ignore-module-all:
"""
TITLE = """\
@@ -80,9 +81,12 @@ def get_module_name(path: Path) -> str:
def get_section_symbol(path: Path) -> str:
- rel_path_parts = path.relative_to(PYTHON_PACKAGE).parts
- assert len(rel_path_parts) < len(SECTION_SYMBOLS), "package structure is too deep"
- return SECTION_SYMBOLS[len(rel_path_parts)]
+ rel_path = path.relative_to(PYTHON_PACKAGE)
+ rel_path_parts = rel_path.parts
+ if len(rel_path_parts) > len(SECTION_SYMBOLS):
+ msg = f"package structure is too deep - ran out of section symbols: {rel_path}"
+ raise RuntimeError(msg)
+ return SECTION_SYMBOLS[len(rel_path_parts) - 1]
def walk_python_files(root: Path, ignore_dirs: Collection[str]) -> Iterator[Path]:
diff --git a/docs/source/_exts/build_custom_js.py b/docs/source/_exts/build_custom_js.py
index b84378353..97857ba74 100644
--- a/docs/source/_exts/build_custom_js.py
+++ b/docs/source/_exts/build_custom_js.py
@@ -3,11 +3,10 @@
from sphinx.application import Sphinx
-
SOURCE_DIR = Path(__file__).parent.parent
CUSTOM_JS_DIR = SOURCE_DIR / "_custom_js"
def setup(app: Sphinx) -> None:
- subprocess.run("npm install", cwd=CUSTOM_JS_DIR, shell=True)
- subprocess.run("npm run build", cwd=CUSTOM_JS_DIR, shell=True)
+ subprocess.run("npm install", cwd=CUSTOM_JS_DIR, shell=True) # noqa S607
+ subprocess.run("npm run build", cwd=CUSTOM_JS_DIR, shell=True) # noqa S607
diff --git a/docs/source/_exts/copy_vdom_json_schema.py b/docs/source/_exts/copy_vdom_json_schema.py
index f78a163c2..38fc171ac 100644
--- a/docs/source/_exts/copy_vdom_json_schema.py
+++ b/docs/source/_exts/copy_vdom_json_schema.py
@@ -3,7 +3,7 @@
from sphinx.application import Sphinx
-from idom.core.vdom import VDOM_JSON_SCHEMA
+from reactpy.core.vdom import VDOM_JSON_SCHEMA
def setup(app: Sphinx) -> None:
diff --git a/docs/source/_exts/custom_autosectionlabel.py b/docs/source/_exts/custom_autosectionlabel.py
index 573bc35dd..92ff5e2df 100644
--- a/docs/source/_exts/custom_autosectionlabel.py
+++ b/docs/source/_exts/custom_autosectionlabel.py
@@ -4,8 +4,10 @@
https://github.com/sphinx-doc/sphinx/blob/f9968594206e538f13fa1c27c065027f10d4ea27/LICENSE
"""
+from __future__ import annotations
+
from fnmatch import fnmatch
-from typing import Any, Dict, cast
+from typing import Any, cast
from docutils import nodes
from docutils.nodes import Node
@@ -15,7 +17,6 @@
from sphinx.util import logging
from sphinx.util.nodes import clean_astext
-
logger = logging.getLogger(__name__)
@@ -30,7 +31,6 @@ def get_node_depth(node: Node) -> int:
def register_sections_as_label(app: Sphinx, document: Node) -> None:
docname = app.env.docname
- print(docname)
for pattern in app.config.autosectionlabel_skip_docs:
if fnmatch(docname, pattern):
@@ -67,7 +67,7 @@ def register_sections_as_label(app: Sphinx, document: Node) -> None:
domain.labels[name] = docname, labelid, sectname
-def setup(app: Sphinx) -> Dict[str, Any]:
+def setup(app: Sphinx) -> dict[str, Any]:
app.add_config_value("autosectionlabel_prefix_document", False, "env")
app.add_config_value("autosectionlabel_maxdepth", None, "env")
app.add_config_value("autosectionlabel_skip_docs", [], "env")
diff --git a/docs/source/_exts/idom_example.py b/docs/source/_exts/reactpy_example.py
similarity index 86%
rename from docs/source/_exts/idom_example.py
rename to docs/source/_exts/reactpy_example.py
index 07c0a74a2..1171d32e0 100644
--- a/docs/source/_exts/idom_example.py
+++ b/docs/source/_exts/reactpy_example.py
@@ -2,28 +2,26 @@
import re
from pathlib import Path
-from typing import Any
+from typing import Any, ClassVar
+from docs_app.examples import (
+ SOURCE_DIR,
+ get_example_files_by_name,
+ get_normalized_example_name,
+)
from docutils.parsers.rst import directives
from docutils.statemachine import StringList
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
from sphinx_design.tabs import TabSetDirective
-from docs.examples import (
- SOURCE_DIR,
- get_example_files_by_name,
- get_normalized_example_name,
-)
-
class WidgetExample(SphinxDirective):
-
has_content = False
required_arguments = 1
_next_id = 0
- option_spec = {
+ option_spec: ClassVar[dict[str, Any]] = {
"result-is-default-tab": directives.flag,
"activate-button": directives.flag,
}
@@ -42,16 +40,14 @@ def run(self):
ex_files = get_example_files_by_name(example_name)
if not ex_files:
src_file, line_num = self.get_source_info()
- raise ValueError(
- f"Missing example named {example_name!r} "
- f"referenced by document {src_file}:{line_num}"
- )
+ msg = f"Missing example named {example_name!r} referenced by document {src_file}:{line_num}"
+ raise ValueError(msg)
labeled_tab_items: list[tuple[str, Any]] = []
if len(ex_files) == 1:
labeled_tab_items.append(
(
- "app.py",
+ "main.py",
_literal_include(
path=ex_files[0],
linenos=show_linenos,
@@ -59,7 +55,9 @@ def run(self):
)
)
else:
- for path in sorted(ex_files, key=lambda p: p.name):
+ for path in sorted(
+ ex_files, key=lambda p: "" if p.name == "main.py" else p.name
+ ):
labeled_tab_items.append(
(
path.name,
@@ -71,7 +69,7 @@ def run(self):
)
result_tab_item = (
- "âļī¸ result",
+ "đ result",
_interactive_widget(
name=example_name,
with_activate_button=not activate_result,
@@ -87,7 +85,7 @@ def run(self):
[],
{},
_make_tab_items(labeled_tab_items),
- self.lineno - 1,
+ self.lineno - 2,
self.content_offset,
"",
self.state,
@@ -113,7 +111,8 @@ def _literal_include(path: Path, linenos: bool):
".json": "json",
}[path.suffix]
except KeyError:
- raise ValueError(f"Unknown extension type {path.suffix!r}")
+ msg = f"Unknown extension type {path.suffix!r}"
+ raise ValueError(msg) from None
return _literal_include_template.format(
name=str(path.relative_to(SOURCE_DIR)),
@@ -161,7 +160,7 @@ def _interactive_widget(name, with_activate_button):
_interactive_widget_template = """
-.. idom-view:: {name}
+.. reactpy-view:: {name}
{activate_button_opt}
"""
@@ -178,4 +177,4 @@ def _string_to_nested_lines(content):
def setup(app: Sphinx) -> None:
- app.add_directive("idom", WidgetExample)
+ app.add_directive("reactpy", WidgetExample)
diff --git a/docs/source/_exts/idom_view.py b/docs/source/_exts/reactpy_view.py
similarity index 69%
rename from docs/source/_exts/idom_view.py
rename to docs/source/_exts/reactpy_view.py
index 995640301..6a583998f 100644
--- a/docs/source/_exts/idom_view.py
+++ b/docs/source/_exts/reactpy_view.py
@@ -1,31 +1,31 @@
import os
+from typing import Any, ClassVar
+from docs_app.examples import get_normalized_example_name
from docutils.nodes import raw
from docutils.parsers.rst import directives
from sphinx.application import Sphinx
from sphinx.util.docutils import SphinxDirective
-from docs.examples import get_normalized_example_name
-
-
-_IDOM_EXAMPLE_HOST = os.environ.get("IDOM_DOC_EXAMPLE_SERVER_HOST", "")
-_IDOM_STATIC_HOST = os.environ.get("IDOM_DOC_STATIC_SERVER_HOST", "/docs").rstrip("/")
+_REACTPY_EXAMPLE_HOST = os.environ.get("REACTPY_DOC_EXAMPLE_SERVER_HOST", "")
+_REACTPY_STATIC_HOST = os.environ.get("REACTPY_DOC_STATIC_SERVER_HOST", "/docs").rstrip(
+ "/"
+)
class IteractiveWidget(SphinxDirective):
-
has_content = False
required_arguments = 1
_next_id = 0
- option_spec = {
+ option_spec: ClassVar[dict[str, Any]] = {
"activate-button": directives.flag,
"margin": float,
}
def run(self):
IteractiveWidget._next_id += 1
- container_id = f"idom-widget-{IteractiveWidget._next_id}"
+ container_id = f"reactpy-widget-{IteractiveWidget._next_id}"
view_id = get_normalized_example_name(
self.arguments[0],
# only used if example name starts with "/"
@@ -38,15 +38,15 @@ def run(self):
@@ -58,4 +58,4 @@ def run(self):
def setup(app: Sphinx) -> None:
- app.add_directive("idom-view", IteractiveWidget)
+ app.add_directive("reactpy-view", IteractiveWidget)
diff --git a/docs/source/_static/css/furo-theme-overrides.css b/docs/source/_static/css/furo-theme-overrides.css
index f23c23168..a258e025e 100644
--- a/docs/source/_static/css/furo-theme-overrides.css
+++ b/docs/source/_static/css/furo-theme-overrides.css
@@ -1,4 +1,6 @@
-body {
- --admonition-title-font-size: 1rem !important;
- --admonition-font-size: 1rem !important;
+.sidebar-container {
+ width: 18em;
+}
+.sidebar-brand-text {
+ display: none;
}
diff --git a/docs/source/_static/css/idom-view.css b/docs/source/_static/css/reactpy-view.css
similarity index 91%
rename from docs/source/_static/css/idom-view.css
rename to docs/source/_static/css/reactpy-view.css
index 9abe24198..56df74970 100644
--- a/docs/source/_static/css/idom-view.css
+++ b/docs/source/_static/css/reactpy-view.css
@@ -21,11 +21,6 @@
width: 100%;
}
-.center-content {
- display: flex;
- align-items: center;
- justify-content: center;
-}
.enable-widget-button {
padding: 10px;
color: #ffffff !important;
diff --git a/docs/source/_static/css/set-color-scheme.css b/docs/source/_static/css/set-color-scheme.css
deleted file mode 100644
index 4b9521c7d..000000000
--- a/docs/source/_static/css/set-color-scheme.css
+++ /dev/null
@@ -1,3 +0,0 @@
-:root {
- color-scheme: light dark;
-}
diff --git a/docs/source/_static/css/sphinx-design-overrides.css b/docs/source/_static/css/sphinx-design-overrides.css
index cc9b285c0..767d9d16c 100644
--- a/docs/source/_static/css/sphinx-design-overrides.css
+++ b/docs/source/_static/css/sphinx-design-overrides.css
@@ -1,12 +1,3 @@
-body {
- --sd-color-info: var(--color-admonition-title-background--note);
- --sd-color-warning: var(--color-admonition-title-background--warning);
- --sd-color-danger: var(--color-admonition-title-background--danger);
- --sd-color-info-text: var(--color-admonition-title--note);
- --sd-color-warning-text: var(--color-admonition-title--warning);
- --sd-color-danger-text: var(--color-admonition-title--danger);
-}
-
.sd-card-body {
display: flex;
flex-direction: column;
diff --git a/docs/source/_static/install-and-run-idom.gif b/docs/source/_static/install-and-run-idom.gif
deleted file mode 100644
index 67d226a12..000000000
Binary files a/docs/source/_static/install-and-run-idom.gif and /dev/null differ
diff --git a/docs/source/_static/install-and-run-reactpy.gif b/docs/source/_static/install-and-run-reactpy.gif
new file mode 100644
index 000000000..49d431341
Binary files /dev/null and b/docs/source/_static/install-and-run-reactpy.gif differ
diff --git a/docs/source/about/changelog.rst b/docs/source/about/changelog.rst
new file mode 100644
index 000000000..48a229eee
--- /dev/null
+++ b/docs/source/about/changelog.rst
@@ -0,0 +1,1171 @@
+Changelog
+=========
+
+.. note::
+
+ All notable changes to this project will be recorded in this document. The style of
+ which is based on `Keep a Changelog
`__. The versioning
+ scheme for the project adheres to `Semantic Versioning
`__. For
+ more info, see the :ref:`Contributor Guide
`.
+
+
+.. INSTRUCTIONS FOR CHANGELOG CONTRIBUTORS
+.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+.. If you're adding a changelog entry, be sure to read the "Creating a Changelog Entry"
+.. section of the documentation before doing so for instructions on how to adhere to the
+.. "Keep a Changelog" style guide (https://keepachangelog.com).
+
+Unreleased
+----------
+
+**Fixed**
+
+- :pull:`1118` - `module_from_template` is broken with a recent release of `requests`
+- :pull:`1131` - `module_from_template` did not work when using Flask backend
+- :pull:`1200` - Fixed `UnicodeDecodeError` when using `reactpy.web.export`
+
+**Added**
+
+- :pull:`1165` - Allow asynchronously rendering discrete component tree - enable this
+ experimental feature by setting `REACTPY_ASYNC_RENDERING=true`. This should improve
+ the overall responsiveness of your app, particularly when handling larger renders
+ that would otherwise block faster renders from being processed.
+
+**Changed**
+
+- :pull:`1171` - Previously ``None``, when present in an HTML element, would render as
+ the string ``"None"``. Now ``None`` will not render at all. This is consistent with
+ how ``None`` is handled when returned from components. It also makes it easier to
+ conditionally render elements. For example, previously you would have needed to use a
+ fragment to conditionally render an element by writing
+ ``something if condition else html._()``. Now you can simply write
+ ``something if condition else None``.
+
+**Deprecated**
+
+- :pull:`1171` - The ``Stop`` exception. Recent releases of ``anyio`` have made this
+ exception difficult to use since it now raises an ``ExceptionGroup``. This exception
+ was primarily used for internal testing purposes and so is now deprecated.
+
+
+v1.0.2
+------
+
+**Fixed**
+
+- :issue:`1086` - fix rendering bug when children change positions (via :pull:`1085`)
+
+
+v1.0.1
+------
+
+**Changed**
+
+- :pull:`1050` - Warn and attempt to fix missing mime types, which can result in ``reactpy.run`` not working as expected.
+- :pull:`1051` - Rename ``reactpy.backend.BackendImplementation`` to ``reactpy.backend.BackendType``
+- :pull:`1051` - Allow ``reactpy.run`` to fail in more predictable ways
+
+**Fixed**
+
+- :issue:`930` - better traceback for JSON serialization errors (via :pull:`1008`)
+- :issue:`437` - explain that JS component attributes must be JSON (via :pull:`1008`)
+- :pull:`1051` - Fix ``reactpy.run`` port assignment sometimes attaching to in-use ports on Windows
+- :pull:`1051` - Fix ``reactpy.run`` not recognizing ``fastapi``
+
+
+v1.0.0
+------
+:octicon:`milestone` *released on 2023-03-14*
+
+No changes.
+
+
+v1.0.0-a6
+---------
+:octicon:`milestone` *released on 2023-02-23*
+
+**Fixed**
+
+- :pull:`936` - remaining issues from :pull:`934`
+
+
+v1.0.0-a5
+---------
+:octicon:`milestone` *released on 2023-02-21*
+
+**Fixed**
+
+- :pull:`934` - minor issues with camelCase rewrite CLI utility
+
+
+v1.0.0-a4
+---------
+:octicon:`milestone` *released on 2023-02-21*
+
+**Changed**
+
+- :pull:`919` - Reverts :pull:`841` as per the conclusion in :discussion:`916`. but
+ preserves the ability to declare attributes with snake_case.
+
+**Deprecated**
+
+- :pull:`919` - Declaration of keys via keyword arguments in standard elements. A script
+ has been added to automatically convert old usages where possible.
+
+
+v1.0.0-a3
+---------
+:octicon:`milestone` *released on 2023-02-02*
+
+**Fixed**
+
+- :pull:`908` - minor type hint issue with ``VdomDictConstructor``
+
+**Removed**
+
+- :pull:`907` - accidental import of reactpy.testing
+
+
+v1.0.0-a2
+---------
+:octicon:`milestone` *released on 2023-01-31*
+
+**Reverted**
+
+- :pull:`901` - reverts :pull:`886` due to :issue:`896`
+
+**Fixed**
+
+- :issue:`896` - Stale event handlers after disconnect/reconnect cycle
+- :issue:`898` - Fixed CLI not registered as entry point
+
+
+v1.0.0-a1
+---------
+:octicon:`milestone` *released on 2023-01-28*
+
+**Changed**
+
+- :pull:`841` - Revamped element constructor interface. Now instead of passing a
+ dictionary of attributes to element constructors, attributes are declared using
+ keyword arguments. For example, instead of writing:
+
+ .. code-block::
+
+ html.div({"className": "some-class"}, "some", "text")
+
+ You now should write:
+
+ .. code-block::
+
+ html.div("some", "text", class_name="some-class")
+
+ .. note::
+
+ All attributes are written using ``snake_case``.
+
+ In conjunction, with these changes, ReactPy now supplies a command line utility that
+ makes a "best effort" attempt to automatically convert code to the new API. Usage of
+ this utility is as follows:
+
+ .. code-block:: bash
+
+ reactpy update-html-usages [PATHS]
+
+ Where ``[PATHS]`` is any number of directories or files that should be rewritten.
+
+ .. warning::
+
+ After running this utility, code comments and formatting may have been altered. It's
+ recommended that you run a code formatting tool like `Black
+ `__ and manually review and replace any comments that
+ may have been moved.
+
+**Fixed**
+
+- :issue:`755` - unification of component and VDOM constructor interfaces. See above.
+
+
+v0.44.0
+-------
+:octicon:`milestone` *released on 2023-01-27*
+
+**Deprecated**
+
+- :pull:`876` - ``reactpy.widgets.hotswap``. The function has no clear uses outside of some
+ internal applications. For this reason it has been deprecated.
+
+**Removed**
+
+- :pull:`886` - Ability to access element value from events via `event['value']` key.
+ Instead element value should be accessed via `event['target']['value']`. Originally
+ deprecated in :ref:`v0.34.0`.
+- :pull:`886` - old misspelled option ``reactpy.config.REACTPY_WED_MODULES_DIR``. Originally
+ deprecated in :ref:`v0.36.1`.
+
+
+v0.43.0
+-------
+:octicon:`milestone` *released on 2023-01-09*
+
+**Deprecated**
+
+- :pull:`870` - ``ComponentType.should_render()``. This method was implemented based on
+ reading the React/Preact source code. As it turns out though it seems like it's mostly
+ a vestige from the fact that both these libraries still support class-based
+ components. The ability for components to not render also caused several bugs.
+
+**Fixed**
+
+- :issue:`846` - Nested context does no update value if outer context should not render.
+- :issue:`847` - Detached model state on render of context consumer if unmounted and
+ context value does not change.
+
+
+v0.42.0
+-------
+:octicon:`milestone` *released on 2022-12-02*
+
+**Added**
+
+- :pull:`835` - Ability to customize the ```` element of ReactPy's built-in client.
+- :pull:`835` - ``vdom_to_html`` utility function.
+- :pull:`843` - Ability to subscribe to changes that are made to mutable options.
+- :pull:`832` - ``del_html_head_body_transform`` to remove ````, ````, and ```` while preserving children.
+- :pull:`699` - Support for form element serialization
+
+**Fixed**
+
+- :issue:`582` - ``REACTPY_DEBUG_MODE`` is now mutable and can be changed at runtime
+- :pull:`832` - Fix ``html_to_vdom`` improperly removing ````, ````, and ```` nodes.
+
+**Removed**
+
+- :pull:`832` - Removed ``reactpy.html.body`` as it is currently unusable due to technological limitations, and thus not needed.
+- :pull:`840` - remove ``REACTPY_FEATURE_INDEX_AS_DEFAULT_KEY`` option
+- :pull:`835` - ``serve_static_files`` option from backend configuration
+
+**Deprecated**
+
+- :commit:`8f3785b` - Deprecated ``module_from_template``
+
+v0.41.0
+-------
+:octicon:`milestone` *released on 2022-11-01*
+
+**Changed**
+
+- :pull:`823` - The hooks ``use_location`` and ``use_scope`` are no longer
+ implementation specific and are now available as top-level imports. Instead of each
+ backend defining these hooks, backends establish a ``ConnectionContext`` with this
+ information.
+- :pull:`824` - ReactPy's built-in backend server now expose the following routes:
+
+ - ``/_reactpy/assets/``
+ - ``/_reactpy/stream/``
+ - ``/_reactpy/modules/``
+ - ``//``
+
+ This should allow the browser to cache static resources. Even if your ``url_prefix``
+ is ``/_reactpy``, your app should still work as expected. Though if you're using
+ ``reactpy-router``, ReactPy's server routes will always take priority.
+- :pull:`824` - Backend implementations now strip any URL prefix in the pathname for
+ ``use_location``.
+- :pull:`827` - ``use_state`` now returns a named tuple with ``value`` and ``set_value``
+ fields. This is convenient for adding type annotations if the initial state value is
+ not the same as the values you might pass to the state setter. Where previously you
+ might have to do something like:
+
+ .. code-block::
+
+ value: int | None = None
+ value, set_value = use_state(value)
+
+ Now you can annotate your state using the ``State`` class:
+
+ .. code-block::
+
+ state: State[int | None] = use_state(None)
+
+ # access value and setter
+ state.value
+ state.set_value
+
+ # can still destructure if you need to
+ value, set_value = state
+
+**Added**
+
+- :pull:`823` - There is a new ``use_connection`` hook which returns a ``Connection``
+ object. This ``Connection`` object contains a ``location`` and ``scope``, along with
+ a ``carrier`` which is unique to each backend implementation.
+
+
+v0.40.2
+-------
+:octicon:`milestone` *released on 2022-09-13*
+
+**Changed**
+
+- :pull:`809` - Avoid the use of JSON patch for diffing models.
+
+
+v0.40.1
+-------
+:octicon:`milestone` *released on 2022-09-11*
+
+**Fixed**
+
+- :issue:`806` - Child models after a component fail to render
+
+
+v0.40.0 (yanked)
+----------------
+:octicon:`milestone` *released on 2022-08-13*
+
+**Fixed**
+
+- :issue:`777` - Fix edge cases where ``html_to_vdom`` can fail to convert HTML
+- :issue:`789` - Conditionally rendered components cannot use contexts
+- :issue:`773` - Use strict equality check for text, numeric, and binary types in hooks
+- :issue:`801` - Accidental mutation of old model causes invalid JSON Patch
+
+**Changed**
+
+- :pull:`123` - set default timeout on playwright page for testing
+- :pull:`787` - Track contexts in hooks as state
+- :pull:`787` - remove non-standard ``name`` argument from ``create_context``
+
+**Added**
+
+- :pull:`123` - ``asgiref`` as a dependency
+- :pull:`795` - ``lxml`` as a dependency
+
+
+v0.39.0
+-------
+:octicon:`milestone` *released on 2022-06-20*
+
+**Fixed**
+
+- :pull:`763` - ``No module named 'reactpy.server'`` from ``reactpy.run``
+- :pull:`749` - Setting appropriate MIME type for web modules in `sanic` server implementation
+
+**Changed**
+
+- :pull:`763` - renamed various:
+
+ - ``reactpy.testing.server -> reactpy.testing.backend``
+ - ``ServerFixture -> BackendFixture``
+ - ``DisplayFixture.server -> DisplayFixture.backend``
+
+- :pull:`765` - ``exports_default`` parameter is removed from ``module_from_template``.
+
+**Added**
+
+- :pull:`765` - ability to specify versions with module templates (e.g.
+ ``module_from_template("react@^17.0.0", ...)``).
+
+
+v0.38.1
+-------
+:octicon:`milestone` *released on 2022-04-15*
+
+**Fixed**
+
+- `reactive-python/reactpy-jupyter#22 `__ -
+ a missing file extension was causing a problem with WebPack.
+
+
+v0.38.0
+-------
+:octicon:`milestone` *released on 2022-04-15*
+
+No changes.
+
+
+v0.38.0-a4
+----------
+:octicon:`milestone` *released on 2022-04-15*
+
+**Added**
+
+- :pull:`733` - ``use_debug_value`` hook
+
+**Changed**
+
+- :pull:`733` - renamed ``assert_reactpy_logged`` testing util to ``assert_reactpy_did_log``
+
+
+v0.38.0-a3
+----------
+:octicon:`milestone` *released on 2022-04-15*
+
+**Changed**
+
+- :pull:`730` - Layout context management is not async
+
+
+v0.38.0-a2
+----------
+:octicon:`milestone` *released on 2022-04-14*
+
+**Added**
+
+- :pull:`721` - Implement ``use_location()`` hook. Navigating to any route below the
+ root of the application will be reflected in the ``location.pathname``. This operates
+ in concert with how ReactPy's configured routes have changed. This will ultimately work
+ towards resolving :issue:`569`.
+
+**Changed**
+
+- :pull:`721` - The routes ReactPy configures on apps have changed
+
+ .. code-block:: text
+
+ prefix/_api/modules/* web modules
+ prefix/_api/stream websocket endpoint
+ prefix/* client react app
+
+ This means that ReactPy's client app is available at any route below the configured
+ ``url_prefix`` besides ``prefix/_api``. The ``_api`` route will likely remain a route
+ which is reserved by ReactPy. The route navigated to below the ``prefix`` will be shown
+ in ``use_location``.
+
+- :pull:`721` - ReactPy's client now uses Preact instead of React
+
+- :pull:`726` - Renamed ``reactpy.server`` to ``reactpy.backend``. Other references to "server
+ implementations" have been renamed to "backend implementations" throughout the
+ documentation and code.
+
+**Removed**
+
+- :pull:`721` - ``redirect_root`` server option
+
+
+v0.38.0-a1
+----------
+:octicon:`milestone` *released on 2022-03-27*
+
+**Changed**
+
+- :pull:`703` - How ReactPy integrates with servers. ``reactpy.run`` no longer accepts an app
+ instance to discourage use outside of testing. ReactPy's server implementations now
+ provide ``configure()`` functions instead. ``reactpy.testing`` has been completely
+ reworked in order to support async web drivers
+- :pull:`703` - ``PerClientStateServer`` has been functionally replaced by ``configure``
+
+**Added**
+
+- :issue:`669` - Access to underlying server requests via contexts
+
+**Removed**
+
+- :issue:`669` - Removed ``reactpy.widgets.multiview`` since basic routing view ``use_scope`` is
+ now possible as well as all ``SharedClientStateServer`` implementations.
+
+**Fixed**
+
+- :issue:`591` - ReactPy's test suite no longer uses sync web drivers
+- :issue:`678` - Updated Sanic requirement to ``>=21``
+- :issue:`657` - How we advertise ``reactpy.run``
+
+
+v0.37.2
+-------
+:octicon:`milestone` *released on 2022-03-27*
+
+**Changed**
+
+- :pull:`701` - The name of ``proto`` modules to ``types`` and added a top level
+ ``reactpy.types`` module
+
+**Fixed**
+
+- :pull:`716` - A typo caused ReactPy to use the insecure ``ws`` web-socket protocol on
+ pages loaded with ``https`` instead of the secure ``wss`` protocol
+
+
+v0.37.1
+-------
+:octicon:`milestone` *released on 2022-03-05*
+
+No changes.
+
+
+v0.37.1-a2
+----------
+:octicon:`milestone` *released on 2022-03-02*
+
+**Fixed:**
+
+- :issue:`684` - Revert :pull:`694` and by making ``value`` uncontrolled client-side
+
+
+v0.37.1-a1
+----------
+:octicon:`milestone` *released on 2022-02-28*
+
+**Fixed:**
+
+- :issue:`684` - ``onChange`` event for inputs missing key strokes
+
+
+v0.37.0
+-------
+:octicon:`milestone` *released on 2022-02-27*
+
+**Added:**
+
+- :issue:`682` - Support for keys in HTML fragments
+- :pull:`585` - Use Context Hook
+
+**Fixed:**
+
+- :issue:`690` - React warning about set state in unmounted component
+- :pull:`688` - Missing reset of schedule_render_later flag
+
+----
+
+Releases below do not use the "Keep a Changelog" style guidelines.
+
+----
+
+v0.36.3
+-------
+:octicon:`milestone` *released on 2022-02-18*
+
+Misc bug fixes along with a minor improvement that allows components to return ``None``
+to render nothing.
+
+**Closed Issues**
+
+- All child states wiped upon any child key change - :issue:`652`
+- Allow NoneType returns within components - :issue:`538`
+
+**Merged Pull Requests**
+
+- fix #652 - :pull:`672`
+- Fix 663 - :pull:`667`
+
+
+v0.36.2
+-------
+:octicon:`milestone` *released on 2022-02-02*
+
+Hot fix for newly introduced ``DeprecatedOption``:
+
+- :commit:`c146dfb264cbc3d2256a62efdfe9ccf62c795b01`
+
+
+v0.36.1
+-------
+:octicon:`milestone` *released on 2022-02-02*
+
+Includes bug fixes and renames the configuration option ``REACTPY_WED_MODULES_DIR`` to
+``REACTPY_WEB_MODULES_DIR`` with a corresponding deprecation warning.
+
+**Closed Issues**
+
+- Fix Key Error When Cleaning Up Event Handlers - :issue:`640`
+- Update Script Tag Behavior - :issue:`628`
+
+**Merged Pull Requests**
+
+- mark old state as None if unmounting - :pull:`641`
+- rename REACTPY_WED_MODULES_DIR to REACTPY_WEB_MODULES_DIR - :pull:`638`
+
+
+v0.36.0
+-------
+:octicon:`milestone` *released on 2022-01-30*
+
+This release includes an important fix for errors produced after :pull:`623` was merged.
+In addition there is not a new ``http.script`` element which can behave similarly to a
+standard HTML ``
diff --git a/docs/source/getting-started/_static/embed-idom-view/index.html b/docs/source/getting-started/_static/embed-idom-view/index.html
deleted file mode 100644
index 3080e1d5d..000000000
--- a/docs/source/getting-started/_static/embed-idom-view/index.html
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
-
-
- Example App
-
-
- This is an Example App
- Just below is an embedded IDOM view...
-
-
-
-
diff --git a/docs/source/getting-started/_static/embed-idom-view/main.py b/docs/source/getting-started/_static/embed-idom-view/main.py
deleted file mode 100644
index 0c0cb5ac0..000000000
--- a/docs/source/getting-started/_static/embed-idom-view/main.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from sanic import Sanic
-from sanic.response import file
-
-from idom import component, html
-from idom.server.sanic import Config, PerClientStateServer
-
-
-app = Sanic(__name__)
-
-
-@app.route("/")
-async def index(request):
- return await file("index.html")
-
-
-@component
-def IdomView():
- return html.code("This text came from an IDOM App")
-
-
-PerClientStateServer(IdomView, app=app, config=Config(url_prefix="/_idom"))
-
-app.run(host="127.0.0.1", port=5000)
diff --git a/docs/source/getting-started/index.rst b/docs/source/getting-started/index.rst
deleted file mode 100644
index 0a5ed4daf..000000000
--- a/docs/source/getting-started/index.rst
+++ /dev/null
@@ -1,108 +0,0 @@
-Getting Started
-===============
-
-.. toctree::
- :hidden:
-
- installing-idom
- running-idom
-
-.. dropdown:: :octicon:`bookmark-fill;2em` What You'll Learn
- :color: info
- :animate: fade-in
- :open:
-
- .. grid:: 1 2 2 2
-
- .. grid-item-card:: :octicon:`tools` Installing IDOM
- :link: installing-idom
- :link-type: doc
-
- Learn how IDOM can be installed in a variety of different ways - with different web
- servers and even in different frameworks.
-
- .. grid-item-card:: :octicon:`play` Running IDOM
- :link: running-idom
- :link-type: doc
-
- See the ways that IDOM can be run with servers or be embedded in existing
- applications.
-
-The fastest way to get started with IDOM is to try it out in a `Juptyer Notebook
-`__.
-If you want to use a Notebook to work through the examples shown in this documentation,
-you'll need to replace calls to ``idom.run(App)`` with a line at the end of each cell
-that constructs the ``App()`` in question. If that doesn't make sense, the introductory
-notebook linked below will demonstrate how to do this:
-
-.. card::
- :link: https://mybinder.org/v2/gh/idom-team/idom-jupyter/main?urlpath=lab/tree/notebooks/introduction.ipynb
-
- .. image:: _static/idom-in-jupyterlab.gif
- :scale: 72%
- :align: center
-
-
-Section 1: Installing IDOM
---------------------------
-
-The next fastest option is to install IDOM with ``pip``:
-
-.. code-block:: bash
-
- pip install "idom[stable]"
-
-To check that everything is working you can run the sample application:
-
-.. code-block:: bash
-
- python -c "import idom; idom.run_sample_app(open_browser=True)"
-
-This should automatically open up a browser window to a page that looks like this:
-
-.. card::
-
- .. idom-view:: _examples/sample_app
-
-If you get a ``RuntimeError`` similar to the following:
-
-.. code-block:: text
-
- Found none of the following builtin server implementations...
-
-Then be sure you installed ``"idom[stable]"`` and not just ``idom``.
-
-For anything else, report your issue in IDOM's :discussion-type:`discussion forum
-`.
-
-.. card::
- :link: installing-idom
- :link-type: doc
-
- :octicon:`book` Read More
- ^^^^^^^^^^^^^^^^^^^^^^^^^
-
- Learn how IDOM can be installed in a variety of different ways - with different web
- servers and even in different frameworks.
-
-
-Section 2: Running IDOM
------------------------
-
-Once you've :ref:`installed IDOM `. The simplest way to run IDOM is
-with the :func:`~idom.server.prefab.run` function. By default this will execute your
-application using one of the builtin server implementations whose dependencies have all
-been installed. Running a tiny "hello world" application just requires the following
-code:
-
-.. idom:: _examples/hello_world
-
-.. card::
- :link: running-idom
- :link-type: doc
-
- :octicon:`book` Read More
- ^^^^^^^^^^^^^^^^^^^^^^^^^
-
- See the ways that IDOM can be run with servers or be embedded in existing
- applications.
diff --git a/docs/source/getting-started/installing-idom.rst b/docs/source/getting-started/installing-idom.rst
deleted file mode 100644
index a9082972d..000000000
--- a/docs/source/getting-started/installing-idom.rst
+++ /dev/null
@@ -1,114 +0,0 @@
-Installing IDOM
-===============
-
-The easiest way to ``pip`` install idom is to do so using the ``stable`` option:
-
-.. code-block:: bash
-
- pip install "idom[stable]"
-
-This includes a set of default dependencies for one of the builtin web server
-implementation. If you want to install IDOM without these dependencies you may simply
-``pip install idom``.
-
-
-Installing Other Servers
-------------------------
-
-IDOM includes built-in support for a variety web server implementations. To install the
-required dependencies for each you should substitute ``stable`` from the ``pip install``
-command above with one of the options below:
-
-- ``fastapi`` - https://fastapi.tiangolo.com
-- ``flask`` - https://palletsprojects.com/p/flask/
-- ``sanic`` - https://sanicframework.org
-- ``tornado`` - https://www.tornadoweb.org/en/stable/
-
-If you need to, you can install more than one option by separating them with commas:
-
-.. code-block:: bash
-
- pip install idom[fastapi,flask,sanic,tornado]
-
-Once this is complete you should be able to :ref:`run IDOM ` with your
-:ref:`chosen server implementation `.
-
-
-Installing In Other Frameworks
-------------------------------
-
-While IDOM can run in a variety of contexts, sometimes web frameworks require extra work
-in order to integrate with them. In these cases, the IDOM team distributes bindings for
-various frameworks as separate Python packages. For documentation on how to install and
-run IDOM in the supported frameworks, follow the links below:
-
-.. raw:: html
-
-
-
-.. role:: transparent-text-color
-
-.. We add transparent-text-color to the text so it's not visible, but it's still
-.. searchable.
-
-.. grid:: 3
-
- .. grid-item-card::
- :link: https://github.com/idom-team/django-idom
- :img-background: _static/logo-django.svg
- :class-card: card-logo-image
-
- :transparent-text-color:`Django`
-
- .. grid-item-card::
- :link: https://github.com/idom-team/idom-jupyter
- :img-background: _static/logo-jupyter.svg
- :class-card: card-logo-image
-
- :transparent-text-color:`Jupyter`
-
- .. grid-item-card::
- :link: https://github.com/idom-team/idom-dash
- :img-background: _static/logo-plotly.svg
- :class-card: card-logo-image
-
- :transparent-text-color:`Plotly Dash`
-
-
-Installing for Development
---------------------------
-
-If you want to contribute to the development of IDOM or modify it, you'll want to
-install a development version of IDOM. This involves cloning the repository where IDOM's
-source is maintained, and setting up a :ref:`development environment`. From there you'll
-be able to modifying IDOM's source code and :ref:`run its tests ` to
-ensure the modifications you've made are backwards compatible. If you want to add a new
-feature to IDOM you should write your own test that validates its behavior.
-
-If you have questions about how to modify IDOM or help with its development, be sure to
-`start a discussion
-`__. The IDOM team
-are always excited to :ref:`welcome ` new contributions and
-contributors of all kinds
-
-.. card::
- :link: /developing-idom/index
- :link-type: doc
-
- :octicon:`book` Read More
- ^^^^^^^^^^^^^^^^^^^^^^^^^
-
- Learn more about how to contribute to the development of IDOM.
diff --git a/docs/source/getting-started/running-idom.rst b/docs/source/getting-started/running-idom.rst
deleted file mode 100644
index 3b180f6e5..000000000
--- a/docs/source/getting-started/running-idom.rst
+++ /dev/null
@@ -1,290 +0,0 @@
-Running IDOM
-============
-
-The simplest way to run IDOM is with the :func:`~idom.server.prefab.run` function. By
-default this will execute your application using one of the builtin server
-implementations whose dependencies have all been installed. Running a tiny "hello world"
-application just requires the following code:
-
-.. idom:: _examples/hello_world
-
-.. note::
-
- Try clicking the **âļī¸ Result** tab to see what this displays!
-
-
-Running IDOM in Debug Mode
---------------------------
-
-IDOM provides a debug mode that is turned off by default. This can be enabled when you
-run your application by setting the ``IDOM_DEBUG_MODE`` environment variable.
-
-.. tab-set::
-
- .. tab-item:: Unix Shell
-
- .. code-block::
-
- export IDOM_DEBUG_MODE=1
- python my_idom_app.py
-
- .. tab-item:: Command Prompt
-
- .. code-block:: text
-
- set IDOM_DEBUG_MODE=1
- python my_idom_app.py
-
- .. tab-item:: PowerShell
-
- .. code-block:: powershell
-
- $env:IDOM_DEBUG_MODE = "1"
- python my_idom_app.py
-
-.. danger::
-
- Leave debug mode off in production!
-
-Among other things, running in this mode:
-
-- Turns on debug log messages
-- Adds checks to ensure the :ref:`VDOM` spec is adhered to
-- Displays error messages that occur within your app
-
-Errors will be displayed where the uppermost component is located in the view:
-
-.. idom:: _examples/debug_error_example
-
-
-Choosing a Server Implementation
---------------------------------
-
-Without extra care, running an IDOM app with the ``run()`` function can be somewhat
-inpredictable since the kind of server being used by default depends on what gets
-discovered first. To be more explicit about which server implementation you want to run
-with you can import your chosen server class and pass it to the ``server_type``
-parameter of ``run()``:
-
-.. code-block::
-
- from idom import component, html, run
- from idom.server.sanic import PerClientStateServer
-
-
- @component
- def App():
- return html.h1(f"Hello, World!")
-
-
- run(App, server_type=PerClientStateServer)
-
-Presently IDOM's core library supports the following server implementations:
-
-- :mod:`idom.server.fastapi`
-- :mod:`idom.server.sanic`
-- :mod:`idom.server.flask`
-- :mod:`idom.server.tornado`
-
-.. hint::
-
- To install them, see the :ref:`Installing Other Servers` section.
-
-
-Available Server Types
-----------------------
-
-Some of server implementations have more than one server type available. The server type
-which exists for all implementations is the ``PerClientStateServer``. This server type
-displays a unique view to each user who visits the site. For those that support it,
-there may also be a ``SharedClientStateServer`` available. This server type presents the
-same view to all users who visit the site. For example, if you were to run the following
-code:
-
-.. code-block::
-
- from idom import component, hooks, html, run
- from idom.server.sanic import SharedClientStateServer
-
-
- @component
- def Slider():
- value, set_value = hooks.use_state(50)
- return html.input({"type": "range", "min": 1, "max": 100, "value": value})
-
-
- run(Slider, server_type=SharedClientStateServer)
-
-Two clients could see the slider and see a synchronized view of it. That is, when one
-client moved the slider, the other would see the slider update without their action.
-This might look similar to the video below:
-
-.. image:: _static/shared-client-state-server-slider.gif
-
-Presently the following server implementations support the ``SharedClientStateServer``:
-
-- :func:`idom.server.fastapi.SharedClientStateServer`
-- :func:`idom.server.sanic.SharedClientStateServer`
-
-.. note::
-
- If you need to, your can :ref:`write your own server implementation `.
-
-Common Server Settings
-----------------------
-
-Each server implementation has its own high-level settings that are defined by its
-respective ``Config`` (a typed dictionary). As a general rule, these ``Config`` types
-expose the same options across implementations. These configuration dictionaries can
-then be passed to the ``run()`` function via the ``config`` parameter:
-
-.. code-block::
-
- from idom import run, component, html
- from idom.server.sanic import PerClientStateServer, Config
-
-
- @component
- def App():
- return html.h1(f"Hello, World!")
-
-
- server_config = Config(
- cors=False,
- url_prefix="",
- serve_static_files=True,
- redirect_root_to_index=True,
- )
-
- run(App, server_type=PerClientStateServer, config=server_config)
-
-Here's the list of available configuration types:
-
-- :class:`idom.server.fastapi.Config`
-- :class:`idom.server.sanic.Config`
-- :class:`idom.server.flask.Config`
-- :class:`idom.server.tornado.Config`
-
-
-Specific Server Settings
-------------------------
-
-The ``Config`` :ref:`described above ` is meant to be an
-implementation agnostic - all ``Config`` objects support a similar set of options.
-However, there are inevitably cases where you need to set up your chosen server using
-implementation specific details. For instance, you might want to add an extra route to
-the server your using in order to provide extra resources to your application.
-
-Doing this kind of set up can be achieved by passing in an instance of your chosen
-server implementation into the ``app`` parameter of the ``run()`` function. To
-illustrate, if I'm making my application with ``sanic`` and I want to add an extra route
-I would do the following:
-
-.. code-block::
-
- from sanic import Sanic
- from idom import component, html, run
- from idom.server.sanic import PerClientStateServer
-
- app = Sanic(__name__)
-
- # these are implementation specific settings only known to `sanic` servers
- app.config.REQUEST_TIMEOUT = 60
- app.config.RESPONSE_TIMEOUT = 60
-
-
- @component
- def SomeView():
- return html.form({"action": })
-
-
- run(SomeView, server_type=PerClientStateServer, app=app)
-
-
-Add to an Existing Web Server
------------------------------
-
-If you're already serving an application with one of the supported web servers listed
-above, you can add an IDOM to them as a server extension. Instead of using the ``run()``
-function, you'll instantiate one of IDOM's server implementations by passing it an
-instance of your existing application:
-
-.. code-block::
-
- from sanic import Sanic
-
- from idom import component, html
- from idom.server.sanic import PerClientStateServer, Config
-
- existing_app = Sanic(__name__)
-
-
- @component
- def IdomView():
- return html.h1("This is an IDOM App")
-
-
- PerClientStateServer(IdomView, app=existing_app, config=Config(url_prefix="app"))
-
- existing_app.run(host="127.0.0.1", port=8000)
-
-To test that everything is working, you should be able to navigate to
-``https://127.0.0.1:8000/app`` where you should see the results from ``IdomView``.
-
-
-Embed in an Existing Webpage
-----------------------------
-
-IDOM provides a Javascript client called ``idom-client-react`` that can be used to embed
-IDOM views within an existing applications. This is actually how the interactive
-examples throughout this documentation have been created. You can try this out by
-embedding one the examples from this documentation into your own webpage:
-
-.. tab-set::
-
- .. tab-item:: HTML
-
- .. literalinclude:: _static/embed-doc-ex.html
- :language: html
-
- .. tab-item:: âļī¸ Result
-
- .. raw:: html
- :file: _static/embed-doc-ex.html
-
-.. note::
-
- For more information on how to use the client see the :ref:`Javascript API`
- reference. Or if you need to, your can :ref:`write your own server implementation
- `.
-
-As mentioned though, this is connecting to the server that is hosting this
-documentation. If you want to connect to a view from your own server, you'll need to
-change the URL above to one you provide. One way to do this might be to :ref:`add to an
-existing web server`. Another would be to run IDOM in an adjacent web server instance
-that you coordinate with something like `NGINX `__. For the sake
-of simplicity, we'll assume you do something similar to the following in an existing
-Python app:
-
-.. tab-set::
-
- .. tab-item:: main.py
-
- .. literalinclude:: _static/embed-idom-view/main.py
- :language: python
-
- .. tab-item:: index.html
-
- .. literalinclude:: _static/embed-idom-view/index.html
- :language: html
-
-After running ``python main.py``, you should be able to navigate to
-``http://127.0.0.1:8000/index.html`` and see:
-
-.. card::
- :text-align: center
-
- .. image:: _static/embed-idom-view/screenshot.png
- :width: 500px
-
diff --git a/docs/source/adding-interactivity/components-with-state/_examples/adding_state_variable/data.json b/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/data.json
similarity index 97%
rename from docs/source/adding-interactivity/components-with-state/_examples/adding_state_variable/data.json
rename to docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/data.json
index b2a2ba966..b1315912d 100644
--- a/docs/source/adding-interactivity/components-with-state/_examples/adding_state_variable/data.json
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/data.json
@@ -44,7 +44,7 @@
{
"name": "Terracotta Army",
"artist": "Unknown Artist",
- "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consited of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
+ "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
"alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
},
diff --git a/docs/source/adding-interactivity/components-with-state/_examples/adding_state_variable/app.py b/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py
similarity index 81%
rename from docs/source/adding-interactivity/components-with-state/_examples/adding_state_variable/app.py
rename to docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py
index 5a96d2f03..a919a2354 100644
--- a/docs/source/adding-interactivity/components-with-state/_examples/adding_state_variable/app.py
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/adding_state_variable/main.py
@@ -1,8 +1,7 @@
import json
from pathlib import Path
-from idom import component, hooks, html, run
-
+from reactpy import component, hooks, html, run
HERE = Path(__file__)
DATA_PATH = HERE.parent / "data.json"
@@ -25,9 +24,9 @@ def handle_click(event):
url = sculpture["url"]
return html.div(
- html.button({"onClick": handle_click}, "Next"),
+ html.button({"on_click": handle_click}, "Next"),
html.h2(name, " by ", artist),
- html.p(f"({bounded_index + 1} or {len(sculpture_data)})"),
+ html.p(f"({bounded_index + 1} of {len(sculpture_data)})"),
html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
html.p(description),
)
diff --git a/docs/source/adding-interactivity/components-with-state/_examples/isolated_state/data.json b/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/data.json
similarity index 97%
rename from docs/source/adding-interactivity/components-with-state/_examples/isolated_state/data.json
rename to docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/data.json
index b2a2ba966..b1315912d 100644
--- a/docs/source/adding-interactivity/components-with-state/_examples/isolated_state/data.json
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/data.json
@@ -44,7 +44,7 @@
{
"name": "Terracotta Army",
"artist": "Unknown Artist",
- "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consited of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
+ "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
"alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
},
diff --git a/docs/source/adding-interactivity/components-with-state/_examples/isolated_state/app.py b/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py
similarity index 84%
rename from docs/source/adding-interactivity/components-with-state/_examples/isolated_state/app.py
rename to docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py
index 08a53d1c6..d07b87140 100644
--- a/docs/source/adding-interactivity/components-with-state/_examples/isolated_state/app.py
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/isolated_state/main.py
@@ -1,8 +1,7 @@
import json
from pathlib import Path
-from idom import component, hooks, html, run
-
+from reactpy import component, hooks, html, run
HERE = Path(__file__)
DATA_PATH = HERE.parent / "data.json"
@@ -29,14 +28,14 @@ def handle_more_click(event):
url = sculpture["url"]
return html.div(
- html.button({"onClick": handle_next_click}, "Next"),
+ html.button({"on_click": handle_next_click}, "Next"),
html.h2(name, " by ", artist),
html.p(f"({bounded_index + 1} or {len(sculpture_data)})"),
html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
html.div(
html.button(
- {"onClick": handle_more_click},
- f"{'Show' if show_more else 'Hide'} details",
+ {"on_click": handle_more_click},
+ f"{('Show' if show_more else 'Hide')} details",
),
(html.p(description) if show_more else ""),
),
diff --git a/docs/source/adding-interactivity/components-with-state/_examples/multiple_state_variables/data.json b/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/data.json
similarity index 97%
rename from docs/source/adding-interactivity/components-with-state/_examples/multiple_state_variables/data.json
rename to docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/data.json
index b2a2ba966..b1315912d 100644
--- a/docs/source/adding-interactivity/components-with-state/_examples/multiple_state_variables/data.json
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/data.json
@@ -44,7 +44,7 @@
{
"name": "Terracotta Army",
"artist": "Unknown Artist",
- "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consited of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
+ "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
"alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
},
diff --git a/docs/source/adding-interactivity/components-with-state/_examples/multiple_state_variables/app.py b/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py
similarity index 81%
rename from docs/source/adding-interactivity/components-with-state/_examples/multiple_state_variables/app.py
rename to docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py
index 3e7f7bde4..87f9651be 100644
--- a/docs/source/adding-interactivity/components-with-state/_examples/multiple_state_variables/app.py
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/multiple_state_variables/main.py
@@ -1,8 +1,7 @@
import json
from pathlib import Path
-from idom import component, hooks, html, run
-
+from reactpy import component, hooks, html, run
HERE = Path(__file__)
DATA_PATH = HERE.parent / "data.json"
@@ -29,14 +28,14 @@ def handle_more_click(event):
url = sculpture["url"]
return html.div(
- html.button({"onClick": handle_next_click}, "Next"),
+ html.button({"on_click": handle_next_click}, "Next"),
html.h2(name, " by ", artist),
html.p(f"({bounded_index + 1} or {len(sculpture_data)})"),
html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
html.div(
html.button(
- {"onClick": handle_more_click},
- f"{'Show' if show_more else 'Hide'} details",
+ {"on_click": handle_more_click},
+ f"{('Show' if show_more else 'Hide')} details",
),
(html.p(description) if show_more else ""),
),
diff --git a/docs/source/adding-interactivity/components-with-state/_examples/when_variables_arent_enough/data.json b/docs/source/guides/adding-interactivity/components-with-state/_examples/when_variables_are_not_enough/data.json
similarity index 97%
rename from docs/source/adding-interactivity/components-with-state/_examples/when_variables_arent_enough/data.json
rename to docs/source/guides/adding-interactivity/components-with-state/_examples/when_variables_are_not_enough/data.json
index b2a2ba966..b1315912d 100644
--- a/docs/source/adding-interactivity/components-with-state/_examples/when_variables_arent_enough/data.json
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/when_variables_are_not_enough/data.json
@@ -44,7 +44,7 @@
{
"name": "Terracotta Army",
"artist": "Unknown Artist",
- "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consited of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
+ "description": "The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.",
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg/1920px-2015-09-22-081415_-_Terrakotta-Armee%2C_Grosse_Halle.jpg",
"alt": "12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor."
},
diff --git a/docs/source/adding-interactivity/components-with-state/_examples/when_variables_arent_enough/app.py b/docs/source/guides/adding-interactivity/components-with-state/_examples/when_variables_are_not_enough/main.py
similarity index 89%
rename from docs/source/adding-interactivity/components-with-state/_examples/when_variables_arent_enough/app.py
rename to docs/source/guides/adding-interactivity/components-with-state/_examples/when_variables_are_not_enough/main.py
index f8679cbfc..c617586de 100644
--- a/docs/source/adding-interactivity/components-with-state/_examples/when_variables_arent_enough/app.py
+++ b/docs/source/guides/adding-interactivity/components-with-state/_examples/when_variables_are_not_enough/main.py
@@ -7,7 +7,7 @@
import json
from pathlib import Path
-from idom import component, html, run
+from reactpy import component, html, run
HERE = Path(__file__)
@@ -31,7 +31,7 @@ def handle_click(event):
url = sculpture["url"]
return html.div(
- html.button({"onClick": handle_click}, "Next"),
+ html.button({"on_click": handle_click}, "Next"),
html.h2(name, " by ", artist),
html.p(f"({bounded_index + 1} or {len(sculpture_data)})"),
html.img({"src": url, "alt": alt, "style": {"height": "200px"}}),
diff --git a/docs/source/adding-interactivity/components-with-state/index.rst b/docs/source/guides/adding-interactivity/components-with-state/index.rst
similarity index 81%
rename from docs/source/adding-interactivity/components-with-state/index.rst
rename to docs/source/guides/adding-interactivity/components-with-state/index.rst
index 2542d97bb..f8235ac0d 100644
--- a/docs/source/adding-interactivity/components-with-state/index.rst
+++ b/docs/source/guides/adding-interactivity/components-with-state/index.rst
@@ -5,7 +5,7 @@ Components often need to change whatâs on the screen as a result of an interac
example, typing into the form should update the input field, clicking ânextâ on an image
carousel should change which image is displayed, clicking âbuyâ should put a product in
the shopping cart. Components need to ârememberâ things like the current input value,
-the current image, the shopping cart. In IDOM, this kind of component-specific memory is
+the current image, the shopping cart. In ReactPy, this kind of component-specific memory is
called state.
@@ -16,13 +16,13 @@ Below is a gallery of images about sculpture. Clicking the "Next" button should
increment the ``index`` and, as a result, change what image is displayed. However, this
does not work:
-.. idom:: _examples/when_variables_arent_enough
+.. reactpy:: _examples/when_variables_are_not_enough
.. note::
Try clicking the button to see that it does not cause a change.
-After clicking "Next", if you check the server logs, you'll discover an
+After clicking "Next", if you check your web server's logs, you'll discover an
``UnboundLocalError`` error. It turns out that in this case, the ``index = index + 1``
statement is similar to `trying to set global variables
`__.
@@ -36,13 +36,13 @@ that still wouldn't fix the underlying problems:
destroyed when it updates.
2. **Changes to local variables do not cause components to re-render** - there's no way
- for IDOM to observe when these variables change. Thus IDOM is not aware that
+ for ReactPy to observe when these variables change. Thus ReactPy is not aware that
something has changed and that a re-render should take place.
-To address these problems, IDOM provides the :func:`~idom.core.hooks.use_state` "hook"
+To address these problems, ReactPy provides the :func:`~reactpy.core.hooks.use_state` "hook"
which provides:
-1. A **state variable** whose data is retained aross renders.
+1. A **state variable** whose data is retained across renders.
2. A **state setter** function that can be used to update that variable and trigger a
render.
@@ -51,12 +51,12 @@ which provides:
Adding State to Components
--------------------------
-To create a state variable and state setter with :func:`~idom.core.hooks.use_state` hook
+To create a state variable and state setter with :func:`~reactpy.core.hooks.use_state` hook
as described above, we'll begin by importing it:
.. testcode::
- from idom import use_state
+ from reactpy import use_state
Then we'll make the following changes to our code :ref:`from before `:
@@ -84,16 +84,16 @@ After making those changes we should get:
We'll talk more about what this is doing :ref:`shortly `, but for
now let's just verify that this does in fact fix the problems from before:
-.. idom:: _examples/adding_state_variable
+.. reactpy:: _examples/adding_state_variable
Your First Hook
---------------
-In IDOM, ``use_state``, as well as any other function whose name starts with ``use``, is
-called a "hook". These are special functions that should only be called while IDOM is
+In ReactPy, ``use_state``, as well as any other function whose name starts with ``use``, is
+called a "hook". These are special functions that should only be called while ReactPy is
:ref:`rendering `. They let you "hook into" the different
-capabilities of IDOM's components of which ``use_state`` is just one (well get into the
+capabilities of ReactPy's components of which ``use_state`` is just one (well get into the
other :ref:`later `).
While hooks are just normal functions, but it's helpful to think of them as
@@ -107,7 +107,7 @@ words, you'll "use" hooks at the top of your component in the same way you might
Introduction to ``use_state``
-----------------------------
-When you call :func:`~idom.core.hooks.use_state` inside the body of a component's render
+When you call :func:`~reactpy.core.hooks.use_state` inside the body of a component's render
function, you're declaring that this component needs to remember something. That
"something" which needs to be remembered, is known as **state**. So when we look at an
assignment expression like the one below
@@ -130,7 +130,7 @@ Thus, in this example:
of the component.
The convention is that, if you name your state variable ``thing``, your state setter
-should be named ``set_thing``. While you could name them anything you want, adhereing to
+should be named ``set_thing``. While you could name them anything you want, adhering to
the convention makes things easier to understand across projects.
----
@@ -151,12 +151,12 @@ below highlights a line of code where something of interest occurs:
Initial render
- .. literalinclude:: _examples/adding_state_variable/app.py
+ .. literalinclude:: _examples/adding_state_variable/main.py
:lines: 12-33
:emphasize-lines: 2
At this point, we've just begun to render the ``Gallery`` component. As yet,
- IDOM is not aware that this component has any state or what view it will
+ ReactPy is not aware that this component has any state or what view it will
display. This will change in a moment though when we move to the next line...
.. tab-item:: 2
@@ -165,11 +165,11 @@ below highlights a line of code where something of interest occurs:
Initial state declaration
- .. literalinclude:: _examples/adding_state_variable/app.py
+ .. literalinclude:: _examples/adding_state_variable/main.py
:lines: 12-33
:emphasize-lines: 3
- The ``Gallery`` component has just declared some state. IDOM now knows that it
+ The ``Gallery`` component has just declared some state. ReactPy now knows that it
must remember the ``index`` and trigger an update of this component when
``set_index`` is called. Currently the value of ``index`` is ``0`` as per the
default value given to ``use_state``. Thus, the resulting view will display
@@ -181,7 +181,7 @@ below highlights a line of code where something of interest occurs:
Define event handler
- .. literalinclude:: _examples/adding_state_variable/app.py
+ .. literalinclude:: _examples/adding_state_variable/main.py
:lines: 12-33
:emphasize-lines: 5
@@ -196,7 +196,7 @@ below highlights a line of code where something of interest occurs:
Return the view
- .. literalinclude:: _examples/adding_state_variable/app.py
+ .. literalinclude:: _examples/adding_state_variable/main.py
:lines: 12-33
:emphasize-lines: 16
@@ -212,11 +212,11 @@ below highlights a line of code where something of interest occurs:
User interaction
- .. literalinclude:: _examples/adding_state_variable/app.py
+ .. literalinclude:: _examples/adding_state_variable/main.py
:lines: 12-33
:emphasize-lines: 5
- A user has just clicked the button đąī¸! IDOM has sent information about the event
+ A user has just clicked the button đąī¸! ReactPy has sent information about the event
to the ``handle_click`` function and it is about to execute. In a moment we will
update the state of this component and schedule a re-render.
@@ -226,11 +226,11 @@ below highlights a line of code where something of interest occurs:
New state is set
- .. literalinclude:: _examples/adding_state_variable/app.py
+ .. literalinclude:: _examples/adding_state_variable/main.py
:lines: 12-33
:emphasize-lines: 6
- We've just now told IDOM that we want to update the state of our ``Gallery`` and
+ We've just now told ReactPy that we want to update the state of our ``Gallery`` and
that it needs to be re-rendered. More specifically, we are incrementing its
``index``, and once ``Gallery`` re-renders the index *will* be ``1``.
Importantly, at this point, the value of ``index`` is still ``0``! This will
@@ -242,11 +242,11 @@ below highlights a line of code where something of interest occurs:
Next render begins
- .. literalinclude:: _examples/adding_state_variable/app.py
+ .. literalinclude:: _examples/adding_state_variable/main.py
:lines: 12-33
:emphasize-lines: 2
- The scheduled re-render of ``Gallery`` has just begun. IDOM has now updated its
+ The scheduled re-render of ``Gallery`` has just begun. ReactPy has now updated its
internal state store such that, the next time we call ``use_state`` we will get
back the updated value of ``index``.
@@ -256,12 +256,12 @@ below highlights a line of code where something of interest occurs:
Next state is acquired
- .. literalinclude:: _examples/adding_state_variable/app.py
+ .. literalinclude:: _examples/adding_state_variable/main.py
:lines: 12-33
:emphasize-lines: 3
- With IDOM's state store updated, as we call ``use_state``, instead of returning
- ``0`` for the value of ``index`` as it did before, IDOM now returns the value
+ With ReactPy's state store updated, as we call ``use_state``, instead of returning
+ ``0`` for the value of ``index`` as it did before, ReactPy now returns the value
``1``. With this change the view we display will be altered - instead of
displaying data for the first item in our ``sculpture_data`` list we will now
display information about the second.
@@ -272,7 +272,7 @@ below highlights a line of code where something of interest occurs:
Repeat...
- .. literalinclude:: _examples/adding_state_variable/app.py
+ .. literalinclude:: _examples/adding_state_variable/main.py
:lines: 12-33
From this point on, the steps remain the same. The only difference being the
@@ -293,14 +293,14 @@ below highlights a line of code where something of interest occurs:
Multiple State Declarations
---------------------------
-The powerful thing about hooks like :func:`~idom.core.hooks.use_state` is that you're
+The powerful thing about hooks like :func:`~reactpy.core.hooks.use_state` is that you're
not limited to just one state declaration. You can call ``use_state()`` as many times as
you need to in one component. For example, in the example below we've added a
``show_more`` state variable along with a few other modifications (e.g. renaming
``handle_click``) to make the description for each sculpture optionally displayed. Only
when the user clicks the "Show details" button is this description shown:
-.. idom:: _examples/multiple_state_variables
+.. reactpy:: _examples/multiple_state_variables
It's generally a good idea to define separate state variables if the data they represent
is unrelated. In this case, ``index`` corresponds to what sculpture information is being
@@ -308,7 +308,7 @@ displayed and ``show_more`` is solely concerned with whether the description for
sculpture is shown. Put other way ``index`` is concerned with *what* information is
displayed while ``show_more`` is concerned with *how* it is displayed. Conversely
though, if you have a form with many fields, it probably makes sense to have a single
-objec that holds the data for all the fields rather than an object per-field.
+object that holds the data for all the fields rather than an object per-field.
.. note::
@@ -326,7 +326,7 @@ In this example, the ``Gallery`` component from earlier is rendered twice with n
changes to its logic. Try clicking the buttons inside each of the galleries. Notice that
their state is independent:
-.. idom:: _examples/isolated_state
+.. reactpy:: _examples/isolated_state
:result-is-default-tab:
This is what makes state different from regular variables that you might declare at the
@@ -340,12 +340,12 @@ it. The parent component canât change it. This lets you add state to any compo
remove it without impacting the rest of the components.
.. card::
- :link: /managing-state/shared-component-state
+ :link: /guides/managing-state/sharing-component-state/index
:link-type: doc
:octicon:`book` Read More
^^^^^^^^^^^^^^^^^^^^^^^^^
What if you wanted both galleries to keep their states in sync? The right way to do
- it in IDOM is to remove state from child components and add it to their closest
+ it in ReactPy is to remove state from child components and add it to their closest
shared parent.
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/_examples/dict_remove.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/dict_remove.py
similarity index 77%
rename from docs/source/adding-interactivity/dangers-of-mutability/_examples/dict_remove.py
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/dict_remove.py
index cf1955301..6c3c783da 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/_examples/dict_remove.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/dict_remove.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -26,29 +26,29 @@ def handle_click(event):
return handle_click
return html.div(
- html.button({"onClick": handle_add_click}, "add term"),
+ html.button({"on_click": handle_add_click}, "add term"),
html.label(
"Term: ",
- html.input({"value": term_to_add, "onChange": handle_term_to_add_change}),
+ html.input({"value": term_to_add, "on_change": handle_term_to_add_change}),
),
html.label(
"Definition: ",
html.input(
{
"value": definition_to_add,
- "onChange": handle_definition_to_add_change,
+ "on_change": handle_definition_to_add_change,
}
),
),
html.hr(),
[
html.div(
+ {"key": term},
html.button(
- {"onClick": make_delete_click_handler(term)}, "delete term"
+ {"on_click": make_delete_click_handler(term)}, "delete term"
),
html.dt(term),
html.dd(definition),
- key=term,
)
for term, definition in all_terms.items()
],
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/_examples/dict_update.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/dict_update.py
similarity index 68%
rename from docs/source/adding-interactivity/dangers-of-mutability/_examples/dict_update.py
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/dict_update.py
index 9a2aad5e6..32dd4073a 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/_examples/dict_update.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/dict_update.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -24,20 +24,18 @@ def handle_email_change(event):
html.label(
"First name: ",
html.input(
- {"value": person["first_name"], "onChange": handle_first_name_change},
+ {"value": person["first_name"], "on_change": handle_first_name_change}
),
),
html.label(
- "First name: ",
+ "Last name: ",
html.input(
- {"value": person["last_name"], "onChange": handle_last_name_change},
+ {"value": person["last_name"], "on_change": handle_last_name_change}
),
),
html.label(
- "First name: ",
- html.input(
- {"value": person["email"], "onChange": handle_email_change},
- ),
+ "Email: ",
+ html.input({"value": person["email"], "on_change": handle_email_change}),
),
html.p(f"{person['first_name']} {person['last_name']} {person['email']}"),
)
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/_examples/list_insert.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_insert.py
similarity index 65%
rename from docs/source/adding-interactivity/dangers-of-mutability/_examples/list_insert.py
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_insert.py
index 374198f45..1f4072e0b 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/_examples/list_insert.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_insert.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -16,9 +16,9 @@ def handle_click(event):
return html.div(
html.h1("Inspiring sculptors:"),
- html.input({"value": artist_to_add, "onChange": handle_change}),
- html.button({"onClick": handle_click}, "add"),
- html.ul([html.li(name, key=name) for name in artists]),
+ html.input({"value": artist_to_add, "on_change": handle_change}),
+ html.button({"on_click": handle_click}, "add"),
+ html.ul([html.li({"key": name}, name) for name in artists]),
)
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py
similarity index 55%
rename from docs/source/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py
index 32a0e47c0..3bd2fd601 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_re_order.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -8,16 +8,16 @@ def ArtistList():
)
def handle_sort_click(event):
- set_artists(list(sorted(artists)))
+ set_artists(sorted(artists))
def handle_reverse_click(event):
set_artists(list(reversed(artists)))
return html.div(
html.h1("Inspiring sculptors:"),
- html.button({"onClick": handle_sort_click}, "sort"),
- html.button({"onClick": handle_reverse_click}, "reverse"),
- html.ul([html.li(name, key=name) for name in artists]),
+ html.button({"on_click": handle_sort_click}, "sort"),
+ html.button({"on_click": handle_reverse_click}, "reverse"),
+ html.ul([html.li({"key": name}, name) for name in artists]),
)
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/_examples/list_remove.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_remove.py
similarity index 71%
rename from docs/source/adding-interactivity/dangers-of-mutability/_examples/list_remove.py
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_remove.py
index 170deb2f4..6223284f6 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/_examples/list_remove.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_remove.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -24,14 +24,16 @@ def handle_click(event):
return html.div(
html.h1("Inspiring sculptors:"),
- html.input({"value": artist_to_add, "onChange": handle_change}),
- html.button({"onClick": handle_add_click}, "add"),
+ html.input({"value": artist_to_add, "on_change": handle_change}),
+ html.button({"on_click": handle_add_click}, "add"),
html.ul(
[
html.li(
+ {"key": name},
name,
- html.button({"onClick": make_handle_delete_click(index)}, "delete"),
- key=name,
+ html.button(
+ {"on_click": make_handle_delete_click(index)}, "delete"
+ ),
)
for index, name in enumerate(artists)
]
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/_examples/list_replace.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_replace.py
similarity index 74%
rename from docs/source/adding-interactivity/dangers-of-mutability/_examples/list_replace.py
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_replace.py
index 2fafcfde8..4952b9597 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/_examples/list_replace.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/list_replace.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -15,9 +15,9 @@ def handle_click(event):
return html.ul(
[
html.li(
+ {"key": index},
count,
- html.button({"onClick": make_increment_click_handler(index)}, "+1"),
- key=index,
+ html.button({"on_click": make_increment_click_handler(index)}, "+1"),
)
for index, count in enumerate(counters)
]
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/_examples/moving_dot.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/moving_dot.py
similarity index 79%
rename from docs/source/adding-interactivity/dangers-of-mutability/_examples/moving_dot.py
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/moving_dot.py
index d3edb8590..e5ab54dca 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/_examples/moving_dot.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/moving_dot.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -17,26 +17,26 @@ async def handle_pointer_move(event):
return html.div(
{
- "onPointerMove": handle_pointer_move,
+ "on_pointer_move": handle_pointer_move,
"style": {
"position": "relative",
"height": "200px",
"width": "100%",
- "backgroundColor": "white",
+ "background_color": "white",
},
},
html.div(
{
"style": {
"position": "absolute",
- "backgroundColor": "red",
- "borderRadius": "50%",
+ "background_color": "red",
+ "border_radius": "50%",
"width": "20px",
"height": "20px",
"left": "-10px",
"top": "-10px",
"transform": f"translate({position['x']}px, {position['y']}px)",
- },
+ }
}
),
)
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/_examples/moving_dot_broken.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/moving_dot_broken.py
similarity index 78%
rename from docs/source/adding-interactivity/dangers-of-mutability/_examples/moving_dot_broken.py
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/moving_dot_broken.py
index 90885e7fe..8972ce74e 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/_examples/moving_dot_broken.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/moving_dot_broken.py
@@ -1,6 +1,6 @@
# :linenos:
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -15,26 +15,26 @@ def handle_pointer_move(event):
return html.div(
{
- "onPointerMove": handle_pointer_move,
+ "on_pointer_move": handle_pointer_move,
"style": {
"position": "relative",
"height": "200px",
"width": "100%",
- "backgroundColor": "white",
+ "background_color": "white",
},
},
html.div(
{
"style": {
"position": "absolute",
- "backgroundColor": "red",
- "borderRadius": "50%",
+ "background_color": "red",
+ "border_radius": "50%",
"width": "20px",
"height": "20px",
"left": "-10px",
"top": "-10px",
"transform": f"translate({position['x']}px, {position['y']}px)",
- },
+ }
}
),
)
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/_examples/set_remove.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/set_remove.py
similarity index 82%
rename from docs/source/adding-interactivity/dangers-of-mutability/_examples/set_remove.py
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/set_remove.py
index 4c4905350..abe55a918 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/_examples/set_remove.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/set_remove.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -20,18 +20,18 @@ def handle_click(event):
[
html.div(
{
- "onClick": make_handle_click(index),
+ "on_click": make_handle_click(index),
"style": {
"height": "30px",
"width": "30px",
- "backgroundColor": (
+ "background_color": (
"black" if index in selected_indices else "white"
),
"outline": "1px solid grey",
"cursor": "pointer",
},
- },
- key=index,
+ "key": index,
+ }
)
for index in range(line_size)
],
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/_examples/set_update.py b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/set_update.py
similarity index 79%
rename from docs/source/adding-interactivity/dangers-of-mutability/_examples/set_update.py
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/set_update.py
index 9e1077cb0..27f170a42 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/_examples/set_update.py
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/_examples/set_update.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -17,18 +17,18 @@ def handle_click(event):
[
html.div(
{
- "onClick": make_handle_click(index),
+ "on_click": make_handle_click(index),
"style": {
"height": "30px",
"width": "30px",
- "backgroundColor": (
+ "background_color": (
"black" if index in selected_indices else "white"
),
"outline": "1px solid grey",
"cursor": "pointer",
},
- },
- key=index,
+ "key": index,
+ }
)
for index in range(line_size)
],
diff --git a/docs/source/adding-interactivity/dangers-of-mutability/index.rst b/docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst
similarity index 94%
rename from docs/source/adding-interactivity/dangers-of-mutability/index.rst
rename to docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst
index 80a4e6a04..bcac79e18 100644
--- a/docs/source/adding-interactivity/dangers-of-mutability/index.rst
+++ b/docs/source/guides/adding-interactivity/dangers-of-mutability/index.rst
@@ -2,7 +2,7 @@ Dangers of Mutability
=====================
While state can hold any type of value, you should be careful to avoid directly
-modifying objects that you declare as state with IDOM. In other words, you must not
+modifying objects that you declare as state with ReactPy. In other words, you must not
:ref:`"mutate" ` values which are held as state. Rather, to change
these values you should use new ones or create copies.
@@ -42,8 +42,8 @@ facilitate change:
values are equivalent with the ``==`` or ``!=`` operators.
Thus far, all the values we've been working with have been immutable. These include
-:class:`int`, :class:`float`, :class:`str`, and :class:`bool` values. As a result, we've
-have not had to consider the consiquences of mutations.
+:class:`int`, :class:`float`, :class:`str`, and :class:`bool` values. As a result, we
+have not had to consider the consequences of mutations.
.. _Why Avoid Mutation:
@@ -51,13 +51,13 @@ have not had to consider the consiquences of mutations.
Why Avoid Mutation?
-------------------
-Unfortunately, IDOM does not understand that when a value is mutated, it may have
+Unfortunately, ReactPy does not understand that when a value is mutated, it may have
changed. As a result, mutating values will not trigger re-renders. Thus, you must be
-careful to avoid mutation whenever you want IDOM to re-render a component. For example,
+careful to avoid mutation whenever you want ReactPy to re-render a component. For example,
the intention of the code below is to make the red dot move when you touch or hover over
the preview area. However it doesn't - the dot remains stationary:
-.. idom:: _examples/moving_dot_broken
+.. reactpy:: _examples/moving_dot_broken
The problem is with this section of code:
@@ -68,7 +68,7 @@ The problem is with this section of code:
:lineno-start: 13
This code mutates the ``position`` dictionary from the prior render instead of using the
-state variable's associated state setter. Without calling setter IDOM has no idea that
+state variable's associated state setter. Without calling setter ReactPy has no idea that
the variable's data has been modified. While it can be possible to get away with
mutating state variables, it's highly dicsouraged. Doing so can cause strange and
unpredictable behavior. As a result, you should always treat the data within a state
@@ -80,7 +80,7 @@ call it by passing a *new* dictionary with the values for the next render. Notic
by making these alterations to the code, that the dot now follows your pointer when
you touch or hover over the preview:
-.. idom:: _examples/moving_dot
+.. reactpy:: _examples/moving_dot
.. dropdown:: Local mutation can be alright
@@ -208,7 +208,7 @@ updating user information with a preview of the currently entered data. We can
accomplish this using `"unpacking" `__ with
the ``**`` syntax:
-.. idom:: _examples/dict_update
+.. reactpy:: _examples/dict_update
.. _removing-dictionary-items:
@@ -249,7 +249,7 @@ set of keys. One way to do this is with a dictionary comprehension. The example
shows an interface where you're able to enter a new term and definition. Once added,
you can click a delete button to remove the term and definition:
-.. idom:: _examples/dict_remove
+.. reactpy:: _examples/dict_remove
Working with Lists
@@ -349,7 +349,7 @@ the items we want to append instead. There are several ways to do this for one o
values however it's often simplest to use `"unpacking"
`__ with the ``*`` syntax.
-.. idom:: _examples/list_insert
+.. reactpy:: _examples/list_insert
.. _removing-list-items:
@@ -378,7 +378,7 @@ not quite as clean. You must select the portion the list prior to the item which
be removed (``l[:index]``) and the portion after the item (``l[index + 1:]``) and add
them together:
-.. idom:: _examples/list_remove
+.. reactpy:: _examples/list_remove
.. _replacing-list-items:
@@ -409,7 +409,7 @@ must select the portion before and after the item in question. But this time, in
of adding those two selections together, you must insert that values you want to replace
between them:
-.. idom:: _examples/list_replace
+.. reactpy:: _examples/list_replace
.. _re-ordering-list-items:
@@ -441,7 +441,7 @@ list object, you should use the builtin functions :func:`sorted` and :func:`reve
and pass the resulting iterator into the :class:`list` constructor to create a sorted
or reversed copy of the given list:
-.. idom:: _examples/list_re_order
+.. reactpy:: _examples/list_re_order
Working with Sets
@@ -512,7 +512,7 @@ or operator ``|`` serves as a succinct way to compute the union of two sets. How
you should be careful to not use an in-place assignment with this operator as that will
(counterintuitively) mutate the original set rather than creating a new one.
-.. idom:: _examples/set_update
+.. reactpy:: _examples/set_update
.. _removing-set-items:
@@ -531,7 +531,7 @@ Removing Set Items
s.difference_update(values)
s -= values # "in-place" operators mutate!
- s.symetric_difference_update(values)
+ s.symmetric_difference_update(values)
s ^= values # "in-place" operators mutate!
s.intersection_update(values)
@@ -547,7 +547,7 @@ Removing Set Items
s.difference(values)
s - values
- s.symetric_difference(values)
+ s.symmetric_difference(values)
s ^ values
s.intersection(values)
@@ -558,7 +558,7 @@ methods to return new sets without mutating them. As before when :ref:`adding se
you need to avoid using the inline assignment operators since that will
(counterintuitively) mutate the original set rather than given you a new one:
-.. idom:: _examples/set_remove
+.. reactpy:: _examples/set_remove
Useful Packages
diff --git a/docs/source/adding-interactivity/index.rst b/docs/source/guides/adding-interactivity/index.rst
similarity index 81%
rename from docs/source/adding-interactivity/index.rst
rename to docs/source/guides/adding-interactivity/index.rst
index a09db3f51..af84fab88 100644
--- a/docs/source/adding-interactivity/index.rst
+++ b/docs/source/guides/adding-interactivity/index.rst
@@ -56,15 +56,15 @@ Adding Interactivity
Section 1: Responding to Events
-------------------------------
-IDOM lets you add event handlers to your parts of the interface. This means that you can
+ReactPy lets you add event handlers to your parts of the interface. This means that you can
define synchronous or asynchronous functions that are triggered when a particular user
-interaction occurs like clicking, hovering, of focusing on form inputs, and more.
+interaction occurs like clicking, hovering, focusing on form inputs, and more.
-.. idom:: responding-to-events/_examples/button_prints_message
+.. reactpy:: responding-to-events/_examples/button_prints_message
It may feel weird to define a function within a function like this, but doing so allows
the ``handle_event`` function to access information from within the scope of the
-component. That's important if you want to use any arguments that may have beend passed
+component. That's important if you want to use any arguments that may have been passed
your component in the handler.
.. card::
@@ -85,16 +85,16 @@ Components often need to change whatâs on the screen as a result of an interac
example, typing into the form should update the input field, clicking a âCommentâ button
should bring up a text input field, clicking âBuyâ should put a product in the shopping
cart. Components need to ârememberâ things like the current input value, the current
-image, the shopping cart. In IDOM, this kind of component-specific memory is created and
+image, the shopping cart. In ReactPy, this kind of component-specific memory is created and
updated with a "hook" called ``use_state()`` that creates a **state variable** and
**state setter** respectively:
-.. idom:: components-with-state/_examples/adding_state_variable
+.. reactpy:: components-with-state/_examples/adding_state_variable
-In IDOM, ``use_state``, as well as any other function whose name starts with ``use``, is
-called a "hook". These are special functions that should only be called while IDOM is
+In ReactPy, ``use_state``, as well as any other function whose name starts with ``use``, is
+called a "hook". These are special functions that should only be called while ReactPy is
:ref:`rendering `. They let you "hook into" the different
-capabilities of IDOM's components of which ``use_state`` is just one (well get into the
+capabilities of ReactPy's components of which ``use_state`` is just one (well get into the
other :ref:`later `).
.. card::
@@ -111,7 +111,7 @@ Section 3: State as a Snapshot
------------------------------
As we :ref:`learned earlier `, state setters behave a little
-differently than you might exepct at first glance. Instead of updating your current
+differently than you might expect at first glance. Instead of updating your current
handle on the setter's corresponding variable, it schedules a re-render of the component
which owns the state.
@@ -122,7 +122,7 @@ which owns the state.
set_count(count + 1) # schedule a re-render where count is 1
print(count) # still prints: 0
-This behavior of IDOM means that each render of a component is like taking a snapshot of
+This behavior of ReactPy means that each render of a component is like taking a snapshot of
the UI based on the component's state at that time. Treating state in this way can help
reduce subtle bugs. For instance, in the code below there's a simple chat app with a
message input and recipient selector. The catch is that the message actually gets sent 5
@@ -130,15 +130,15 @@ seconds after the "Send" button is clicked. So what would happen if we changed t
recipient between the time the "Send" button was clicked and the moment the message is
actually sent?
-.. idom:: state-as-a-snapshot/_examples/print_chat_message
+.. reactpy:: state-as-a-snapshot/_examples/print_chat_message
As it turns out, changing the message recipient after pressing send does not change
-where the message ulitmately goes. However, one could imagine a bug where the recipient
+where the message ultimately goes. However, one could imagine a bug where the recipient
of a message is determined at the time the message is sent rather than at the time the
"Send" button it clicked. Thus changing the recipient after pressing send would change
where the message got sent.
-In many cases, IDOM avoids this class of bug entirely because it treats state as a
+In many cases, ReactPy avoids this class of bug entirely because it treats state as a
snapshot.
.. card::
@@ -159,10 +159,10 @@ changes to state only take effect in the next render, not in the current one. Fu
changes to state are batched, calling a particular state setter 3 times won't trigger 3
renders, it will only trigger 1. This means that multiple state assignments are batched
- so long as the event handler is synchronous (i.e. the event handler is not an
-``async`` function), IDOM waits until all the code in an event handler has run before
+``async`` function), ReactPy waits until all the code in an event handler has run before
processing state and starting the next render:
-.. idom:: multiple-state-updates/_examples/set_color_3_times
+.. reactpy:: multiple-state-updates/_examples/set_color_3_times
Sometimes though, you need to update a state variable more than once before the next
render. In these cases, instead of having updates batched, you instead want them to be
@@ -172,7 +172,7 @@ To accomplish this, instead of passing the next state value directly (e.g.
``compute_new_state(old_state)`` to the state setter (e.g.
``set_state(compute_new_state)``):
-.. idom:: multiple-state-updates/_examples/set_state_function
+.. reactpy:: multiple-state-updates/_examples/set_state_function
.. card::
:link: multiple-state-updates/index
@@ -188,17 +188,17 @@ Section 5: Dangers of Mutability
--------------------------------
While state can hold any type of value, you should be careful to avoid directly
-modifying objects that you declare as state with IDOM. In other words, you must not
+modifying objects that you declare as state with ReactPy. In other words, you must not
:ref:`"mutate" ` values which are held as state. Rather, to change
these values you should use new ones or create copies.
-This is because IDOM does not understand that when a value is mutated, it may have
+This is because ReactPy does not understand that when a value is mutated, it may have
changed. As a result, mutating values will not trigger re-renders. Thus, you must be
-careful to avoid mutation whenever you want IDOM to re-render a component. For example,
+careful to avoid mutation whenever you want ReactPy to re-render a component. For example,
instead of mutating dictionaries to update their items you should instead create a
copy that contains the desired changes:
-.. idom:: dangers-of-mutability/_examples/dict_update
+.. reactpy:: dangers-of-mutability/_examples/dict_update
.. card::
:link: dangers-of-mutability/index
diff --git a/docs/source/adding-interactivity/multiple-state-updates/_examples/delay_before_count_updater.py b/docs/source/guides/adding-interactivity/multiple-state-updates/_examples/delay_before_count_updater.py
similarity index 70%
rename from docs/source/adding-interactivity/multiple-state-updates/_examples/delay_before_count_updater.py
rename to docs/source/guides/adding-interactivity/multiple-state-updates/_examples/delay_before_count_updater.py
index 0c000477e..e53c5b1ad 100644
--- a/docs/source/adding-interactivity/multiple-state-updates/_examples/delay_before_count_updater.py
+++ b/docs/source/guides/adding-interactivity/multiple-state-updates/_examples/delay_before_count_updater.py
@@ -1,6 +1,6 @@
import asyncio
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -13,7 +13,7 @@ async def handle_click(event):
return html.div(
html.h1(number),
- html.button({"onClick": handle_click}, "Increment"),
+ html.button({"on_click": handle_click}, "Increment"),
)
diff --git a/docs/source/adding-interactivity/multiple-state-updates/_examples/delay_before_set_count.py b/docs/source/guides/adding-interactivity/multiple-state-updates/_examples/delay_before_set_count.py
similarity index 68%
rename from docs/source/adding-interactivity/multiple-state-updates/_examples/delay_before_set_count.py
rename to docs/source/guides/adding-interactivity/multiple-state-updates/_examples/delay_before_set_count.py
index 024df12e7..bb64724f1 100644
--- a/docs/source/adding-interactivity/multiple-state-updates/_examples/delay_before_set_count.py
+++ b/docs/source/guides/adding-interactivity/multiple-state-updates/_examples/delay_before_set_count.py
@@ -1,6 +1,6 @@
import asyncio
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -13,7 +13,7 @@ async def handle_click(event):
return html.div(
html.h1(number),
- html.button({"onClick": handle_click}, "Increment"),
+ html.button({"on_click": handle_click}, "Increment"),
)
diff --git a/docs/source/adding-interactivity/multiple-state-updates/_examples/set_color_3_times.py b/docs/source/guides/adding-interactivity/multiple-state-updates/_examples/set_color_3_times.py
similarity index 59%
rename from docs/source/adding-interactivity/multiple-state-updates/_examples/set_color_3_times.py
rename to docs/source/guides/adding-interactivity/multiple-state-updates/_examples/set_color_3_times.py
index e755c35b9..59d7d0f20 100644
--- a/docs/source/adding-interactivity/multiple-state-updates/_examples/set_color_3_times.py
+++ b/docs/source/guides/adding-interactivity/multiple-state-updates/_examples/set_color_3_times.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -15,10 +15,11 @@ def handle_reset(event):
return html.div(
html.button(
- {"onClick": handle_click, "style": {"backgroundColor": color}}, "Set Color"
+ {"on_click": handle_click, "style": {"background_color": color}},
+ "Set Color",
),
html.button(
- {"onClick": handle_reset, "style": {"backgroundColor": color}}, "Reset"
+ {"on_click": handle_reset, "style": {"background_color": color}}, "Reset"
),
)
diff --git a/docs/source/adding-interactivity/multiple-state-updates/_examples/set_state_function.py b/docs/source/guides/adding-interactivity/multiple-state-updates/_examples/set_state_function.py
similarity index 74%
rename from docs/source/adding-interactivity/multiple-state-updates/_examples/set_state_function.py
rename to docs/source/guides/adding-interactivity/multiple-state-updates/_examples/set_state_function.py
index ec3193de9..56bbe80e3 100644
--- a/docs/source/adding-interactivity/multiple-state-updates/_examples/set_state_function.py
+++ b/docs/source/guides/adding-interactivity/multiple-state-updates/_examples/set_state_function.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
def increment(old_number):
@@ -17,7 +17,7 @@ def handle_click(event):
return html.div(
html.h1(number),
- html.button({"onClick": handle_click}, "Increment"),
+ html.button({"on_click": handle_click}, "Increment"),
)
diff --git a/docs/source/adding-interactivity/multiple-state-updates/index.rst b/docs/source/guides/adding-interactivity/multiple-state-updates/index.rst
similarity index 87%
rename from docs/source/adding-interactivity/multiple-state-updates/index.rst
rename to docs/source/guides/adding-interactivity/multiple-state-updates/index.rst
index 73524e173..bc3245d7e 100644
--- a/docs/source/adding-interactivity/multiple-state-updates/index.rst
+++ b/docs/source/guides/adding-interactivity/multiple-state-updates/index.rst
@@ -10,21 +10,21 @@ Batched Updates
---------------
As we learned :ref:`previously `, state variables remain fixed
-inside each render as if state were a snapshot taken at the begining of each render.
+inside each render as if state were a snapshot taken at the beginning of each render.
This is why, in the example below, even though it might seem like clicking the
"Increment" button would cause the ``number`` to increase by ``3``, it only does by
``1``:
-.. idom:: ../state-as-a-snapshot/_examples/set_counter_3_times
+.. reactpy:: ../state-as-a-snapshot/_examples/set_counter_3_times
The reason this happens is because, so long as the event handler is synchronous (i.e.
-the event handler is not an ``async`` function), IDOM waits until all the code in an
+the event handler is not an ``async`` function), ReactPy waits until all the code in an
event handler has run before processing state and starting the next render. Thus, it's
the last call to a given state setter that matters. In the example below, even though we
set the color of the button to ``"orange"`` and then ``"pink"`` before ``"blue"``,
-the color does not quickly flash orange and pink before blue - it alway remains blue:
+the color does not quickly flash orange and pink before blue - it always remains blue:
-.. idom:: _examples/set_color_3_times
+.. reactpy:: _examples/set_color_3_times
This behavior let's you make multiple state changes without triggering unnecessary
renders or renders with inconsistent state where only some of the variables have been
@@ -33,13 +33,13 @@ handlers have finished running.
.. note::
- For asynchronous event handlers, IDOM will not render until you ``await`` something.
+ For asynchronous event handlers, ReactPy will not render until you ``await`` something.
As we saw in :ref:`prior examples `, if you introduce
an asynchronous delay to an event handler after changing state, renders may take
place before the remainder of the event handler completes. However, state variables
within handlers, even async ones, always remains static.
-This behavior of IDOM to "batch" state changes that take place inside a single event
+This behavior of ReactPy to "batch" state changes that take place inside a single event
handler, do not extend across event handlers. In other words, distinct events will
always produce distinct renders. To give an example, if clicking a button increments a
counter by one, no matter how fast the user clicks, the view will never jump from 1 to 3
@@ -77,9 +77,9 @@ In our case, ``new_state = old_state + 1``. So we might define:
Which we can use to replace ``set_number(number + 1)`` with ``set_number(increment)``:
-.. idom:: _examples/set_state_function
+.. reactpy:: _examples/set_state_function
-The way to think about how IDOM runs though this series of ``set_state(increment)``
+The way to think about how ReactPy runs though this series of ``set_state(increment)``
calls is to imagine that each one updates the internally managed state with its return
value, then that return value is being passed to the next updater function. Ultimately,
this is functionally equivalent to the following:
@@ -94,14 +94,14 @@ introduced a delay before ``set_number(number + 1)``. What would happen if we cl
the "Increment" button more than once before the delay in the first triggered event
completed?
-.. idom:: _examples/delay_before_set_count
+.. reactpy:: _examples/delay_before_set_count
From an :ref:`earlier lesson `, we learned that introducing
delays do not change the fact that state variables do not change until the next render.
As a result, despite clicking many times before the delay completes, the ``number`` only
increments by one. To solve this we can use updater functions:
-.. idom:: _examples/delay_before_count_updater
+.. reactpy:: _examples/delay_before_count_updater
Now when you click the "Increment" button, each click, though delayed, corresponds to
``number`` being increased. This is because the ``old_number`` in the updater function
diff --git a/docs/source/guides/adding-interactivity/responding-to-events/_examples/audio_player.py b/docs/source/guides/adding-interactivity/responding-to-events/_examples/audio_player.py
new file mode 100644
index 000000000..82826c50d
--- /dev/null
+++ b/docs/source/guides/adding-interactivity/responding-to-events/_examples/audio_player.py
@@ -0,0 +1,21 @@
+import json
+
+import reactpy
+
+
+@reactpy.component
+def PlayDinosaurSound():
+ event, set_event = reactpy.hooks.use_state(None)
+ return reactpy.html.div(
+ reactpy.html.audio(
+ {
+ "controls": True,
+ "on_time_update": lambda e: set_event(e),
+ "src": "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3",
+ }
+ ),
+ reactpy.html.pre(json.dumps(event, indent=2)),
+ )
+
+
+reactpy.run(PlayDinosaurSound)
diff --git a/docs/source/adding-interactivity/responding-to-events/_examples/button_async_handlers.py b/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_async_handlers.py
similarity index 76%
rename from docs/source/adding-interactivity/responding-to-events/_examples/button_async_handlers.py
rename to docs/source/guides/adding-interactivity/responding-to-events/_examples/button_async_handlers.py
index a355f6142..992641e00 100644
--- a/docs/source/adding-interactivity/responding-to-events/_examples/button_async_handlers.py
+++ b/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_async_handlers.py
@@ -1,6 +1,6 @@
import asyncio
-from idom import component, html, run
+from reactpy import component, html, run
@component
@@ -9,7 +9,7 @@ async def handle_event(event):
await asyncio.sleep(delay)
print(message)
- return html.button({"onClick": handle_event}, message)
+ return html.button({"on_click": handle_event}, message)
@component
diff --git a/docs/source/adding-interactivity/responding-to-events/_examples/button_does_nothing.py b/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_does_nothing.py
similarity index 68%
rename from docs/source/adding-interactivity/responding-to-events/_examples/button_does_nothing.py
rename to docs/source/guides/adding-interactivity/responding-to-events/_examples/button_does_nothing.py
index e26e770d2..ea8313263 100644
--- a/docs/source/adding-interactivity/responding-to-events/_examples/button_does_nothing.py
+++ b/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_does_nothing.py
@@ -1,4 +1,4 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
diff --git a/docs/source/adding-interactivity/responding-to-events/_examples/button_handler_as_arg.py b/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_handler_as_arg.py
similarity index 83%
rename from docs/source/adding-interactivity/responding-to-events/_examples/button_handler_as_arg.py
rename to docs/source/guides/adding-interactivity/responding-to-events/_examples/button_handler_as_arg.py
index 4de22a024..e5276bef3 100644
--- a/docs/source/adding-interactivity/responding-to-events/_examples/button_handler_as_arg.py
+++ b/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_handler_as_arg.py
@@ -1,9 +1,9 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
def Button(display_text, on_click):
- return html.button({"onClick": on_click}, display_text)
+ return html.button({"on_click": on_click}, display_text)
@component
diff --git a/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_prints_event.py b/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_prints_event.py
new file mode 100644
index 000000000..38638db4b
--- /dev/null
+++ b/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_prints_event.py
@@ -0,0 +1,12 @@
+from reactpy import component, html, run
+
+
+@component
+def Button():
+ def handle_event(event):
+ print(event)
+
+ return html.button({"on_click": handle_event}, "Click me!")
+
+
+run(Button)
diff --git a/docs/source/adding-interactivity/responding-to-events/_examples/button_prints_message.py b/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_prints_message.py
similarity index 70%
rename from docs/source/adding-interactivity/responding-to-events/_examples/button_prints_message.py
rename to docs/source/guides/adding-interactivity/responding-to-events/_examples/button_prints_message.py
index f5ee69f80..56118a57f 100644
--- a/docs/source/adding-interactivity/responding-to-events/_examples/button_prints_message.py
+++ b/docs/source/guides/adding-interactivity/responding-to-events/_examples/button_prints_message.py
@@ -1,4 +1,4 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
@@ -6,7 +6,7 @@ def PrintButton(display_text, message_text):
def handle_event(event):
print(message_text)
- return html.button({"onClick": handle_event}, display_text)
+ return html.button({"on_click": handle_event}, display_text)
@component
diff --git a/docs/source/adding-interactivity/responding-to-events/_examples/prevent_default_event_actions.py b/docs/source/guides/adding-interactivity/responding-to-events/_examples/prevent_default_event_actions.py
similarity index 70%
rename from docs/source/adding-interactivity/responding-to-events/_examples/prevent_default_event_actions.py
rename to docs/source/guides/adding-interactivity/responding-to-events/_examples/prevent_default_event_actions.py
index 7e8ef9938..d3f0941bd 100644
--- a/docs/source/adding-interactivity/responding-to-events/_examples/prevent_default_event_actions.py
+++ b/docs/source/guides/adding-interactivity/responding-to-events/_examples/prevent_default_event_actions.py
@@ -1,4 +1,4 @@
-from idom import component, event, html, run
+from reactpy import component, event, html, run
@component
@@ -7,7 +7,7 @@ def DoNotChangePages():
html.p("Normally clicking this link would take you to a new page"),
html.a(
{
- "onClick": event(lambda event: None, prevent_default=True),
+ "on_click": event(lambda event: None, prevent_default=True),
"href": "https://google.com",
},
"https://google.com",
diff --git a/docs/source/adding-interactivity/responding-to-events/_examples/stop_event_propagation.py b/docs/source/guides/adding-interactivity/responding-to-events/_examples/stop_event_propagation.py
similarity index 61%
rename from docs/source/adding-interactivity/responding-to-events/_examples/stop_event_propagation.py
rename to docs/source/guides/adding-interactivity/responding-to-events/_examples/stop_event_propagation.py
index e87bae026..41c575042 100644
--- a/docs/source/adding-interactivity/responding-to-events/_examples/stop_event_propagation.py
+++ b/docs/source/guides/adding-interactivity/responding-to-events/_examples/stop_event_propagation.py
@@ -1,4 +1,4 @@
-from idom import component, event, hooks, html, run
+from reactpy import component, event, hooks, html, run
@component
@@ -9,24 +9,28 @@ def DivInDiv():
div_in_div = html.div(
{
- "onClick": lambda event: set_outer_count(outer_count + 1),
- "style": {"height": "100px", "width": "100px", "backgroundColor": "red"},
+ "on_click": lambda event: set_outer_count(outer_count + 1),
+ "style": {"height": "100px", "width": "100px", "background_color": "red"},
},
html.div(
{
- "onClick": event(
+ "on_click": event(
lambda event: set_inner_count(inner_count + 1),
stop_propagation=stop_propagatation,
),
- "style": {"height": "50px", "width": "50px", "backgroundColor": "blue"},
- },
+ "style": {
+ "height": "50px",
+ "width": "50px",
+ "background_color": "blue",
+ },
+ }
),
)
return html.div(
html.button(
- {"onClick": lambda event: set_stop_propagatation(not stop_propagatation)},
- "Toggle Propogation",
+ {"on_click": lambda event: set_stop_propagatation(not stop_propagatation)},
+ "Toggle Propagation",
),
html.pre(f"Will propagate: {not stop_propagatation}"),
html.pre(f"Inner click count: {inner_count}"),
diff --git a/docs/source/adding-interactivity/responding-to-events/index.rst b/docs/source/guides/adding-interactivity/responding-to-events/index.rst
similarity index 77%
rename from docs/source/adding-interactivity/responding-to-events/index.rst
rename to docs/source/guides/adding-interactivity/responding-to-events/index.rst
index 3b3033bf6..89f567ca0 100644
--- a/docs/source/adding-interactivity/responding-to-events/index.rst
+++ b/docs/source/guides/adding-interactivity/responding-to-events/index.rst
@@ -1,7 +1,7 @@
Responding to Events
====================
-IDOM lets you add event handlers to your parts of the interface. These events handlers
+ReactPy lets you add event handlers to your parts of the interface. These events handlers
are functions which can be assigned to a part of a UI such that, when a user iteracts
with the interface, those functions get triggered. Examples of interaction include
clicking, hovering, of focusing on form inputs, and more.
@@ -12,7 +12,7 @@ Adding Event Handlers
To start out we'll just display a button that, for the moment, doesn't do anything:
-.. idom:: _examples/button_does_nothing
+.. reactpy:: _examples/button_does_nothing
To add an event handler to this button we'll do three things:
@@ -20,19 +20,19 @@ To add an event handler to this button we'll do three things:
2. Add logic to ``handle_event`` that will print the ``event`` it receives to the console.
3. Add an ``"onClick": handle_event`` attribute to the ```` element.
-.. idom:: _examples/button_prints_event
+.. reactpy:: _examples/button_prints_event
.. note::
Normally print statements will only be displayed in the terminal where you launched
- IDOM.
+ ReactPy.
It may feel weird to define a function within a function like this, but doing so allows
the ``handle_event`` function to access information from within the scope of the
-component. That's important if you want to use any arguments that may have beend passed
+component. That's important if you want to use any arguments that may have been passed
your component in the handler:
-.. idom:: _examples/button_prints_message
+.. reactpy:: _examples/button_prints_message
With all that said, since our ``handle_event`` function isn't doing that much work, if
we wanted to streamline our component definition, we could pass in our event handler as a
@@ -46,9 +46,9 @@ lambda:
Supported Event Types
---------------------
-Since IDOM's event information comes from React, most the the information (:ref:`with
+Since ReactPy's event information comes from React, most the the information (:ref:`with
some exceptions `) about how React handles events translates
-directly to IDOM. Follow the links below to learn about each category of event:
+directly to ReactPy. Follow the links below to learn about each category of event:
- :ref:`Clipboard Events`
- :ref:`Composition Events`
@@ -77,7 +77,7 @@ generic component definition. This allows the component to focus on the things w
common while still giving its usages customizablity. Consider the case below where we
want to create a generic ``Button`` component that can be used for a variety of purpose:
-.. idom:: _examples/button_handler_as_arg
+.. reactpy:: _examples/button_handler_as_arg
.. _Async Event Handler:
@@ -86,13 +86,13 @@ Async Event Handlers
--------------------
Sometimes event handlers need to execute asynchronous tasks when they are triggered.
-Behind the scenes, IDOM is running an :mod:`asyncio` event loop for just this purpose.
+Behind the scenes, ReactPy is running an :mod:`asyncio` event loop for just this purpose.
By defining your event handler as an asynchronous function instead of a normal
synchronous one. In the layout below we sleep for several seconds before printing out a
message in the first button. However, because the event handler is asynchronous, the
handler for the second button is still able to respond:
-.. idom:: _examples/button_async_handlers
+.. reactpy:: _examples/button_async_handlers
Event Data Serialization
@@ -100,20 +100,20 @@ Event Data Serialization
Not all event data is serialized. The most notable example of this is the lack of a
``target`` key in the dictionary sent back to the handler. Instead, data which is not
-inherhently JSON serializable must be treated on a case-by-case basis. A simple case
+inherently JSON serializable must be treated on a case-by-case basis. A simple case
to demonstrate this is the ``currentTime`` attribute of ``audio`` and ``video``
elements. Normally this would be accessible via ``event.target.currentTime``, but here
it's simply passed in under the key ``currentTime``:
-.. idom:: _examples/audio_player
+.. reactpy:: _examples/audio_player
Client-side Event Behavior
--------------------------
-Because IDOM operates server-side, there are inevitable limitations that prevent it from
+Because ReactPy operates server-side, there are inevitable limitations that prevent it from
achieving perfect parity with all the behaviors of React. With that said, any feature
-that cannot be achieved in Python with IDOM, can be done by creating
+that cannot be achieved in Python with ReactPy, can be done by creating
:ref:`Custom Javascript Components`.
@@ -122,23 +122,23 @@ Preventing Default Event Actions
Instead of calling an ``event.preventDefault()`` method as you would do in React, you
must declare whether to prevent default behavior ahead of time. This can be accomplished
-using the :func:`~idom.core.events.event` decorator and setting ``prevent_default``. For
+using the :func:`~reactpy.core.events.event` decorator and setting ``prevent_default``. For
example, we can stop a link from going to the specified URL:
-.. idom:: _examples/prevent_default_event_actions
+.. reactpy:: _examples/prevent_default_event_actions
Unfortunately this means you cannot conditionally prevent default behavior in response
to event data without writing :ref:`Custom Javascript Components`.
-Stop Event Propogation
+Stop Event Propagation
......................
Similarly to :ref:`preventing default behavior `, you
-can use the :func:`~idom.core.events.event` decorator to prevent events originating in a
+can use the :func:`~reactpy.core.events.event` decorator to prevent events originating in a
child element from propagating to parent elements by setting ``stop_propagation``. In
-the example below we place a red ``div`` inside a parent blue ``div``. When propogation
+the example below we place a red ``div`` inside a parent blue ``div``. When propagation
is turned on, clicking the red element will cause the handler for the outer blue one to
trigger. Conversely, when it's off, only the handler for the red element will trigger.
-.. idom:: _examples/stop_event_propagation
+.. reactpy:: _examples/stop_event_propagation
diff --git a/docs/source/adding-interactivity/state-as-a-snapshot/_examples/delayed_print_after_set.py b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/delayed_print_after_set.py
similarity index 72%
rename from docs/source/adding-interactivity/state-as-a-snapshot/_examples/delayed_print_after_set.py
rename to docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/delayed_print_after_set.py
index 5471616d4..b6295b09f 100644
--- a/docs/source/adding-interactivity/state-as-a-snapshot/_examples/delayed_print_after_set.py
+++ b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/delayed_print_after_set.py
@@ -1,6 +1,6 @@
import asyncio
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -15,7 +15,7 @@ async def handle_click(event):
return html.div(
html.h1(number),
- html.button({"onClick": handle_click}, "Increment"),
+ html.button({"on_click": handle_click}, "Increment"),
)
diff --git a/docs/source/adding-interactivity/state-as-a-snapshot/_examples/print_chat_message.py b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/print_chat_message.py
similarity index 73%
rename from docs/source/adding-interactivity/state-as-a-snapshot/_examples/print_chat_message.py
rename to docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/print_chat_message.py
index 35fbc23fb..ecbad9381 100644
--- a/docs/source/adding-interactivity/state-as-a-snapshot/_examples/print_chat_message.py
+++ b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/print_chat_message.py
@@ -1,6 +1,6 @@
import asyncio
-from idom import component, event, html, run, use_state
+from reactpy import component, event, html, run, use_state
@component
@@ -16,13 +16,14 @@ async def handle_submit(event):
print(f"Sent '{message}' to {recipient}")
return html.form(
- {"onSubmit": handle_submit, "style": {"display": "inline-grid"}},
+ {"on_submit": handle_submit, "style": {"display": "inline-grid"}},
html.label(
+ {},
"To: ",
html.select(
{
"value": recipient,
- "onChange": lambda event: set_recipient(event["target"]["value"]),
+ "on_change": lambda event: set_recipient(event["target"]["value"]),
},
html.option({"value": "Alice"}, "Alice"),
html.option({"value": "Bob"}, "Bob"),
@@ -33,7 +34,7 @@ async def handle_submit(event):
"type": "text",
"placeholder": "Your message...",
"value": message,
- "onChange": lambda event: set_message(event["target"]["value"]),
+ "on_change": lambda event: set_message(event["target"]["value"]),
}
),
html.button({"type": "submit"}, "Send"),
diff --git a/docs/source/adding-interactivity/state-as-a-snapshot/_examples/print_count_after_set.py b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/print_count_after_set.py
similarity index 65%
rename from docs/source/adding-interactivity/state-as-a-snapshot/_examples/print_count_after_set.py
rename to docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/print_count_after_set.py
index 039a261d9..40ee78259 100644
--- a/docs/source/adding-interactivity/state-as-a-snapshot/_examples/print_count_after_set.py
+++ b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/print_count_after_set.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -11,7 +11,7 @@ def handle_click(event):
return html.div(
html.h1(number),
- html.button({"onClick": handle_click}, "Increment"),
+ html.button({"on_click": handle_click}, "Increment"),
)
diff --git a/docs/source/adding-interactivity/state-as-a-snapshot/_examples/send_message.py b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/send_message.py
similarity index 66%
rename from docs/source/adding-interactivity/state-as-a-snapshot/_examples/send_message.py
rename to docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/send_message.py
index 0ceaf8850..4702a7464 100644
--- a/docs/source/adding-interactivity/state-as-a-snapshot/_examples/send_message.py
+++ b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/send_message.py
@@ -1,4 +1,4 @@
-from idom import component, event, html, run, use_state
+from reactpy import component, event, html, run, use_state
@component
@@ -10,7 +10,7 @@ def App():
return html.div(
html.h1("Message sent!"),
html.button(
- {"onClick": lambda event: set_is_sent(False)}, "Send new message?"
+ {"on_click": lambda event: set_is_sent(False)}, "Send new message?"
),
)
@@ -20,12 +20,12 @@ def handle_submit(event):
set_is_sent(True)
return html.form(
- {"onSubmit": handle_submit, "style": {"display": "inline-grid"}},
+ {"on_submit": handle_submit, "style": {"display": "inline-grid"}},
html.textarea(
{
"placeholder": "Your message here...",
"value": message,
- "onChange": lambda event: set_message(event["target"]["value"]),
+ "on_change": lambda event: set_message(event["target"]["value"]),
}
),
html.button({"type": "submit"}, "Send"),
diff --git a/docs/source/adding-interactivity/state-as-a-snapshot/_examples/set_counter_3_times.py b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/set_counter_3_times.py
similarity index 69%
rename from docs/source/adding-interactivity/state-as-a-snapshot/_examples/set_counter_3_times.py
rename to docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/set_counter_3_times.py
index 24801d47b..fe97351af 100644
--- a/docs/source/adding-interactivity/state-as-a-snapshot/_examples/set_counter_3_times.py
+++ b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_examples/set_counter_3_times.py
@@ -1,4 +1,4 @@
-from idom import component, html, run, use_state
+from reactpy import component, html, run, use_state
@component
@@ -12,7 +12,7 @@ def handle_click(event):
return html.div(
html.h1(number),
- html.button({"onClick": handle_click}, "Increment"),
+ html.button({"on_click": handle_click}, "Increment"),
)
diff --git a/docs/source/guides/adding-interactivity/state-as-a-snapshot/_static/direct-state-change.png b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_static/direct-state-change.png
new file mode 100644
index 000000000..cfcd9c87f
Binary files /dev/null and b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_static/direct-state-change.png differ
diff --git a/docs/source/guides/adding-interactivity/state-as-a-snapshot/_static/reactpy-state-change.png b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_static/reactpy-state-change.png
new file mode 100644
index 000000000..b36b0514a
Binary files /dev/null and b/docs/source/guides/adding-interactivity/state-as-a-snapshot/_static/reactpy-state-change.png differ
diff --git a/docs/source/adding-interactivity/state-as-a-snapshot/index.rst b/docs/source/guides/adding-interactivity/state-as-a-snapshot/index.rst
similarity index 85%
rename from docs/source/adding-interactivity/state-as-a-snapshot/index.rst
rename to docs/source/guides/adding-interactivity/state-as-a-snapshot/index.rst
index e38b2226c..a677a3e68 100644
--- a/docs/source/adding-interactivity/state-as-a-snapshot/index.rst
+++ b/docs/source/guides/adding-interactivity/state-as-a-snapshot/index.rst
@@ -9,22 +9,22 @@ parts of the view directly. As an illustration, you may think that when a user c
.. image:: _static/direct-state-change.png
-IDOM works a bit differently though - user interactions cause event handlers to
-:ref:`"set state" ` triggering IDOM to re-render a new
+ReactPy works a bit differently though - user interactions cause event handlers to
+:ref:`"set state" ` triggering ReactPy to re-render a new
version of the view rather then mutating the existing one.
-.. image:: _static/idom-state-change.png
+.. image:: _static/reactpy-state-change.png
-Given this, when IDOM "renders" something, it's as if IDOM has taken a snapshot of the
+Given this, when ReactPy "renders" something, it's as if ReactPy has taken a snapshot of the
UI where all the event handlers, local variables and the view itself were calculated
-using what state was present at the time of that render. Then, when user iteractions
-trigger state setters, IDOM is made away of the newly set state and schedules a
+using what state was present at the time of that render. Then, when user interactions
+trigger state setters, ReactPy is made away of the newly set state and schedules a
re-render. When this subsequent renders occurs it performs all the same calculations as
before, but with this new state.
As we've :ref:`already seen `, state variables are not
like normal variables. Instead, they live outside your components and are managed by
-IDOM. When a component is rendered, IDOM provides the component a snapshot of the state
+ReactPy. When a component is rendered, ReactPy provides the component a snapshot of the state
in that exact moment. As a result, the view returned by that component is itself a
snapshot of the UI at that time.
@@ -42,7 +42,7 @@ Let's experiment with this behaviors of state to see why we should think about i
respect to these "snapshots" in time. Take a look at the example below and try to guess
how it will behave. **What will the count be after you click the "Increment" button?**
-.. idom:: _examples/set_counter_3_times
+.. reactpy:: _examples/set_counter_3_times
Despite the fact that we called ``set_count(count + 1)`` three times, the count only
increments by ``1``! This is perhaps a surprising result, but let's break what's
@@ -68,9 +68,9 @@ above as:
Even though, we called ``set_count`` three times with what might have seemed like
different values, every time we were actually just doing ``set_count(1)`` on each call.
-Only after the event handler returns will IDOM actually perform the next render where
+Only after the event handler returns will ReactPy actually perform the next render where
count is ``1``. When it does, ``number`` will be ``1`` and we'll be able to perform the
-same subtitution as before to see what the next number will be after we click
+same substitution as before to see what the next number will be after we click
"Increment":
.. code-block::
@@ -87,9 +87,9 @@ Given what we :ref:`learned above `, we ought to
to reason about what should happen in the example below. What will be printed when the
"Increment" button is clicked?
-.. idom:: _examples/print_count_after_set
+.. reactpy:: _examples/print_count_after_set
-If we use the same subtitution trick we saw before, we can rewrite these lines:
+If we use the same substitution trick we saw before, we can rewrite these lines:
.. code-block::
@@ -112,7 +112,7 @@ What if we slightly modify this example, by introducing a delay between when we
``set_number`` and when we print? Will this behavior remain the same? To add this delay
we'll use an :ref:`async event handler` and :func:`~asyncio.sleep` for some time:
-.. idom:: _examples/delayed_print_after_set
+.. reactpy:: _examples/delayed_print_after_set
Even though the render completed before the print statement took place, the behavior
remained the same! Despite the fact that the next render took place before the print
@@ -140,9 +140,9 @@ been sent yet, should not impact where the message is ultimately sent. We then n
ask what actually happens. Will it print âYou said Hello to Aliceâ or âYou said Hello to
Bobâ?
-.. idom:: _examples/print_chat_message
+.. reactpy:: _examples/print_chat_message
-As it turns out, the code above matches the user's expectation. This is because IDOM
+As it turns out, the code above matches the user's expectation. This is because ReactPy
keeps the state values fixed within the event handlers defined during a particular
render. As a result, you don't need to worry about whether state has changed while
code in an event handler is running.
diff --git a/docs/source/creating-interfaces/html-with-idom.rst b/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
similarity index 54%
rename from docs/source/creating-interfaces/html-with-idom.rst
rename to docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
index 79d2a6ed0..4a8ba4957 100644
--- a/docs/source/creating-interfaces/html-with-idom.rst
+++ b/docs/source/guides/creating-interfaces/html-with-reactpy/index.rst
@@ -1,20 +1,19 @@
-HTML With IDOM
-==============
+HTML With ReactPy
+=================
-In a typical Python-base web application the resonsibility of defining the view along
+In a typical Python-based web application the responsibility of defining the view along
with its backing data and logic are distributed between a client and server
-respectively. With IDOM, both these tasks are centralized in a single place. This is
+respectively. With ReactPy, both these tasks are centralized in a single place. This is
done by allowing HTML interfaces to be constructed in Python. Take a look at the two
-code examples below. The one on the left shows how to make a basic title and todo list
-using standard HTML, the one of the right uses IDOM in Python, and below is a view of
-what the HTML would look like if displayed:
+code examples below. The first one shows how to make a basic title and todo list using
+standard HTML, the second uses ReactPy in Python, and below is a view of what the HTML
+would look like if displayed:
.. grid:: 1 1 2 2
:margin: 0
:padding: 0
.. grid-item::
- :columns: 6
.. code-block:: html
@@ -25,11 +24,10 @@ what the HTML would look like if displayed:
.. grid-item::
- :columns: 6
.. testcode::
- from idom import html
+ from reactpy import html
html.h1("My Todo List")
html.ul(
@@ -50,13 +48,13 @@ what the HTML would look like if displayed:
-What this shows is that you can recreate the same HTML layouts with IDOM using functions
-from the :mod:`idom.html` module. These function share the same names as their
+What this shows is that you can recreate the same HTML layouts with ReactPy using functions
+from the :mod:`reactpy.html` module. These function share the same names as their
corresponding HTML tags. For instance, the `` `` element above has a similarly named
-:func:`~idom.html.h1` function. With that said, while the code above looks similar, it's
+:func:`~reactpy.html.h1` function. With that said, while the code above looks similar, it's
not very useful because we haven't captured the results from these function calls in a
variable. To do this we need to wrap up the layout above into a single
-:func:`~idom.html.div` and assign it to a variable:
+:func:`~reactpy.html.div` and assign it to a variable:
.. testcode::
@@ -73,49 +71,61 @@ Adding HTML Attributes
----------------------
That's all well and good, but there's more to HTML than just text. What if we wanted to
-display an image? In HTMl we'd use the ` ` element and add attributes to it order
+display an image? In HTMl we'd use the `` `` element and add attributes to it order
to specify a URL to its ``src`` and use some ``style`` to modify and position it:
.. code-block:: html
-In IDOM we add these attributes to elements using dictionaries. There are some notable
-differences though. The biggest being the fact that all names in IDOM use ``camelCase``
-instead of dash-sepearted words. For example, ``margin-left`` becomes ``marginLeft``.
-Additionally, instead of specifying ``style`` using a string, we use a dictionary:
+In ReactPy we add these attributes to elements using a dictionary:
.. testcode::
html.img(
{
"src": "https://picsum.photos/id/237/500/300",
- "style": {"width": "50%", "marginLeft": "25%"},
+ "class_name": "img-fluid",
+ "style": {"width": "50%", "margin_left": "25%"},
"alt": "Billie Holiday",
}
)
.. raw:: html
+
+There are some notable differences. First, all names in ReactPy use ``snake_case`` instead
+of dash-separated words. For example, ``tabindex`` and ``margin-left`` become
+``tab_index`` and ``margin_left`` respectively. Second, instead of using a string to
+specify the ``style`` attribute, we use a dictionary to describe the CSS properties we
+want to apply to an element. This is done to avoid having to escape quotes and other
+characters in the string. Finally, the ``class`` attribute is renamed to ``class_name``
+to avoid conflicting with the ``class`` keyword in Python.
+
+For full list of supported attributes and differences from HTML, see the
+:ref:`HTML Attributes` reference.
----------
.. card::
- :link: /understanding-idom/representing-html
+ :link: /guides/understanding-reactpy/representing-html
:link-type: doc
:octicon:`book` Read More
^^^^^^^^^^^^^^^^^^^^^^^^^
- Dive into the data structures IDOM uses to represent HTML
+ Dive into the data structures ReactPy uses to represent HTML
diff --git a/docs/source/creating-interfaces/index.rst b/docs/source/guides/creating-interfaces/index.rst
similarity index 72%
rename from docs/source/creating-interfaces/index.rst
rename to docs/source/guides/creating-interfaces/index.rst
index 77f9fd4fc..78466eaef 100644
--- a/docs/source/creating-interfaces/index.rst
+++ b/docs/source/guides/creating-interfaces/index.rst
@@ -4,9 +4,9 @@ Creating Interfaces
.. toctree::
:hidden:
- html-with-idom
- your-first-components
- rendering-data
+ html-with-reactpy/index
+ your-first-components/index
+ rendering-data/index
.. dropdown:: :octicon:`bookmark-fill;2em` What You'll Learn
:color: info
@@ -15,39 +15,39 @@ Creating Interfaces
.. grid:: 1 2 2 2
- .. grid-item-card:: :octicon:`code-square` HTML with IDOM
- :link: html-with-idom
+ .. grid-item-card:: :octicon:`code-square` HTML with ReactPy
+ :link: html-with-reactpy/index
:link-type: doc
Construct HTML layouts from the basic units of user interface functionality.
.. grid-item-card:: :octicon:`package` Your First Components
- :link: your-first-components
+ :link: your-first-components/index
:link-type: doc
Define reusable building blocks that it easier to construct complex
interfaces.
.. grid-item-card:: :octicon:`database` Rendering Data
- :link: rendering-data
+ :link: rendering-data/index
:link-type: doc
Use data to organize and render HTML elements and components.
-IDOM is a Python package for making user interfaces (UI). These interfaces are built
-from small elements of functionality like buttons text and images. IDOM allows you to
+ReactPy is a Python package for making user interfaces (UI). These interfaces are built
+from small elements of functionality like buttons text and images. ReactPy allows you to
combine these elements into reusable, nestable :ref:`"components" `. In the sections that follow you'll learn how these UI elements are created
and organized into components. Then, you'll use components to customize and
conditionally display more complex UIs.
-Section 1: HTML with IDOM
--------------------------
+Section 1: HTML with ReactPy
+----------------------------
-In a typical Python-base web application the resonsibility of defining the view along
+In a typical Python-base web application the responsibility of defining the view along
with its backing data and logic are distributed between a client and server
-respectively. With IDOM, both these tasks are centralized in a single place. The most
+respectively. With ReactPy, both these tasks are centralized in a single place. The most
foundational pilar of this capability is formed by allowing HTML interfaces to be
constructed in Python. Let's consider the HTML sample below:
@@ -59,11 +59,11 @@ constructed in Python. Let's consider the HTML sample below:
Share it with the world!
-To recreate the same thing in IDOM you would write:
+To recreate the same thing in ReactPy you would write:
.. code-block::
- from idom import html
+ from reactpy import html
html.div(
html.h1("My Todo List"),
@@ -75,7 +75,7 @@ To recreate the same thing in IDOM you would write:
)
.. card::
- :link: html-with-idom
+ :link: html-with-reactpy/index
:link-type: doc
:octicon:`book` Read More
@@ -87,17 +87,17 @@ To recreate the same thing in IDOM you would write:
Section 2: Your First Components
--------------------------------
-The next building block in our journey with IDOM are components. At their core,
-components are just a normal Python functions that return :ref:`HTML `.
+The next building block in our journey with ReactPy are components. At their core,
+components are just a normal Python functions that return :ref:`HTML `.
The one special thing about them that we'll concern ourselves with now, is that to
create them we need to add an ``@component`` `decorator
`__. To see what this looks like in
practice we'll quickly make a ``Photo`` component:
-.. idom:: _examples/simple_photo
+.. reactpy:: your-first-components/_examples/simple_photo
.. card::
- :link: your-first-components
+ :link: your-first-components/index
:link-type: doc
:octicon:`book` Read More
@@ -112,14 +112,14 @@ Section 3: Rendering Data
The last pillar of knowledge you need before you can start making :ref:`interactive
interfaces ` is the ability to render sections of the UI given a
collection of data. This will require you to understand how elements which are derived
-from data in this way must be orgnized with :ref:`"keys" `.
+from data in this way must be organized with :ref:`"keys" `.
One case where we might want to do this is if items in a todo list come from a list of
data that we want to sort and filter:
-.. idom:: _examples/todo_list_with_keys
+.. reactpy:: rendering-data/_examples/todo_list_with_keys
.. card::
- :link: rendering-data
+ :link: rendering-data/index
:link-type: doc
:octicon:`book` Read More
diff --git a/docs/source/creating-interfaces/_examples/sorted_and_filtered_todo_list.py b/docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py
similarity index 89%
rename from docs/source/creating-interfaces/_examples/sorted_and_filtered_todo_list.py
rename to docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py
index 28708416e..8be2b5f30 100644
--- a/docs/source/creating-interfaces/_examples/sorted_and_filtered_todo_list.py
+++ b/docs/source/guides/creating-interfaces/rendering-data/_examples/sorted_and_filtered_todo_list.py
@@ -1,4 +1,4 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
@@ -6,7 +6,7 @@ def DataList(items, filter_by_priority=None, sort_by_priority=False):
if filter_by_priority is not None:
items = [i for i in items if i["priority"] <= filter_by_priority]
if sort_by_priority:
- items = list(sorted(items, key=lambda i: i["priority"]))
+ items = sorted(items, key=lambda i: i["priority"])
list_item_elements = [html.li(i["text"]) for i in items]
return html.ul(list_item_elements)
diff --git a/docs/source/creating-interfaces/_examples/todo_from_list.py b/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_from_list.py
similarity index 92%
rename from docs/source/creating-interfaces/_examples/todo_from_list.py
rename to docs/source/guides/creating-interfaces/rendering-data/_examples/todo_from_list.py
index 474c78770..85ba1d79d 100644
--- a/docs/source/creating-interfaces/_examples/todo_from_list.py
+++ b/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_from_list.py
@@ -1,4 +1,4 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
diff --git a/docs/source/creating-interfaces/_examples/todo_list_with_keys.py b/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py
similarity index 83%
rename from docs/source/creating-interfaces/_examples/todo_list_with_keys.py
rename to docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py
index 5a173e1e0..8afd2ae55 100644
--- a/docs/source/creating-interfaces/_examples/todo_list_with_keys.py
+++ b/docs/source/guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys.py
@@ -1,4 +1,4 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
@@ -6,8 +6,8 @@ def DataList(items, filter_by_priority=None, sort_by_priority=False):
if filter_by_priority is not None:
items = [i for i in items if i["priority"] <= filter_by_priority]
if sort_by_priority:
- items = list(sorted(items, key=lambda i: i["priority"]))
- list_item_elements = [html.li(i["text"], key=i["id"]) for i in items]
+ items = sorted(items, key=lambda i: i["priority"])
+ list_item_elements = [html.li({"key": i["id"]}, i["text"]) for i in items]
return html.ul(list_item_elements)
diff --git a/docs/source/creating-interfaces/rendering-data.rst b/docs/source/guides/creating-interfaces/rendering-data/index.rst
similarity index 89%
rename from docs/source/creating-interfaces/rendering-data.rst
rename to docs/source/guides/creating-interfaces/rendering-data/index.rst
index d10aed9c3..620426142 100644
--- a/docs/source/creating-interfaces/rendering-data.rst
+++ b/docs/source/guides/creating-interfaces/rendering-data/index.rst
@@ -40,7 +40,7 @@ We could then take this list and "render" it into a series of ```` elements:
.. testcode::
- from idom import html
+ from reactpy import html
list_item_elements = [html.li(text) for text in tasks]
@@ -52,7 +52,7 @@ This list of elements can then be passed into a parent ```` element:
The last thing we have to do is return this from a component:
-.. idom:: _examples/todo_from_list
+.. reactpy:: _examples/todo_from_list
Filtering and Sorting Elements
@@ -76,14 +76,10 @@ priority. Thus, we need to change the data structure we're using to represent ou
]
With this we can now imaging writing some filtering and sorting logic using Python's
-:func:`filter` and :func:`sorted` functions respecitvely. We'll do this by only
+:func:`filter` and :func:`sorted` functions respectively. We'll do this by only
displaying items whose ``priority`` is less than or equal to some ``filter_by_priority``
and then ordering the elements based on the ``priority``:
-.. testcode::
-
- x = 1
-
.. testcode::
filter_by_priority = 1
@@ -105,13 +101,18 @@ and then ordering the elements based on the ``priority``:
We could then add this code to our ``DataList`` component:
-.. idom:: _examples/sorted_and_filtered_todo_list
+.. warning::
+
+ The code below produces a bunch of warnings! Be sure to read the
+ :ref:`next section ` to find out why.
+
+.. reactpy:: _examples/sorted_and_filtered_todo_list
Organizing Items With Keys
--------------------------
-If you run the examples above :ref:`in debug mode ` you'll
+If you run the examples above :ref:`in debug mode ` you'll
see the server log a bunch of errors that look something like:
.. code-block:: text
@@ -135,20 +136,19 @@ structure even further to include a unique ID for each item in our todo list:
{"id": 7, "text": "Read a book", "priority": 1},
]
-Then, as we're constructing our ```` elements we'll pass in a ``key`` argument to
-the element constructor:
+Then, as we're constructing our `` `` elements we'll declare a ``key`` attribute:
.. code-block::
- list_item_elements = [html.li(t["text"], key=t["id"]) for t in tasks]
+ list_item_elements = [html.li({"key": t["id"]}, t["text"]) for t in tasks]
-This ``key`` tells IDOM which `` `` element corresponds to which item of data in our
+This ``key`` tells ReactPy which `` `` element corresponds to which item of data in our
``tasks`` list. This becomes important if the order or number of items in your list can
change. In our case, if we decided to change whether we want to ``filter_by_priority``
or ``sort_by_priority`` the items in our ```` element would change. Given this,
here's how we'd change our component:
-.. idom:: _examples/todo_list_with_keys
+.. reactpy:: _examples/todo_list_with_keys
Keys for Components
@@ -161,7 +161,7 @@ exact same way that it does for standard HTML elements:
.. testcode::
- from idom import component
+ from reactpy import component
@component
@@ -189,7 +189,7 @@ exact same way that it does for standard HTML elements:
.. testcode::
- from idom import component
+ from reactpy import component
@component
def FunctionWithKeyParam(key):
@@ -231,7 +231,7 @@ with the correct UI element.
html.ul([html.li(data["text"], key=data["id"]) for data in data_2]),
)
-.. dropdown:: Keys must be unique amonst siblings
+.. dropdown:: Keys must be unique amongst siblings
:color: danger
Keys must be unique among siblings.
@@ -288,10 +288,10 @@ use for each ``key``?
.. card::
- :link: /understanding-idom/why-idom-needs-keys
+ :link: /guides/understanding-reactpy/why-reactpy-needs-keys
:link-type: doc
:octicon:`book` Read More
^^^^^^^^^^^^^^^^^^^^^^^^^
- Learn about why IDOM needs keys in the first place.
+ Learn about why ReactPy needs keys in the first place.
diff --git a/docs/source/creating-interfaces/_examples/bad_conditional_todo_list.py b/docs/source/guides/creating-interfaces/your-first-components/_examples/bad_conditional_todo_list.py
similarity index 91%
rename from docs/source/creating-interfaces/_examples/bad_conditional_todo_list.py
rename to docs/source/guides/creating-interfaces/your-first-components/_examples/bad_conditional_todo_list.py
index a5de11f77..1ed3268b6 100644
--- a/docs/source/creating-interfaces/_examples/bad_conditional_todo_list.py
+++ b/docs/source/guides/creating-interfaces/your-first-components/_examples/bad_conditional_todo_list.py
@@ -1,4 +1,4 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
diff --git a/docs/source/creating-interfaces/_examples/good_conditional_todo_list.py b/docs/source/guides/creating-interfaces/your-first-components/_examples/good_conditional_todo_list.py
similarity index 79%
rename from docs/source/creating-interfaces/_examples/good_conditional_todo_list.py
rename to docs/source/guides/creating-interfaces/your-first-components/_examples/good_conditional_todo_list.py
index 64d6f6813..cd9ab6fc0 100644
--- a/docs/source/creating-interfaces/_examples/good_conditional_todo_list.py
+++ b/docs/source/guides/creating-interfaces/your-first-components/_examples/good_conditional_todo_list.py
@@ -1,9 +1,9 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
def Item(name, done):
- return html.li(name, "" if done else " â")
+ return html.li(name, " â" if done else "")
@component
diff --git a/docs/source/creating-interfaces/_examples/nested_photos.py b/docs/source/guides/creating-interfaces/your-first-components/_examples/nested_photos.py
similarity index 89%
rename from docs/source/creating-interfaces/_examples/nested_photos.py
rename to docs/source/guides/creating-interfaces/your-first-components/_examples/nested_photos.py
index 4c512b7e6..96f8531d3 100644
--- a/docs/source/creating-interfaces/_examples/nested_photos.py
+++ b/docs/source/guides/creating-interfaces/your-first-components/_examples/nested_photos.py
@@ -1,4 +1,4 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
diff --git a/docs/source/creating-interfaces/_examples/parametrized_photos.py b/docs/source/guides/creating-interfaces/your-first-components/_examples/parametrized_photos.py
similarity index 78%
rename from docs/source/creating-interfaces/_examples/parametrized_photos.py
rename to docs/source/guides/creating-interfaces/your-first-components/_examples/parametrized_photos.py
index aeea41589..665dd8c86 100644
--- a/docs/source/creating-interfaces/_examples/parametrized_photos.py
+++ b/docs/source/guides/creating-interfaces/your-first-components/_examples/parametrized_photos.py
@@ -1,11 +1,11 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
def Photo(alt_text, image_id):
return html.img(
{
- "src": f"https://picsum.photos/id/{image_id}/500/300",
+ "src": f"https://picsum.photos/id/{image_id}/500/200",
"style": {"width": "50%"},
"alt": alt_text,
}
diff --git a/docs/source/creating-interfaces/_examples/simple_photo.py b/docs/source/guides/creating-interfaces/your-first-components/_examples/simple_photo.py
similarity index 83%
rename from docs/source/creating-interfaces/_examples/simple_photo.py
rename to docs/source/guides/creating-interfaces/your-first-components/_examples/simple_photo.py
index c6b92c652..94fa6633f 100644
--- a/docs/source/creating-interfaces/_examples/simple_photo.py
+++ b/docs/source/guides/creating-interfaces/your-first-components/_examples/simple_photo.py
@@ -1,4 +1,4 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
diff --git a/docs/source/creating-interfaces/_examples/todo_list.py b/docs/source/guides/creating-interfaces/your-first-components/_examples/todo_list.py
similarity index 90%
rename from docs/source/creating-interfaces/_examples/todo_list.py
rename to docs/source/guides/creating-interfaces/your-first-components/_examples/todo_list.py
index 4f0053c4b..2ffd09261 100644
--- a/docs/source/creating-interfaces/_examples/todo_list.py
+++ b/docs/source/guides/creating-interfaces/your-first-components/_examples/todo_list.py
@@ -1,4 +1,4 @@
-from idom import component, html, run
+from reactpy import component, html, run
@component
diff --git a/docs/source/guides/creating-interfaces/your-first-components/_examples/wrap_in_div.py b/docs/source/guides/creating-interfaces/your-first-components/_examples/wrap_in_div.py
new file mode 100644
index 000000000..58ed79dd8
--- /dev/null
+++ b/docs/source/guides/creating-interfaces/your-first-components/_examples/wrap_in_div.py
@@ -0,0 +1,13 @@
+from reactpy import component, html, run
+
+
+@component
+def MyTodoList():
+ return html.div(
+ html.h1("My Todo List"),
+ html.img({"src": "https://picsum.photos/id/0/500/300"}),
+ html.ul(html.li("The first thing I need to do is...")),
+ )
+
+
+run(MyTodoList)
diff --git a/docs/source/guides/creating-interfaces/your-first-components/_examples/wrap_in_fragment.py b/docs/source/guides/creating-interfaces/your-first-components/_examples/wrap_in_fragment.py
new file mode 100644
index 000000000..cc54d8b71
--- /dev/null
+++ b/docs/source/guides/creating-interfaces/your-first-components/_examples/wrap_in_fragment.py
@@ -0,0 +1,13 @@
+from reactpy import component, html, run
+
+
+@component
+def MyTodoList():
+ return html._(
+ html.h1("My Todo List"),
+ html.img({"src": "https://picsum.photos/id/0/500/200"}),
+ html.ul(html.li("The first thing I need to do is...")),
+ )
+
+
+run(MyTodoList)
diff --git a/docs/source/creating-interfaces/your-first-components.rst b/docs/source/guides/creating-interfaces/your-first-components/index.rst
similarity index 55%
rename from docs/source/creating-interfaces/your-first-components.rst
rename to docs/source/guides/creating-interfaces/your-first-components/index.rst
index eeee7fa31..00b53d1b7 100644
--- a/docs/source/creating-interfaces/your-first-components.rst
+++ b/docs/source/guides/creating-interfaces/your-first-components/index.rst
@@ -1,10 +1,10 @@
Your First Components
=====================
-As we learned :ref:`earlier ` we can use IDOM to make rich structured
+As we learned :ref:`earlier ` we can use ReactPy to make rich structured
documents out of standard HTML elements. As these documents become larger and more
complex though, working with these tiny UI elements can become difficult. When this
-happens, IDOM allows you to group these elements together info "components". These
+happens, ReactPy allows you to group these elements together info "components". These
components can then be reused throughout your application.
@@ -16,9 +16,9 @@ component you just need to add a ``@component`` `decorator
`__ to a function. Functions
decorator in this way are known as **render function** and, by convention, we name them
like classes - with ``CamelCase``. So consider what we would do if we wanted to write,
-and then :ref:`display ` a ``Photo`` component:
+and then :ref:`display ` a ``Photo`` component:
-.. idom:: _examples/simple_photo
+.. reactpy:: _examples/simple_photo
.. warning::
@@ -39,7 +39,64 @@ can define a "parent" ``Gallery`` component that returns one or more ``Profile``
components. This is part of what makes components so powerful - you can define a
component once and use it wherever and however you need to:
-.. idom:: _examples/nested_photos
+.. reactpy:: _examples/nested_photos
+
+
+Return a Single Root Element
+----------------------------
+
+Components must return a "single root element". That one root element may have children,
+but you cannot for example, return a list of element from a component and expect it to
+be rendered correctly. If you want to return multiple elements you must wrap them in
+something like a :func:`html.div `:
+
+.. reactpy:: _examples/wrap_in_div
+
+If don't want to add an extra ``div`` you can use a "fragment" instead with the
+:func:`html._ ` function:
+
+.. reactpy:: _examples/wrap_in_fragment
+
+Fragments allow you to group elements together without leaving any trace in the UI. For
+example, the first code sample written with ReactPy will produce the second HTML code
+block:
+
+.. grid:: 1 2 2 2
+ :margin: 0
+ :padding: 0
+
+ .. grid-item::
+
+ .. testcode::
+
+ from reactpy import html
+
+ html.ul(
+ html._(
+ html.li("Group 1 Item 1"),
+ html.li("Group 1 Item 2"),
+ html.li("Group 1 Item 3"),
+ ),
+ html._(
+ html.li("Group 2 Item 1"),
+ html.li("Group 2 Item 2"),
+ html.li("Group 2 Item 3"),
+ )
+ )
+
+ .. grid-item::
+
+ .. code-block:: html
+
+
+ Group 1 Item 1
+ Group 1 Item 2
+ Group 1 Item 3
+ Group 2 Item 1
+ Group 2 Item 2
+ Group 2 Item 3
+
+
Parametrizing Components
@@ -50,7 +107,7 @@ parent components to pass information to child components. Where standard HTML e
are parametrized by dictionaries, since components behave like typical functions you can
give them positional and keyword arguments as you would normally:
-.. idom:: _examples/parametrized_photos
+.. reactpy:: _examples/parametrized_photos
Conditional Rendering
@@ -61,17 +118,17 @@ conditions. Let's imagine that we had a basic todo list where only some of the i
have been completed. Below we have a basic implementation for such a list except that
the ``Item`` component doesn't change based on whether it's ``done``:
-.. idom:: _examples/todo_list
+.. reactpy:: _examples/todo_list
Let's imagine that we want to add a â to the items which have been marked ``done=True``.
One way to do this might be to write an ``if`` statement where we return one ``li``
element if the item is ``done`` and a different one if it's not:
-.. idom:: _examples/bad_conditional_todo_list
+.. reactpy:: _examples/bad_conditional_todo_list
As you can see this accomplishes our goal! However, notice how similar ``html.li(name, "
â")`` and ``html.li(name)`` are. While in this case it isn't especially harmful, we
could make our code a little easier to read and maintain by using an "inline" ``if``
statement.
-.. idom:: _examples/good_conditional_todo_list
+.. reactpy:: _examples/good_conditional_todo_list
diff --git a/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py b/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py
new file mode 100644
index 000000000..3ad4dac5b
--- /dev/null
+++ b/docs/source/guides/escape-hatches/_examples/material_ui_button_no_action.py
@@ -0,0 +1,16 @@
+from reactpy import component, run, web
+
+mui = web.module_from_template(
+ "react@^17.0.0",
+ "@material-ui/core@4.12.4",
+ fallback="â",
+)
+Button = web.export(mui, "Button")
+
+
+@component
+def HelloWorld():
+ return Button({"color": "primary", "variant": "contained"}, "Hello World!")
+
+
+run(HelloWorld)
diff --git a/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py b/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py
new file mode 100644
index 000000000..3fc684005
--- /dev/null
+++ b/docs/source/guides/escape-hatches/_examples/material_ui_button_on_click.py
@@ -0,0 +1,30 @@
+import json
+
+import reactpy
+
+mui = reactpy.web.module_from_template(
+ "react@^17.0.0",
+ "@material-ui/core@4.12.4",
+ fallback="â",
+)
+Button = reactpy.web.export(mui, "Button")
+
+
+@reactpy.component
+def ViewButtonEvents():
+ event, set_event = reactpy.hooks.use_state(None)
+
+ return reactpy.html.div(
+ Button(
+ {
+ "color": "primary",
+ "variant": "contained",
+ "onClick": lambda event: set_event(event),
+ },
+ "Click Me!",
+ ),
+ reactpy.html.pre(json.dumps(event, indent=2)),
+ )
+
+
+reactpy.run(ViewButtonEvents)
diff --git a/docs/source/escape-hatches/_examples/super_simple_chart/app.py b/docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py
similarity index 94%
rename from docs/source/escape-hatches/_examples/super_simple_chart/app.py
rename to docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py
index 2f2e17556..4640785f8 100644
--- a/docs/source/escape-hatches/_examples/super_simple_chart/app.py
+++ b/docs/source/guides/escape-hatches/_examples/super_simple_chart/main.py
@@ -1,7 +1,6 @@
from pathlib import Path
-from idom import component, run, web
-
+from reactpy import component, run, web
file = Path(__file__).parent / "super-simple-chart.js"
ssc = web.module_from_file("super-simple-chart", file, fallback="â")
diff --git a/docs/source/escape-hatches/_examples/super_simple_chart/super-simple-chart.js b/docs/source/guides/escape-hatches/_examples/super_simple_chart/super-simple-chart.js
similarity index 91%
rename from docs/source/escape-hatches/_examples/super_simple_chart/super-simple-chart.js
rename to docs/source/guides/escape-hatches/_examples/super_simple_chart/super-simple-chart.js
index f14103d78..486e5c363 100644
--- a/docs/source/escape-hatches/_examples/super_simple_chart/super-simple-chart.js
+++ b/docs/source/guides/escape-hatches/_examples/super_simple_chart/super-simple-chart.js
@@ -8,7 +8,7 @@ export function bind(node, config) {
create: (component, props, children) => h(component, props, ...children),
render: (element) => render(element, node),
unmount: () => render(null, node),
- }
+ };
}
export function SuperSimpleChart(props) {
@@ -48,10 +48,9 @@ function makePath(props, domain, data, options) {
const getSvgX = (x) => ((x - xMin) / (xMax - xMin)) * width;
const getSvgY = (y) => height - ((y - yMin) / (yMax - yMin)) * height;
- let pathD = "M " + getSvgX(data[0].x) + " " + getSvgY(data[0].y) + " ";
- pathD += data.map((point, i) => {
- return "L " + getSvgX(point.x) + " " + getSvgY(point.y) + " ";
- });
+ let pathD =
+ `M ${getSvgX(data[0].x)} ${getSvgY(data[0].y)} ` +
+ data.map(({ x, y }, i) => `L ${getSvgX(x)} ${getSvgY(y)}`).join(" ");
return html`=40.8.0", "wheel"]
build-backend = "setuptools.build_meta"
-Then, we can creat the ``setup.py`` file which uses Setuptools. This will differ
+Then, we can create the ``setup.py`` file which uses Setuptools. This will differ
substantially from a normal ``setup.py`` file since, as part of the build process we'll
need to use NPM to bundle our Javascript. This requires customizing some of the build
commands in Setuptools like ``build``, ``sdist``, and ``develop``:
@@ -230,10 +230,10 @@ commands in Setuptools like ``build``, ``sdist``, and ``develop``:
name=PACKAGE_NAME,
version="0.0.1",
packages=find_packages(exclude=["tests*"]),
- classifiers=["Framework :: IDOM", ...],
- keywords=["IDOM", "components", ...],
- # install IDOM with this package
- install_requires=["idom"],
+ classifiers=["Framework :: ReactPy", ...],
+ keywords=["ReactPy", "components", ...],
+ # install ReactPy with this package
+ install_requires=["reactpy"],
# required in order to include static files like bundle.js using MANIFEST.in
include_package_data=True,
# we need access to the file system, so cannot be run from a zip file
@@ -297,7 +297,7 @@ up and running as quickly as possible.
.. _install NPM: https://www.npmjs.com/get-npm
.. _CDN: https://en.wikipedia.org/wiki/Content_delivery_network
.. _PyPI: https://pypi.org/
-.. _template repository: https://github.com/idom-team/idom-react-component-cookiecutter
+.. _template repository: https://github.com/reactive-python/reactpy-js-component-template
.. _web module: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
.. _Rollup: https://rollupjs.org/guide/en/
.. _Webpack: https://webpack.js.org/
diff --git a/docs/source/escape-hatches/index.rst b/docs/source/guides/escape-hatches/index.rst
similarity index 66%
rename from docs/source/escape-hatches/index.rst
rename to docs/source/guides/escape-hatches/index.rst
index ddf0be60e..3ef1b7122 100644
--- a/docs/source/escape-hatches/index.rst
+++ b/docs/source/guides/escape-hatches/index.rst
@@ -4,11 +4,10 @@ Escape Hatches
.. toctree::
:hidden:
- class-components
javascript-components
distributing-javascript
- writing-your-own-server
- writing-your-own-client
+ using-a-custom-backend
+ using-a-custom-client
.. note::
diff --git a/docs/source/escape-hatches/javascript-components.rst b/docs/source/guides/escape-hatches/javascript-components.rst
similarity index 87%
rename from docs/source/escape-hatches/javascript-components.rst
rename to docs/source/guides/escape-hatches/javascript-components.rst
index 4e0177dcf..f0a71b6b7 100644
--- a/docs/source/escape-hatches/javascript-components.rst
+++ b/docs/source/guides/escape-hatches/javascript-components.rst
@@ -3,12 +3,12 @@
Javascript Components
=====================
-While IDOM is a great tool for displaying HTML and responding to browser events with
+While ReactPy is a great tool for displaying HTML and responding to browser events with
pure Python, there are other projects which already allow you to do this inside
`Jupyter Notebooks `__
or in standard
`web apps `__.
-The real power of IDOM comes from its ability to seamlessly leverage the existing
+The real power of ReactPy comes from its ability to seamlessly leverage the existing
Javascript ecosystem. This can be accomplished in different ways for different reasons:
.. list-table::
@@ -18,7 +18,7 @@ Javascript ecosystem. This can be accomplished in different ways for different r
- Use Case
* - :ref:`Dynamically Loaded Components`
- - You want to **quickly experiment** with IDOM and the Javascript ecosystem.
+ - You want to **quickly experiment** with ReactPy and the Javascript ecosystem.
* - :ref:`Custom Javascript Components`
- You want to create polished software that can be **easily shared** with others.
@@ -35,18 +35,18 @@ Dynamically Loaded Components
Javascript` for more info. Instead, it's best used during exploratory phases of
development.
-IDOM makes it easy to draft your code when you're in the early stages of development by
+ReactPy makes it easy to draft your code when you're in the early stages of development by
using a CDN_ to dynamically load Javascript packages on the fly. In this example we'll
be using the ubiquitous React-based UI framework `Material UI`_.
-.. idom:: _examples/material_ui_button_no_action
+.. reactpy:: _examples/material_ui_button_no_action
So now that we can display a Material UI Button we probably want to make it do
something. Thankfully there's nothing new to learn here, you can pass event handlers to
the button just as you did when :ref:`getting started `. Thus, all
we need to do is add an ``onClick`` handler to the component:
-.. idom:: _examples/material_ui_button_on_click
+.. reactpy:: _examples/material_ui_button_on_click
.. _Custom Javascript Component:
@@ -55,7 +55,7 @@ Custom Javascript Components
----------------------------
For projects that will be shared with others, we recommend bundling your Javascript with
-Rollup_ or Webpack_ into a `web module`_. IDOM also provides a `template repository`_
+Rollup_ or Webpack_ into a `web module`_. ReactPy also provides a `template repository`_
that can be used as a blueprint to build a library of React components.
To work as intended, the Javascript bundle must export a function ``bind()`` that
@@ -132,7 +132,7 @@ Javascript that can run directly in the browser. This means we can't use fancy s
like `JSX `__ and instead will use
`htm `__ to simulate JSX in plain Javascript.
-.. idom:: _examples/super_simple_chart
+.. reactpy:: _examples/super_simple_chart
.. Links
@@ -140,7 +140,7 @@ like `JSX `__ and instead will us
.. _Material UI: https://material-ui.com/
.. _CDN: https://en.wikipedia.org/wiki/Content_delivery_network
-.. _template repository: https://github.com/idom-team/idom-react-component-cookiecutter
+.. _template repository: https://github.com/reactive-python/reactpy-js-component-template
.. _web module: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Modules
.. _Rollup: https://rollupjs.org/guide/en/
.. _Webpack: https://webpack.js.org/
diff --git a/docs/source/guides/escape-hatches/using-a-custom-backend.rst b/docs/source/guides/escape-hatches/using-a-custom-backend.rst
new file mode 100644
index 000000000..f9d21208a
--- /dev/null
+++ b/docs/source/guides/escape-hatches/using-a-custom-backend.rst
@@ -0,0 +1,9 @@
+.. _Writing Your Own Backend:
+.. _Using a Custom Backend:
+
+Using a Custom Backend đ§
+=========================
+
+.. note::
+
+ Under construction đ§
diff --git a/docs/source/guides/escape-hatches/using-a-custom-client.rst b/docs/source/guides/escape-hatches/using-a-custom-client.rst
new file mode 100644
index 000000000..95de23e59
--- /dev/null
+++ b/docs/source/guides/escape-hatches/using-a-custom-client.rst
@@ -0,0 +1,9 @@
+.. _Writing Your Own Client:
+.. _Using a Custom Client:
+
+Using a Custom Client đ§
+========================
+
+.. note::
+
+ Under construction đ§
diff --git a/docs/source/guides/getting-started/_examples/debug_error_example.py b/docs/source/guides/getting-started/_examples/debug_error_example.py
new file mode 100644
index 000000000..dd0b212ab
--- /dev/null
+++ b/docs/source/guides/getting-started/_examples/debug_error_example.py
@@ -0,0 +1,20 @@
+from reactpy import component, html, run
+
+
+@component
+def App():
+ return html.div(GoodComponent(), BadComponent())
+
+
+@component
+def GoodComponent():
+ return html.p("This component rendered successfully")
+
+
+@component
+def BadComponent():
+ msg = "This component raised an error"
+ raise RuntimeError(msg)
+
+
+run(App)
diff --git a/docs/source/guides/getting-started/_examples/hello_world.py b/docs/source/guides/getting-started/_examples/hello_world.py
new file mode 100644
index 000000000..f38d9e38f
--- /dev/null
+++ b/docs/source/guides/getting-started/_examples/hello_world.py
@@ -0,0 +1,9 @@
+from reactpy import component, html, run
+
+
+@component
+def App():
+ return html.h1("Hello, world!")
+
+
+run(App)
diff --git a/docs/source/guides/getting-started/_examples/run_fastapi.py b/docs/source/guides/getting-started/_examples/run_fastapi.py
new file mode 100644
index 000000000..bb02e9d6a
--- /dev/null
+++ b/docs/source/guides/getting-started/_examples/run_fastapi.py
@@ -0,0 +1,22 @@
+# :lines: 11-
+
+from reactpy import run
+from reactpy.backend import fastapi as fastapi_server
+
+# the run() function is the entry point for examples
+fastapi_server.configure = lambda _, cmpt: run(cmpt)
+
+
+from fastapi import FastAPI
+
+from reactpy import component, html
+from reactpy.backend.fastapi import configure
+
+
+@component
+def HelloWorld():
+ return html.h1("Hello, world!")
+
+
+app = FastAPI()
+configure(app, HelloWorld)
diff --git a/docs/source/guides/getting-started/_examples/run_flask.py b/docs/source/guides/getting-started/_examples/run_flask.py
new file mode 100644
index 000000000..f98753784
--- /dev/null
+++ b/docs/source/guides/getting-started/_examples/run_flask.py
@@ -0,0 +1,22 @@
+# :lines: 11-
+
+from reactpy import run
+from reactpy.backend import flask as flask_server
+
+# the run() function is the entry point for examples
+flask_server.configure = lambda _, cmpt: run(cmpt)
+
+
+from flask import Flask
+
+from reactpy import component, html
+from reactpy.backend.flask import configure
+
+
+@component
+def HelloWorld():
+ return html.h1("Hello, world!")
+
+
+app = Flask(__name__)
+configure(app, HelloWorld)
diff --git a/docs/source/guides/getting-started/_examples/run_sanic.py b/docs/source/guides/getting-started/_examples/run_sanic.py
new file mode 100644
index 000000000..1dae9f6e0
--- /dev/null
+++ b/docs/source/guides/getting-started/_examples/run_sanic.py
@@ -0,0 +1,26 @@
+# :lines: 11-
+
+from reactpy import run
+from reactpy.backend import sanic as sanic_server
+
+# the run() function is the entry point for examples
+sanic_server.configure = lambda _, cmpt: run(cmpt)
+
+
+from sanic import Sanic
+
+from reactpy import component, html
+from reactpy.backend.sanic import configure
+
+
+@component
+def HelloWorld():
+ return html.h1("Hello, world!")
+
+
+app = Sanic("MyApp")
+configure(app, HelloWorld)
+
+
+if __name__ == "__main__":
+ app.run(port=8000)
diff --git a/docs/source/guides/getting-started/_examples/run_starlette.py b/docs/source/guides/getting-started/_examples/run_starlette.py
new file mode 100644
index 000000000..966b9ef77
--- /dev/null
+++ b/docs/source/guides/getting-started/_examples/run_starlette.py
@@ -0,0 +1,22 @@
+# :lines: 11-
+
+from reactpy import run
+from reactpy.backend import starlette as starlette_server
+
+# the run() function is the entry point for examples
+starlette_server.configure = lambda _, cmpt: run(cmpt)
+
+
+from starlette.applications import Starlette
+
+from reactpy import component, html
+from reactpy.backend.starlette import configure
+
+
+@component
+def HelloWorld():
+ return html.h1("Hello, world!")
+
+
+app = Starlette()
+configure(app, HelloWorld)
diff --git a/docs/source/guides/getting-started/_examples/run_tornado.py b/docs/source/guides/getting-started/_examples/run_tornado.py
new file mode 100644
index 000000000..b86126e63
--- /dev/null
+++ b/docs/source/guides/getting-started/_examples/run_tornado.py
@@ -0,0 +1,31 @@
+# :lines: 11-
+
+from reactpy import run
+from reactpy.backend import tornado as tornado_server
+
+# the run() function is the entry point for examples
+tornado_server.configure = lambda _, cmpt: run(cmpt)
+
+
+import tornado.ioloop
+import tornado.web
+
+from reactpy import component, html
+from reactpy.backend.tornado import configure
+
+
+@component
+def HelloWorld():
+ return html.h1("Hello, world!")
+
+
+def make_app():
+ app = tornado.web.Application()
+ configure(app, HelloWorld)
+ return app
+
+
+if __name__ == "__main__":
+ app = make_app()
+ app.listen(8000)
+ tornado.ioloop.IOLoop.current().start()
diff --git a/docs/source/guides/getting-started/_examples/sample_app.py b/docs/source/guides/getting-started/_examples/sample_app.py
new file mode 100644
index 000000000..a1cc34e6d
--- /dev/null
+++ b/docs/source/guides/getting-started/_examples/sample_app.py
@@ -0,0 +1,3 @@
+import reactpy
+
+reactpy.run(reactpy.sample.SampleApp)
diff --git a/docs/source/guides/getting-started/_static/embed-doc-ex.html b/docs/source/guides/getting-started/_static/embed-doc-ex.html
new file mode 100644
index 000000000..589cb5d80
--- /dev/null
+++ b/docs/source/guides/getting-started/_static/embed-doc-ex.html
@@ -0,0 +1,8 @@
+
+
diff --git a/docs/source/guides/getting-started/_static/embed-reactpy-view/index.html b/docs/source/guides/getting-started/_static/embed-reactpy-view/index.html
new file mode 100644
index 000000000..146d715e4
--- /dev/null
+++ b/docs/source/guides/getting-started/_static/embed-reactpy-view/index.html
@@ -0,0 +1,31 @@
+
+
+
+
+ Example App
+
+
+ This is an Example App
+ Just below is an embedded ReactPy view...
+
+
+
+
diff --git a/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py b/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py
new file mode 100644
index 000000000..6e3687f27
--- /dev/null
+++ b/docs/source/guides/getting-started/_static/embed-reactpy-view/main.py
@@ -0,0 +1,22 @@
+from sanic import Sanic
+from sanic.response import file
+
+from reactpy import component, html
+from reactpy.backend.sanic import Options, configure
+
+app = Sanic("MyApp")
+
+
+@app.route("/")
+async def index(request):
+ return await file("index.html")
+
+
+@component
+def ReactPyView():
+ return html.code("This text came from an ReactPy App")
+
+
+configure(app, ReactPyView, Options(url_prefix="/_reactpy"))
+
+app.run(host="127.0.0.1", port=5000)
diff --git a/docs/source/getting-started/_static/embed-idom-view/screenshot.png b/docs/source/guides/getting-started/_static/embed-reactpy-view/screenshot.png
similarity index 100%
rename from docs/source/getting-started/_static/embed-idom-view/screenshot.png
rename to docs/source/guides/getting-started/_static/embed-reactpy-view/screenshot.png
diff --git a/docs/source/getting-started/_static/logo-django.svg b/docs/source/guides/getting-started/_static/logo-django.svg
similarity index 100%
rename from docs/source/getting-started/_static/logo-django.svg
rename to docs/source/guides/getting-started/_static/logo-django.svg
diff --git a/docs/source/getting-started/_static/logo-jupyter.svg b/docs/source/guides/getting-started/_static/logo-jupyter.svg
similarity index 100%
rename from docs/source/getting-started/_static/logo-jupyter.svg
rename to docs/source/guides/getting-started/_static/logo-jupyter.svg
diff --git a/docs/source/getting-started/_static/logo-plotly.svg b/docs/source/guides/getting-started/_static/logo-plotly.svg
similarity index 100%
rename from docs/source/getting-started/_static/logo-plotly.svg
rename to docs/source/guides/getting-started/_static/logo-plotly.svg
diff --git a/docs/source/getting-started/_static/idom-in-jupyterlab.gif b/docs/source/guides/getting-started/_static/reactpy-in-jupyterlab.gif
similarity index 100%
rename from docs/source/getting-started/_static/idom-in-jupyterlab.gif
rename to docs/source/guides/getting-started/_static/reactpy-in-jupyterlab.gif
diff --git a/docs/source/getting-started/_static/shared-client-state-server-slider.gif b/docs/source/guides/getting-started/_static/shared-client-state-server-slider.gif
similarity index 100%
rename from docs/source/getting-started/_static/shared-client-state-server-slider.gif
rename to docs/source/guides/getting-started/_static/shared-client-state-server-slider.gif
diff --git a/docs/source/guides/getting-started/index.rst b/docs/source/guides/getting-started/index.rst
new file mode 100644
index 000000000..dd210be60
--- /dev/null
+++ b/docs/source/guides/getting-started/index.rst
@@ -0,0 +1,123 @@
+Getting Started
+===============
+
+.. toctree::
+ :hidden:
+
+ installing-reactpy
+ running-reactpy
+
+.. dropdown:: :octicon:`bookmark-fill;2em` What You'll Learn
+ :color: info
+ :animate: fade-in
+ :open:
+
+ .. grid:: 1 2 2 2
+
+ .. grid-item-card:: :octicon:`tools` Installing ReactPy
+ :link: installing-reactpy
+ :link-type: doc
+
+ Learn how ReactPy can be installed in a variety of different ways - with
+ different web servers and even in different frameworks.
+
+ .. grid-item-card:: :octicon:`play` Running ReactPy
+ :link: running-reactpy
+ :link-type: doc
+
+ See how ReactPy can be run with a variety of different production servers or be
+ added to existing applications.
+
+The fastest way to get started with ReactPy is to try it out in a `Juptyer Notebook
+`__.
+If you want to use a Notebook to work through the examples shown in this documentation,
+you'll need to replace calls to ``reactpy.run(App)`` with a line at the end of each cell
+that constructs the ``App()`` in question. If that doesn't make sense, the introductory
+notebook linked below will demonstrate how to do this:
+
+.. card::
+ :link: https://mybinder.org/v2/gh/reactive-python/reactpy-jupyter/main?urlpath=lab/tree/notebooks/introduction.ipynb
+
+ .. image:: _static/reactpy-in-jupyterlab.gif
+ :scale: 72%
+ :align: center
+
+
+Section 1: Installing ReactPy
+-----------------------------
+
+The next fastest option is to install ReactPy along with a supported server (like
+``starlette``) with ``pip``:
+
+.. code-block:: bash
+
+ pip install "reactpy[starlette]"
+
+To check that everything is working you can run the sample application:
+
+.. code-block:: bash
+
+ python -c "import reactpy; reactpy.run(reactpy.sample.SampleApp)"
+
+.. note::
+
+ This launches a simple development server which is good enough for testing, but
+ probably not what you want to use in production. When deploying in production,
+ there's a number of different ways of :ref:`running ReactPy `.
+
+You should then see a few log messages:
+
+.. code-block:: text
+
+ 2022-03-27T11:58:59-0700 | WARNING | You are running a development server. Change this before deploying in production!
+ 2022-03-27T11:58:59-0700 | INFO | Running with 'Starlette' at http://127.0.0.1:8000
+
+The second log message includes a URL indicating where you should go to view the app.
+That will usually be http://127.0.0.1:8000. Once you go to that URL you should see
+something like this:
+
+.. card::
+
+ .. reactpy-view:: _examples/sample_app
+
+If you get a ``RuntimeError`` similar to the following:
+
+.. code-block:: text
+
+ Found none of the following builtin server implementations...
+
+Then be sure you run ``pip install "reactpy[starlette]"`` instead of just ``reactpy``. For
+anything else, report your issue in ReactPy's :discussion-type:`discussion forum
+`.
+
+.. card::
+ :link: installing-reactpy
+ :link-type: doc
+
+ :octicon:`book` Read More
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ Learn how ReactPy can be installed in a variety of different ways - with different web
+ servers and even in different frameworks.
+
+
+Section 2: Running ReactPy
+--------------------------
+
+Once you've :ref:`installed ReactPy `, you'll want to learn how to run an
+application. Throughout most of the examples in this documentation, you'll see the
+:func:`~reactpy.backend.utils.run` function used. While it's convenient tool for
+development it shouldn't be used in production settings - it's slow, and could leak
+secrets through debug log messages.
+
+.. reactpy:: _examples/hello_world
+
+.. card::
+ :link: running-reactpy
+ :link-type: doc
+
+ :octicon:`book` Read More
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ See how ReactPy can be run with a variety of different production servers or be
+ added to existing applications.
diff --git a/docs/source/guides/getting-started/installing-reactpy.rst b/docs/source/guides/getting-started/installing-reactpy.rst
new file mode 100644
index 000000000..0b2ffc28a
--- /dev/null
+++ b/docs/source/guides/getting-started/installing-reactpy.rst
@@ -0,0 +1,121 @@
+Installing ReactPy
+==================
+
+You will typically ``pip`` install ReactPy to alongside one of it's natively supported
+backends. For example, if we want to run ReactPy using the `Starlette
+ `__ backend you would run
+
+.. code-block:: bash
+
+ pip install "reactpy[starlette]"
+
+If you want to install a "pure" version of ReactPy **without a backend implementation**
+you can do so without any installation extras. You might do this if you wanted to
+:ref:`use a custom backend ` or if you wanted to manually pin
+the dependencies for your chosen backend:
+
+.. code-block:: bash
+
+ pip install reactpy
+
+
+Native Backends
+---------------
+
+ReactPy includes built-in support for a variety backend implementations. To install the
+required dependencies for each you should substitute ``starlette`` from the ``pip
+install`` command above with one of the options below:
+
+- ``fastapi`` - https://fastapi.tiangolo.com
+- ``flask`` - https://palletsprojects.com/p/flask/
+- ``sanic`` - https://sanicframework.org
+- ``starlette`` - https://www.starlette.io/
+- ``tornado`` - https://www.tornadoweb.org/en/stable/
+
+If you need to, you can install more than one option by separating them with commas:
+
+.. code-block:: bash
+
+ pip install "reactpy[fastapi,flask,sanic,starlette,tornado]"
+
+Once this is complete you should be able to :ref:`run ReactPy ` with your
+chosen implementation.
+
+
+Other Backends
+--------------
+
+While ReactPy can run in a variety of contexts, sometimes frameworks require extra work in
+order to integrate with them. In these cases, the ReactPy team distributes bindings for
+those frameworks as separate Python packages. For documentation on how to install and
+run ReactPy in these supported frameworks, follow the links below:
+
+.. raw:: html
+
+
+
+.. role:: transparent-text-color
+
+.. We add transparent-text-color to the text so it's not visible, but it's still
+.. searchable.
+
+.. grid:: 3
+
+ .. grid-item-card::
+ :link: https://github.com/reactive-python/reactpy-django
+ :img-background: _static/logo-django.svg
+ :class-card: card-logo-image
+
+ :transparent-text-color:`Django`
+
+ .. grid-item-card::
+ :link: https://github.com/reactive-python/reactpy-jupyter
+ :img-background: _static/logo-jupyter.svg
+ :class-card: card-logo-image
+
+ :transparent-text-color:`Jupyter`
+
+ .. grid-item-card::
+ :link: https://github.com/reactive-python/reactpy-dash
+ :img-background: _static/logo-plotly.svg
+ :class-card: card-logo-image
+
+ :transparent-text-color:`Plotly Dash`
+
+
+For Development
+---------------
+
+If you want to contribute to the development of ReactPy or modify it, you'll want to
+install a development version of ReactPy. This involves cloning the repository where ReactPy's
+source is maintained, and setting up a :ref:`development environment`. From there you'll
+be able to modifying ReactPy's source code and :ref:`run its tests ` to
+ensure the modifications you've made are backwards compatible. If you want to add a new
+feature to ReactPy you should write your own test that validates its behavior.
+
+If you have questions about how to modify ReactPy or help with its development, be sure to
+:discussion:`start a discussion `. The ReactPy team are always
+excited to :ref:`welcome ` new contributions and contributors
+of all kinds
+
+.. card::
+ :link: /about/contributor-guide
+ :link-type: doc
+
+ :octicon:`book` Read More
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ Learn more about how to contribute to the development of ReactPy.
diff --git a/docs/source/guides/getting-started/running-reactpy.rst b/docs/source/guides/getting-started/running-reactpy.rst
new file mode 100644
index 000000000..8abbd574f
--- /dev/null
+++ b/docs/source/guides/getting-started/running-reactpy.rst
@@ -0,0 +1,221 @@
+Running ReactPy
+===============
+
+The simplest way to run ReactPy is with the :func:`~reactpy.backend.utils.run` function. This
+is the method you'll see used throughout this documentation. However, this executes your
+application using a development server which is great for testing, but probably not what
+if you're :ref:`deploying in production `. Below are some
+more robust and performant ways of running ReactPy with various supported servers.
+
+
+Running ReactPy in Production
+-----------------------------
+
+The first thing you'll need to do if you want to run ReactPy in production is choose a
+backend implementation and follow its documentation on how to create and run an
+application. This is the backend :ref:`you probably chose ` when
+installing ReactPy. Then you'll need to configure that application with an ReactPy view. We
+show the basics of how to set up, and then run, each supported backend below, but all
+implementations will follow a pattern similar to the following:
+
+.. code-block::
+
+ from my_chosen_backend import Application
+
+ from reactpy import component, html
+ from reactpy.backend.my_chosen_backend import configure
+
+
+ @component
+ def HelloWorld():
+ return html.h1("Hello, world!")
+
+
+ app = Application()
+ configure(app, HelloWorld)
+
+You'll then run this ``app`` using an `ASGI `__
+or `WSGI `__ server from the command line.
+
+
+Running with `FastAPI `__
+.......................................................
+
+.. reactpy:: _examples/run_fastapi
+
+Then assuming you put this in ``main.py``, you can run the ``app`` using the `Uvicorn
+ `__ ASGI server:
+
+.. code-block:: bash
+
+ uvicorn main:app
+
+
+Running with `Flask `__
+.............................................................
+
+.. reactpy:: _examples/run_flask
+
+Then assuming you put this in ``main.py``, you can run the ``app`` using the `Gunicorn
+ `__ WSGI server:
+
+.. code-block:: bash
+
+ gunicorn main:app
+
+
+Running with `Sanic `__
+...................................................
+
+.. reactpy:: _examples/run_sanic
+
+Then assuming you put this in ``main.py``, you can run the ``app`` using Sanic's builtin
+server:
+
+.. code-block:: bash
+
+ sanic main.app
+
+
+Running with `Starlette `__
+......................................................
+
+.. reactpy:: _examples/run_starlette
+
+Then assuming you put this in ``main.py``, you can run the application using the
+`Uvicorn `__ ASGI server:
+
+.. code-block:: bash
+
+ uvicorn main:app
+
+
+Running with `Tornado `__
+................................................................
+
+.. reactpy:: _examples/run_tornado
+
+Tornado is run using it's own builtin server rather than an external WSGI or ASGI
+server.
+
+
+Running ReactPy in Debug Mode
+-----------------------------
+
+ReactPy provides a debug mode that is turned off by default. This can be enabled when you
+run your application by setting the ``REACTPY_DEBUG_MODE`` environment variable.
+
+.. tab-set::
+
+ .. tab-item:: Unix Shell
+
+ .. code-block::
+
+ export REACTPY_DEBUG_MODE=1
+ python my_reactpy_app.py
+
+ .. tab-item:: Command Prompt
+
+ .. code-block:: text
+
+ set REACTPY_DEBUG_MODE=1
+ python my_reactpy_app.py
+
+ .. tab-item:: PowerShell
+
+ .. code-block:: powershell
+
+ $env:REACTPY_DEBUG_MODE = "1"
+ python my_reactpy_app.py
+
+.. danger::
+
+ Leave debug mode off in production!
+
+Among other things, running in this mode:
+
+- Turns on debug log messages
+- Adds checks to ensure the :ref:`VDOM` spec is adhered to
+- Displays error messages that occur within your app
+
+Errors will be displayed where the uppermost component is located in the view:
+
+.. reactpy:: _examples/debug_error_example
+
+
+Backend Configuration Options
+-----------------------------
+
+ReactPy's various backend implementations come with ``Options`` that can be passed to their
+respective ``configure()`` functions in the following way:
+
+.. code-block::
+
+ from reactpy.backend. import configure, Options
+
+ configure(app, MyComponent, Options(...))
+
+To learn more read about the options for your chosen backend ````:
+
+- :class:`reactpy.backend.fastapi.Options`
+- :class:`reactpy.backend.flask.Options`
+- :class:`reactpy.backend.sanic.Options`
+- :class:`reactpy.backend.starlette.Options`
+- :class:`reactpy.backend.tornado.Options`
+
+
+Embed in an Existing Webpage
+----------------------------
+
+ReactPy provides a Javascript client called ``@reactpy/client`` that can be used to embed
+ReactPy views within an existing applications. This is actually how the interactive
+examples throughout this documentation have been created. You can try this out by
+embedding one the examples from this documentation into your own webpage:
+
+.. tab-set::
+
+ .. tab-item:: HTML
+
+ .. literalinclude:: _static/embed-doc-ex.html
+ :language: html
+
+ .. tab-item:: âļī¸ Result
+
+ .. raw:: html
+ :file: _static/embed-doc-ex.html
+
+.. note::
+
+ For more information on how to use the client see the :ref:`Javascript API`
+ reference. Or if you need to, your can :ref:`write your own backend implementation
+ `.
+
+As mentioned though, this is connecting to the server that is hosting this
+documentation. If you want to connect to a view from your own server, you'll need to
+change the URL above to one you provide. One way to do this might be to add to an
+existing application. Another would be to run ReactPy in an adjacent web server instance
+that you coordinate with something like `NGINX `__. For the sake
+of simplicity, we'll assume you do something similar to the following in an existing
+Python app:
+
+.. tab-set::
+
+ .. tab-item:: main.py
+
+ .. literalinclude:: _static/embed-reactpy-view/main.py
+ :language: python
+
+ .. tab-item:: index.html
+
+ .. literalinclude:: _static/embed-reactpy-view/index.html
+ :language: html
+
+After running ``python main.py``, you should be able to navigate to
+``http://127.0.0.1:8000/index.html`` and see:
+
+.. card::
+ :text-align: center
+
+ .. image:: _static/embed-reactpy-view/screenshot.png
+ :width: 500px
+
diff --git a/docs/source/guides/managing-state/combining-contexts-and-reducers/index.rst b/docs/source/guides/managing-state/combining-contexts-and-reducers/index.rst
new file mode 100644
index 000000000..b9f274f0a
--- /dev/null
+++ b/docs/source/guides/managing-state/combining-contexts-and-reducers/index.rst
@@ -0,0 +1,6 @@
+Combining Contexts and Reducers đ§
+==================================
+
+.. note::
+
+ Under construction đ§
diff --git a/docs/source/guides/managing-state/deeply-sharing-state-with-contexts/index.rst b/docs/source/guides/managing-state/deeply-sharing-state-with-contexts/index.rst
new file mode 100644
index 000000000..4a00caa48
--- /dev/null
+++ b/docs/source/guides/managing-state/deeply-sharing-state-with-contexts/index.rst
@@ -0,0 +1,6 @@
+Deeply Sharing State with Contexts đ§
+=====================================
+
+.. note::
+
+ Under construction đ§
diff --git a/docs/source/managing-state/structuring-your-state.rst b/docs/source/guides/managing-state/how-to-structure-state/index.rst
similarity index 77%
rename from docs/source/managing-state/structuring-your-state.rst
rename to docs/source/guides/managing-state/how-to-structure-state/index.rst
index 68209cccf..5092370a5 100644
--- a/docs/source/managing-state/structuring-your-state.rst
+++ b/docs/source/guides/managing-state/how-to-structure-state/index.rst
@@ -1,6 +1,6 @@
.. _Structuring Your State:
-Structuring Your State đ§
+How to Structure State đ§
=========================
.. note::
diff --git a/docs/source/guides/managing-state/index.rst b/docs/source/guides/managing-state/index.rst
new file mode 100644
index 000000000..0578bafdd
--- /dev/null
+++ b/docs/source/guides/managing-state/index.rst
@@ -0,0 +1,127 @@
+Managing State
+==============
+
+.. toctree::
+ :hidden:
+
+ how-to-structure-state/index
+ sharing-component-state/index
+ when-and-how-to-reset-state/index
+ simplifying-updates-with-reducers/index
+ deeply-sharing-state-with-contexts/index
+ combining-contexts-and-reducers/index
+
+.. dropdown:: :octicon:`bookmark-fill;2em` What You'll Learn
+ :color: info
+ :animate: fade-in
+ :open:
+
+ .. grid:: 1 2 2 2
+
+ .. grid-item-card:: :octicon:`organization` How to Structure State
+ :link: how-to-structure-state/index
+ :link-type: doc
+
+ Make it easy to reason about your application with strategies for organizing
+ state.
+
+ .. grid-item-card:: :octicon:`link` Sharing Component State
+ :link: sharing-component-state/index
+ :link-type: doc
+
+ Allow components to vary vary together, by lifting state into common
+ parents.
+
+ .. grid-item-card:: :octicon:`light-bulb` When and How to Reset State
+ :link: when-and-how-to-reset-state/index
+ :link-type: doc
+
+ Control if and how state is preserved by understanding it's relationship to
+ the "UI tree".
+
+ .. grid-item-card:: :octicon:`plug` Simplifying Updates with Reducers
+ :link: simplifying-updates-with-reducers/index
+ :link-type: doc
+
+ Consolidate state update logic outside your component in a single function,
+ called a âreducer".
+
+ .. grid-item-card:: :octicon:`broadcast` Deeply Sharing State with Contexts
+ :link: deeply-sharing-state-with-contexts/index
+ :link-type: doc
+
+ Instead of passing shared state down deep component trees, bring state into
+ "contexts" instead.
+
+ .. grid-item-card:: :octicon:`rocket` Combining Contexts and Reducers
+ :link: combining-contexts-and-reducers/index
+ :link-type: doc
+
+ You can combine reducers and context together to manage state of a complex
+ screen.
+
+
+Section 1: How to Structure State
+---------------------------------
+
+.. note::
+
+ Under construction đ§
+
+
+Section 2: Shared Component State
+---------------------------------
+
+Sometimes, you want the state of two components to always change together. To do it,
+remove state from both of them, move it to their closest common parent, and then pass it
+down to them via props. This is known as âlifting state upâ, and itâs one of the most
+common things you will do writing code with ReactPy.
+
+In the example below the search input and the list of elements below share the same
+state, the state represents the food name. Note how the component ``Table`` gets called
+at each change of state. The component is observing the state and reacting to state
+changes automatically, just like it would do in React.
+
+.. reactpy:: sharing-component-state/_examples/synced_inputs
+
+.. card::
+ :link: sharing-component-state/index
+ :link-type: doc
+
+ :octicon:`book` Read More
+ ^^^^^^^^^^^^^^^^^^^^^^^^^
+
+ Allow components to vary vary together, by lifting state into common parents.
+
+
+Section 3: When and How to Reset State
+--------------------------------------
+
+.. note::
+
+ Under construction đ§
+
+
+Section 4: Simplifying Updates with Reducers
+--------------------------------------------
+
+.. note::
+
+ Under construction đ§
+
+
+Section 5: Deeply Sharing State with Contexts
+---------------------------------------------
+
+.. note::
+
+ Under construction đ§
+
+
+
+Section 6: Combining Contexts and Reducers
+------------------------------------------
+
+.. note::
+
+ Under construction đ§
diff --git a/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/data.json b/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/data.json
new file mode 100644
index 000000000..f977fe9a7
--- /dev/null
+++ b/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/data.json
@@ -0,0 +1,22 @@
+[
+ {
+ "name": "Sushi",
+ "description": "Sushi is a traditional Japanese dish of prepared vinegared rice"
+ },
+ {
+ "name": "Dal",
+ "description": "The most common way of preparing dal is in the form of a soup to which onions, tomatoes and various spices may be added"
+ },
+ {
+ "name": "Pierogi",
+ "description": "Pierogi are filled dumplings made by wrapping unleavened dough around a savoury or sweet filling and cooking in boiling water"
+ },
+ {
+ "name": "Shish Kebab",
+ "description": "Shish kebab is a popular meal of skewered and grilled cubes of meat"
+ },
+ {
+ "name": "Dim sum",
+ "description": "Dim sum is a large range of small dishes that Cantonese people traditionally enjoy in restaurants for breakfast and lunch"
+ }
+]
diff --git a/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py b/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py
new file mode 100644
index 000000000..ca68aedcb
--- /dev/null
+++ b/docs/source/guides/managing-state/sharing-component-state/_examples/filterable_list/main.py
@@ -0,0 +1,44 @@
+import json
+from pathlib import Path
+
+from reactpy import component, hooks, html, run
+
+HERE = Path(__file__)
+DATA_PATH = HERE.parent / "data.json"
+food_data = json.loads(DATA_PATH.read_text())
+
+
+@component
+def FilterableList():
+ value, set_value = hooks.use_state("")
+ return html.p(Search(value, set_value), html.hr(), Table(value, set_value))
+
+
+@component
+def Search(value, set_value):
+ def handle_change(event):
+ set_value(event["target"]["value"])
+
+ return html.label(
+ "Search by Food Name: ",
+ html.input({"value": value, "on_change": handle_change}),
+ )
+
+
+@component
+def Table(value, set_value):
+ rows = []
+ for row in food_data:
+ name = html.td(row["name"])
+ descr = html.td(row["description"])
+ tr = html.tr(name, descr, value)
+ if not value:
+ rows.append(tr)
+ elif value.lower() in row["name"].lower():
+ rows.append(tr)
+ headers = html.tr(html.td(html.b("name")), html.td(html.b("description")))
+ table = html.table(html.thead(headers), html.tbody(rows))
+ return table
+
+
+run(FilterableList)
diff --git a/docs/source/guides/managing-state/sharing-component-state/_examples/synced_inputs/main.py b/docs/source/guides/managing-state/sharing-component-state/_examples/synced_inputs/main.py
new file mode 100644
index 000000000..e8bcdf333
--- /dev/null
+++ b/docs/source/guides/managing-state/sharing-component-state/_examples/synced_inputs/main.py
@@ -0,0 +1,23 @@
+from reactpy import component, hooks, html, run
+
+
+@component
+def SyncedInputs():
+ value, set_value = hooks.use_state("")
+ return html.p(
+ Input("First input", value, set_value),
+ Input("Second input", value, set_value),
+ )
+
+
+@component
+def Input(label, value, set_value):
+ def handle_change(event):
+ set_value(event["target"]["value"])
+
+ return html.label(
+ label + " ", html.input({"value": value, "on_change": handle_change})
+ )
+
+
+run(SyncedInputs)
diff --git a/docs/source/guides/managing-state/sharing-component-state/index.rst b/docs/source/guides/managing-state/sharing-component-state/index.rst
new file mode 100644
index 000000000..54b61335a
--- /dev/null
+++ b/docs/source/guides/managing-state/sharing-component-state/index.rst
@@ -0,0 +1,38 @@
+Sharing Component State
+=======================
+
+.. note::
+
+ Parts of this document are still under construction đ§
+
+Sometimes, you want the state of two components to always change together. To do it,
+remove state from both of them, move it to their closest common parent, and then pass it
+down to them via props. This is known as âlifting state upâ, and itâs one of the most
+common things you will do writing code with ReactPy.
+
+
+Synced Inputs
+-------------
+
+In the code below the two input boxes are synchronized, this happens because they share
+state. The state is shared via the parent component ``SyncedInputs``. Check the ``value``
+and ``set_value`` variables.
+
+.. reactpy:: _examples/synced_inputs
+
+
+Filterable List
+----------------
+
+In the example below the search input and the list of elements below share the
+same state, the state represents the food name.
+
+Note how the component ``Table`` gets called at each change of state. The
+component is observing the state and reacting to state changes automatically,
+just like it would do in React.
+
+.. reactpy:: _examples/filterable_list
+
+.. note::
+
+ Try typing a food name in the search bar.
diff --git a/docs/source/guides/managing-state/simplifying-updates-with-reducers/index.rst b/docs/source/guides/managing-state/simplifying-updates-with-reducers/index.rst
new file mode 100644
index 000000000..08fce5a69
--- /dev/null
+++ b/docs/source/guides/managing-state/simplifying-updates-with-reducers/index.rst
@@ -0,0 +1,6 @@
+Simplifying Updates with Reducers đ§
+====================================
+
+.. note::
+
+ Under construction đ§
diff --git a/docs/source/managing-state/when-to-reset-state.rst b/docs/source/guides/managing-state/when-and-how-to-reset-state/index.rst
similarity index 50%
rename from docs/source/managing-state/when-to-reset-state.rst
rename to docs/source/guides/managing-state/when-and-how-to-reset-state/index.rst
index 2a0b8c3ae..6a96f4b30 100644
--- a/docs/source/managing-state/when-to-reset-state.rst
+++ b/docs/source/guides/managing-state/when-and-how-to-reset-state/index.rst
@@ -1,7 +1,7 @@
.. _When to Reset State:
-When to Reset State đ§
-======================
+When and How to Reset State đ§
+==============================
.. note::
diff --git a/docs/source/understanding-idom/_static/idom-flow-diagram.svg b/docs/source/guides/understanding-reactpy/_static/idom-flow-diagram.svg
similarity index 99%
rename from docs/source/understanding-idom/_static/idom-flow-diagram.svg
rename to docs/source/guides/understanding-reactpy/_static/idom-flow-diagram.svg
index 27c78b0b2..9077913ca 100644
--- a/docs/source/understanding-idom/_static/idom-flow-diagram.svg
+++ b/docs/source/guides/understanding-reactpy/_static/idom-flow-diagram.svg
@@ -13,7 +13,7 @@
viewBox="-0.5 -0.5 680 580"
content="<mxfile host="app.diagrams.net" modified="2020-09-07T18:34:20.858Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36" etag="IvUE9xI9CxZQnD7O0sJm" version="13.6.6" type="device"><diagram id="3GUrj3vU2Wc3lj3yRkW7" name="Page-1">7Zpdb9owFIZ/DZetkrgEuCy027R1XSWkddqdiU8Sqw5mjvnar5+d2CEhFEpbPkZBlUqO7WP7+D0PdpIG6iWzzwKP4u+cAGt4Dpk10E3D89wrz2voP4fMc0sLdXJDJCgxlRaGPv0Lxtg01jElkFYqSs6ZpKOqMeDDIQSyYsNC8Gm1WshZtdcRjqBm6AeY1a2PlMg4t7abzsL+BWgU255dx5Qk2FY2hjTGhE9LJnTbQD3Bucy/JbMeMB08G5e83adnSouBCRjKlzTAv+WFQ760viXebfT49/4J/cIXfu5lgtnYTLjh+Uz564ZcuVUBw0Fe4P8Z65F2e3wsKAhVdA/ThVlPUM5Zta72cZFma3qtKqjRzsot/Mj8zzocWMMdnvOxtGY1ocFyVWXLh2fNXqV7T/DxkICet6uKpzGV0B/lE5kqmSpbLBNmikPKWI8zLrK2iGBoh4Gyp1LwJyiV+EEbBmFRYuWAihFMQEiYPbs8brHoKluAJyDFXFUxDTzX6MQkim8upwvVFckUlxRn62Ej9KjwvNCC+mLksIU0WkcojRuajrAMYt3Jx5ZHcX0wfbSPUB+PoL46fRCTs0LQwQnSOUKF9BiF4dofl4H4iGppHpwn7iqgLEUahuRab+rUVcBwmtKgGtxqqJr1cINLmtBaFe6O30LYL4ILpLYv3BjaUujs/rAcOWsTwLCkk6r7VeE0PTxwmmXLbGml5naVllYkVUkUgGlV3hBucFRcW0cSiwhkzVG2usW037DgG/mwddJvQZQ8l7/2f9yrsge9qXhpumtp3OGBOuhUtIcZjYZamEomqkPU1flJ1Uni2hQklBDtoytAzQAPMn+Ouh7pCGcxb3YbzRtlYdp9FwdPUcaWkkw/ZZ91DDAnIeN/cf4oC3hNAj5LDOfSayNUUYz7NkEbLxduq9qEh2EKO5GcHdyZMdsxxmu9E2PQ8s/Hjhlj1/fMmNcwRg/R3JLxnJ0wZ8MuZYfMQXtjDjoz5zXMcd9rX+PteV/jXR2eOT9vfnw/s2ZVIp42a1Ztqd+bNWEIfrDyyEpanYHj/AesQWgDIl7KmmVHNWjtmDW2+8Pub1TJ7aR6W+VMnCId90+c/QEH7eNAdYLAqZ2DXguc2sls18A5igPVGTjr0vGkgbOP09QJAqd2c/e1wKndbt41cI7gNHUGzvp0PGXgXG3cYW+rpkcYpDx4gkJM2z9xzNb+gadUUq5lI3J+FPpiEMqVoEtjPNI+klmk39e6TNRAxqPLBAv9LxgLNu+KbD4VYNpXl7SICE7j7GGns/J2U/bR1ahQisqHp1ijR/Cukn35o07fvku2TLANJO5s/6RTXS5e6MoVuHgtDt3+Aw==</diagram></mxfile>"
id="svg4818"
- sodipodi:docname="idom-flow-diagram.svg"
+ sodipodi:docname="reactpy-flow-diagram.svg"
inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)">
diff --git a/docs/source/understanding-idom/_static/live-examples-in-docs.gif b/docs/source/guides/understanding-reactpy/_static/live-examples-in-docs.gif
similarity index 100%
rename from docs/source/understanding-idom/_static/live-examples-in-docs.gif
rename to docs/source/guides/understanding-reactpy/_static/live-examples-in-docs.gif
diff --git a/docs/source/understanding-idom/_static/mvc-flow-diagram.svg b/docs/source/guides/understanding-reactpy/_static/mvc-flow-diagram.svg
similarity index 100%
rename from docs/source/understanding-idom/_static/mvc-flow-diagram.svg
rename to docs/source/guides/understanding-reactpy/_static/mvc-flow-diagram.svg
diff --git a/docs/source/understanding-idom/_static/npm-download-trends.png b/docs/source/guides/understanding-reactpy/_static/npm-download-trends.png
similarity index 100%
rename from docs/source/understanding-idom/_static/npm-download-trends.png
rename to docs/source/guides/understanding-reactpy/_static/npm-download-trends.png
diff --git a/docs/source/understanding-idom/index.rst b/docs/source/guides/understanding-reactpy/index.rst
similarity index 54%
rename from docs/source/understanding-idom/index.rst
rename to docs/source/guides/understanding-reactpy/index.rst
index 5c1b94231..3e0b2ab72 100644
--- a/docs/source/understanding-idom/index.rst
+++ b/docs/source/guides/understanding-reactpy/index.rst
@@ -1,5 +1,5 @@
-Understanding IDOM
-==================
+Understanding ReactPy
+=====================
.. toctree::
:hidden:
@@ -7,6 +7,11 @@ Understanding IDOM
representing-html
what-are-components
the-rendering-pipeline
- why-idom-needs-keys
+ why-reactpy-needs-keys
the-rendering-process
layout-render-servers
+ writing-tests
+
+.. note::
+
+ Under construction đ§
diff --git a/docs/source/understanding-idom/layout-render-servers.rst b/docs/source/guides/understanding-reactpy/layout-render-servers.rst
similarity index 100%
rename from docs/source/understanding-idom/layout-render-servers.rst
rename to docs/source/guides/understanding-reactpy/layout-render-servers.rst
diff --git a/docs/source/understanding-idom/representing-html.rst b/docs/source/guides/understanding-reactpy/representing-html.rst
similarity index 89%
rename from docs/source/understanding-idom/representing-html.rst
rename to docs/source/guides/understanding-reactpy/representing-html.rst
index ffae0d331..c2f32ebd9 100644
--- a/docs/source/understanding-idom/representing-html.rst
+++ b/docs/source/guides/understanding-reactpy/representing-html.rst
@@ -7,10 +7,10 @@ Representing HTML đ§
Under construction đ§
-We've already discussed how to contruct HTML with IDOM in a :ref:`previous section `, but we skimmed over the question of the data structure we use to represent
+We've already discussed how to construct HTML with ReactPy in a :ref:`previous section `, but we skimmed over the question of the data structure we use to represent
it. Let's reconsider the examples from before - on the top is some HTML and on the
-bottom is the corresponding code to create it in IDOM:
+bottom is the corresponding code to create it in ReactPy:
.. code-block:: html
@@ -24,7 +24,7 @@ bottom is the corresponding code to create it in IDOM:
.. testcode::
- from idom import html
+ from reactpy import html
layout = html.div(
html.h1("My Todo List"),
diff --git a/docs/source/understanding-idom/the-rendering-pipeline.rst b/docs/source/guides/understanding-reactpy/the-rendering-pipeline.rst
similarity index 100%
rename from docs/source/understanding-idom/the-rendering-pipeline.rst
rename to docs/source/guides/understanding-reactpy/the-rendering-pipeline.rst
diff --git a/docs/source/understanding-idom/the-rendering-process.rst b/docs/source/guides/understanding-reactpy/the-rendering-process.rst
similarity index 100%
rename from docs/source/understanding-idom/the-rendering-process.rst
rename to docs/source/guides/understanding-reactpy/the-rendering-process.rst
diff --git a/docs/source/understanding-idom/what-are-components.rst b/docs/source/guides/understanding-reactpy/what-are-components.rst
similarity index 100%
rename from docs/source/understanding-idom/what-are-components.rst
rename to docs/source/guides/understanding-reactpy/what-are-components.rst
diff --git a/docs/source/managing-state/shared-component-state.rst b/docs/source/guides/understanding-reactpy/why-reactpy-needs-keys.rst
similarity index 54%
rename from docs/source/managing-state/shared-component-state.rst
rename to docs/source/guides/understanding-reactpy/why-reactpy-needs-keys.rst
index 3c9f66617..e570b8f41 100644
--- a/docs/source/managing-state/shared-component-state.rst
+++ b/docs/source/guides/understanding-reactpy/why-reactpy-needs-keys.rst
@@ -1,6 +1,6 @@
-.. _Shared Component State:
+.. _Why ReactPy Needs Keys:
-Shared Component State đ§
+Why ReactPy Needs Keys đ§
=========================
.. note::
diff --git a/docs/source/managing-state/writing-tests.rst b/docs/source/guides/understanding-reactpy/writing-tests.rst
similarity index 100%
rename from docs/source/managing-state/writing-tests.rst
rename to docs/source/guides/understanding-reactpy/writing-tests.rst
diff --git a/docs/source/index.rst b/docs/source/index.rst
index e7ecbc18d..8b21160f6 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -1,80 +1,81 @@
.. card::
This documentation is still under construction đ§. We welcome your `feedback
- `__!
+ `__!
-What is IDOM?
-=============
+ReactPy
+=======
.. toctree::
:hidden:
- :caption: User Guide
+ :caption: Guides
- getting-started/index
- creating-interfaces/index
- adding-interactivity/index
- managing-state/index
- escape-hatches/index
- understanding-idom/index
+ guides/getting-started/index
+ guides/creating-interfaces/index
+ guides/adding-interactivity/index
+ guides/managing-state/index
+ guides/escape-hatches/index
+ guides/understanding-reactpy/index
.. toctree::
:hidden:
- :caption: Other Resources
+ :caption: Reference
- developing-idom/index
- reference-material/index
- credits-and-licenses
+ reference/browser-events
+ reference/html-attributes
+ reference/hooks-api
+ _auto/apis
+ reference/javascript-api
+ reference/specifications
.. toctree::
:hidden:
- :caption: External Links
+ :caption: About
- Source Code
- Community
- Issues
+ about/changelog
+ about/contributor-guide
+ about/credits-and-licenses
+ Source Code
+ Community
-
-IDOM is a Python web framework for building **interactive websites without needing a
-single line of Javascript**. This is accomplished by breaking down complex applications
-into nestable and reusable chunks of code called :ref:`"components" ` that allow you to focus on what your application does rather than how it
-does it.
-
-Ecosystem independence is also a core feature of IDOM. It can be added to existing
-applications built on a variety of sync and async web servers, as well as integrated
-with other frameworks like Django, Jupyter, and Plotly Dash. Not only does this mean
-you're free to choose what technology stack to run on, but on top of that, you can run
-the exact same components wherever you need them. For example, you can take a component
-originally developed in a Jupyter Notebook and embed it in your production application
-without changing anything about the component itself.
+ReactPy is a library for building user interfaces in Python without Javascript. ReactPy
+interfaces are made from :ref:`components ` which look and behave
+similarly to those found in `ReactJS `__. Designed with simplicity
+in mind, ReactPy can be used by those without web development experience while also
+being powerful enough to grow with your ambitions.
At a Glance
-----------
-To get a rough idea of how to write apps in IDOM, take a look at the tiny `"hello world"
+To get a rough idea of how to write apps in ReactPy, take a look at the tiny `"hello world"
`__ application below:
-.. idom:: getting-started/_examples/hello_world
+.. reactpy:: guides/getting-started/_examples/hello_world
.. hint::
- Try clicking the **âļī¸ result** tab to see what this displays!
+ Try clicking the **đ result** tab to see what this displays!
-So what exactly does this code do? First, it imports a few tools from ``idom`` that will
+So what exactly does this code do? First, it imports a few tools from ``reactpy`` that will
get used to describe and execute an application. Then, we create an ``App`` function
which will define the content the application displays. Specifically, it displays a kind
of HTML element called an ``h1`` `section heading
`__.
Importantly though, a ``@component`` decorator has been applied to the ``App`` function
to turn it into a :ref:`component `. Finally, we :ref:`run
-` an application server by passing the ``App`` component to the ``run()``
-function.
+` a development web server by passing the ``App`` component to the
+``run()`` function.
+
+.. note::
+
+ See :ref:`Running ReactPy in Production` to learn how to use a production-grade server
+ to run ReactPy.
-Learning IDOM
--------------
+Learning ReactPy
+----------------
This documentation is broken up into chapters and sections that introduce you to
concepts step by step with detailed explanations and lots of examples. You should feel
@@ -88,45 +89,45 @@ Chapter 1 - :ref:`Getting Started`
-----------------------------------
If you want to follow along with examples in the sections that follow, you'll want to
-start here so you can :ref:`install IDOM `. This section also contains
-more detailed information about how to :ref:`run IDOM ` in different
-contexts. For example, if you want to embed IDOM into an existing application, or run
-IDOM within a Jupyter Notebook, this is where you can learn how to do those things.
+start here so you can :ref:`install ReactPy `. This section also contains
+more detailed information about how to :ref:`run ReactPy ` in different
+contexts. For example, if you want to embed ReactPy into an existing application, or run
+ReactPy within a Jupyter Notebook, this is where you can learn how to do those things.
.. grid:: 1 2 2 2
.. grid-item::
- .. image:: _static/install-and-run-idom.gif
+ .. image:: _static/install-and-run-reactpy.gif
.. grid-item::
- .. image:: getting-started/_static/idom-in-jupyterlab.gif
+ .. image:: guides/getting-started/_static/reactpy-in-jupyterlab.gif
.. card::
- :link: getting-started/index
+ :link: guides/getting-started/index
:link-type: doc
:octicon:`book` Read More
^^^^^^^^^^^^^^^^^^^^^^^^^
- Install IDOM and run it in a variety of different ways - with different web servers
- and frameworks. You'll even embed IDOM into an existing app.
+ Install ReactPy and run it in a variety of different ways - with different web servers
+ and frameworks. You'll even embed ReactPy into an existing app.
Chapter 2 - :ref:`Creating Interfaces`
--------------------------------------
-IDOM is a Python package for making user interfaces (UI). These interfaces are built
-from small elements of functionality like buttons text and images. IDOM allows you to
+ReactPy is a Python package for making user interfaces (UI). These interfaces are built
+from small elements of functionality like buttons text and images. ReactPy allows you to
combine these elements into reusable :ref:`"components" `. In the
sections that follow you'll learn how these UI elements are created and organized into
components. Then, you'll use this knowledge to create interfaces from raw data:
-.. idom:: creating-interfaces/_examples/todo_list_with_keys
+.. reactpy:: guides/creating-interfaces/rendering-data/_examples/todo_list_with_keys
.. card::
- :link: creating-interfaces/index
+ :link: guides/creating-interfaces/index
:link-type: doc
:octicon:`book` Read More
@@ -142,20 +143,20 @@ Components often need to change whatâs on the screen as a result of an interac
example, typing into the form should update the input field, clicking a âCommentâ button
should bring up a text input field, clicking âBuyâ should put a product in the shopping
cart. Components need to ârememberâ things like the current input value, the current
-image, the shopping cart. In IDOM, this kind of component-specific memory is created and
+image, the shopping cart. In ReactPy, this kind of component-specific memory is created and
updated with a "hook" called ``use_state()`` that creates a **state variable** and
**state setter** respectively:
-.. idom:: adding-interactivity/components-with-state/_examples/adding_state_variable
+.. reactpy:: guides/adding-interactivity/components-with-state/_examples/adding_state_variable
-In IDOM, ``use_state``, as well as any other function whose name starts with ``use``, is
-called a "hook". These are special functions that should only be called while IDOM is
+In ReactPy, ``use_state``, as well as any other function whose name starts with ``use``, is
+called a "hook". These are special functions that should only be called while ReactPy is
:ref:`rendering `. They let you "hook into" the different
-capabilities of IDOM's components of which ``use_state`` is just one (well get into the
+capabilities of ReactPy's components of which ``use_state`` is just one (well get into the
other :ref:`later `).
.. card::
- :link: adding-interactivity/index
+ :link: guides/adding-interactivity/index
:link-type: doc
:octicon:`book` Read More
@@ -168,7 +169,7 @@ Chapter 4 - :ref:`Managing State`
---------------------------------
.. card::
- :link: managing-state/index
+ :link: guides/managing-state/index
:link-type: doc
:octicon:`book` Read More
@@ -182,7 +183,7 @@ Chapter 5 - :ref:`Escape Hatches`
---------------------------------
.. card::
- :link: escape-hatches/index
+ :link: guides/escape-hatches/index
:link-type: doc
:octicon:`book` Read More
@@ -191,11 +192,11 @@ Chapter 5 - :ref:`Escape Hatches`
Under construction đ§
-Chapter 6 - :ref:`Understanding IDOM`
--------------------------------------
+Chapter 6 - :ref:`Understanding ReactPy`
+----------------------------------------
.. card::
- :link: escape-hatches/index
+ :link: guides/escape-hatches/index
:link-type: doc
:octicon:`book` Read More
diff --git a/docs/source/managing-state/index.rst b/docs/source/managing-state/index.rst
deleted file mode 100644
index 4ef9850ac..000000000
--- a/docs/source/managing-state/index.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-Managing State
-==============
-
-.. toctree::
- :hidden:
-
- keeping-components-pure
- logical-flow-of-state
- structuring-your-state
- shared-component-state
- when-to-reset-state
- writing-tests
-
-Under construction đ§
diff --git a/docs/source/managing-state/keeping-components-pure.rst b/docs/source/managing-state/keeping-components-pure.rst
deleted file mode 100644
index a2fc1a15b..000000000
--- a/docs/source/managing-state/keeping-components-pure.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-.. _Keeping Components Pure:
-
-Keeping Components Pure đ§
-==========================
-
-.. note::
-
- Under construction đ§
diff --git a/docs/source/managing-state/logical-flow-of-state.rst b/docs/source/managing-state/logical-flow-of-state.rst
deleted file mode 100644
index 53bf0cff9..000000000
--- a/docs/source/managing-state/logical-flow-of-state.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-.. _Logical Flow of State:
-
-Logical Flow of State đ§
-========================
-
-.. note::
-
- Under construction đ§
diff --git a/docs/source/reference-material/_examples/click_count.py b/docs/source/reference-material/_examples/click_count.py
deleted file mode 100644
index 6f30ce517..000000000
--- a/docs/source/reference-material/_examples/click_count.py
+++ /dev/null
@@ -1,14 +0,0 @@
-import idom
-
-
-@idom.component
-def ClickCount():
- count, set_count = idom.hooks.use_state(0)
-
- return idom.html.button(
- {"onClick": lambda event: set_count(count + 1)},
- [f"Click count: {count}"],
- )
-
-
-idom.run(ClickCount)
diff --git a/docs/source/reference-material/_examples/material_ui_switch.py b/docs/source/reference-material/_examples/material_ui_switch.py
deleted file mode 100644
index 9e4363b74..000000000
--- a/docs/source/reference-material/_examples/material_ui_switch.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import idom
-
-
-mui = idom.web.module_from_template("react", "@material-ui/core@^5.0", fallback="â")
-Switch = idom.web.export(mui, "Switch")
-
-
-@idom.component
-def DayNightSwitch():
- checked, set_checked = idom.hooks.use_state(False)
-
- return idom.html.div(
- Switch(
- {
- "checked": checked,
- "onChange": lambda event, checked: set_checked(checked),
- }
- ),
- "đ" if checked else "đ",
- )
-
-
-idom.run(DayNightSwitch)
diff --git a/docs/source/reference-material/_examples/todo.py b/docs/source/reference-material/_examples/todo.py
deleted file mode 100644
index 7b1f6f675..000000000
--- a/docs/source/reference-material/_examples/todo.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import idom
-
-
-@idom.component
-def Todo():
- items, set_items = idom.hooks.use_state([])
-
- async def add_new_task(event):
- if event["key"] == "Enter":
- set_items(items + [event["target"]["value"]])
-
- tasks = []
-
- for index, text in enumerate(items):
-
- async def remove_task(event, index=index):
- set_items(items[:index] + items[index + 1 :])
-
- task_text = idom.html.td(idom.html.p(text))
- delete_button = idom.html.td({"onClick": remove_task}, idom.html.button(["x"]))
- tasks.append(idom.html.tr(task_text, delete_button))
-
- task_input = idom.html.input({"onKeyDown": add_new_task})
- task_table = idom.html.table(tasks)
-
- return idom.html.div(
- idom.html.p("press enter to add a task:"),
- task_input,
- task_table,
- )
-
-
-idom.run(Todo)
diff --git a/docs/source/reference-material/_examples/use_reducer_counter.py b/docs/source/reference-material/_examples/use_reducer_counter.py
deleted file mode 100644
index ea1b780a0..000000000
--- a/docs/source/reference-material/_examples/use_reducer_counter.py
+++ /dev/null
@@ -1,26 +0,0 @@
-import idom
-
-
-def reducer(count, action):
- if action == "increment":
- return count + 1
- elif action == "decrement":
- return count - 1
- elif action == "reset":
- return 0
- else:
- raise ValueError(f"Unknown action '{action}'")
-
-
-@idom.component
-def Counter():
- count, dispatch = idom.hooks.use_reducer(reducer, 0)
- return idom.html.div(
- f"Count: {count}",
- idom.html.button({"onClick": lambda event: dispatch("reset")}, "Reset"),
- idom.html.button({"onClick": lambda event: dispatch("increment")}, "+"),
- idom.html.button({"onClick": lambda event: dispatch("decrement")}, "-"),
- )
-
-
-idom.run(Counter)
diff --git a/docs/source/reference-material/_examples/use_state_counter.py b/docs/source/reference-material/_examples/use_state_counter.py
deleted file mode 100644
index 8626a60b9..000000000
--- a/docs/source/reference-material/_examples/use_state_counter.py
+++ /dev/null
@@ -1,24 +0,0 @@
-import idom
-
-
-def increment(last_count):
- return last_count + 1
-
-
-def decrement(last_count):
- return last_count - 1
-
-
-@idom.component
-def Counter():
- initial_count = 0
- count, set_count = idom.hooks.use_state(initial_count)
- return idom.html.div(
- f"Count: {count}",
- idom.html.button({"onClick": lambda event: set_count(initial_count)}, "Reset"),
- idom.html.button({"onClick": lambda event: set_count(increment)}, "+"),
- idom.html.button({"onClick": lambda event: set_count(decrement)}, "-"),
- )
-
-
-idom.run(Counter)
diff --git a/docs/source/reference-material/_examples/victory_chart.py b/docs/source/reference-material/_examples/victory_chart.py
deleted file mode 100644
index e2c48d34e..000000000
--- a/docs/source/reference-material/_examples/victory_chart.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import idom
-
-
-victory = idom.web.module_from_template("react", "victory-bar", fallback="â")
-VictoryBar = idom.web.export(victory, "VictoryBar")
-
-bar_style = {"parent": {"width": "500px"}, "data": {"fill": "royalblue"}}
-idom.run(idom.component(lambda: VictoryBar({"style": bar_style})))
diff --git a/docs/source/reference-material/examples.rst b/docs/source/reference-material/examples.rst
deleted file mode 100644
index 694864e53..000000000
--- a/docs/source/reference-material/examples.rst
+++ /dev/null
@@ -1,106 +0,0 @@
-Examples
-========
-
-Slideshow
----------
-
-Try clicking the image đąī¸
-
-.. idom:: _examples/slideshow
- :result-is-default-tab:
-
-
-Click Counter
--------------
-
-.. idom:: _examples/click_count
- :result-is-default-tab:
-
-
-To Do List
-----------
-
-Try typing in the text box and pressing 'Enter' đ
-
-.. idom:: _examples/todo
- :result-is-default-tab:
-
-
-Simple Image Movement
----------------------
-
-.. idom:: _examples/character_movement
- :result-is-default-tab:
-
-
-The Game Snake
---------------
-
-Click to start playing and use the arrow keys to move đŽ
-
-Slow internet may cause inconsistent frame pacing đ
-
-.. idom:: _examples/snake_game
- :result-is-default-tab:
-
-
-Matplotlib Plot
----------------
-
-Pick the polynomial coefficients (separate each coefficient by a space) đĸ:
-
-.. idom:: _examples/matplotlib_plot
- :result-is-default-tab:
-
-
-Simple Dashboard
-----------------
-
-Try interacting with the sliders đ
-
-.. idom:: _examples/simple_dashboard
- :result-is-default-tab:
-
-
-Dynamically Loaded React Components
------------------------------------
-
-This method is not recommended for use in production applications, but it's great while
-you're experimenting:
-
-.. idom:: _examples/victory_chart
- :result-is-default-tab:
-
-
-Material UI Button
-------------------
-
-Click the button to change the indicator đ
-
-.. idom:: _examples/material_ui_switch
- :result-is-default-tab:
-
-
-Pigeon Maps
------------
-
-Click the map to create pinned location đ:
-
-.. idom:: _examples/pigeon_maps
- :result-is-default-tab:
-
-
-Cytoscape Network Graph
------------------------
-
-You can move the nodes in the graph đ¸ī¸:
-
-.. idom:: _examples/network_graph
- :result-is-default-tab:
-
-
-.. Links
-.. =====
-
-.. |launch-binder| image:: https://mybinder.org/badge_logo.svg
- :target: https://mybinder.org/v2/gh/idom-team/idom-jupyter/main?filepath=examples%2Fintroduction.ipynb
diff --git a/docs/source/reference-material/faq.rst b/docs/source/reference-material/faq.rst
deleted file mode 100644
index df1628dcd..000000000
--- a/docs/source/reference-material/faq.rst
+++ /dev/null
@@ -1,64 +0,0 @@
-FAQ
-===
-
-See our :discussion-type:`Discussion Forum ` for more questions and answers.
-
-
-Do UI components run client-side?
----------------------------------
-
-No. The layout is constructed, and components are executed, server-side in Python. Only
-rendering occurs client-side. This means you can access files, databases, and all your
-favorite Python packages with IDOM.
-
-
-Does IDOM transpile Python to Javascript?
------------------------------------------
-
-No. As in the answer to :ref:`Do UI components run client-side?`, IDOM runs almost
-everything server-side and in Python. This was an explicit design choice to keep things
-simple and one which allows you to do everything you normally would in Python.
-
-
-Does IDOM support any React component?
---------------------------------------
-
-If you use :ref:`Dynamically Loaded Components`, then the answer is no. Only components
-whose props are JSON serializable, or which expect basic callback functions similar to
-those of standard event handlers (e.g. ``onClick``) will operate as expected.
-
-However, if you import a :ref:`Custom Javascript Component `
-then, so long as the bundle has be defined appropriately, any component can be made to
-work, even those that don't rely on React.
-
-
-How does IDOM communicate with the client?
-------------------------------------------
-
-IDOM sends diffs of a Virtual Document Object Model (:ref:`VDOM`) to the
-client. For more details, see the description in
-`this article `__.
-
-
-Can I use Javascript components from a CDN?
--------------------------------------------
-
-Yes, but with some restrictions:
-
-1. The Javascript in question must be distributed as an ECMAScript Module
- (`ESM `__)
-2. The module must export the :ref:`required interface `.
-
-These restrictions apply because the Javascript from the CDN must be able to run
-natively in the browser and the module must be able to run in isolation from the main
-application.
-
-See :ref:`Distributing Javascript via CDN_` for more info.
-
-
-What props can I pass to Javascript components?
------------------------------------------------
-
-You can only pass JSON serializable props to components implemented in Javascript. It is
-possible to create a :ref:`Custom Javascript Component `
-which undestands how to deserialise JSON data into native Javascript objects though.
diff --git a/docs/source/reference-material/index.rst b/docs/source/reference-material/index.rst
deleted file mode 100644
index fb4021aca..000000000
--- a/docs/source/reference-material/index.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-Reference Material
-==================
-
-.. toctree::
- :hidden:
-
- examples
- hooks-api
- /_auto/apis
- javascript-api
- browser-events
- specifications
- faq
-
-.. note::
-
- Under construction đ§
diff --git a/docs/source/reference-material/_examples/character_movement/app.py b/docs/source/reference/_examples/character_movement/main.py
similarity index 64%
rename from docs/source/reference-material/_examples/character_movement/app.py
rename to docs/source/reference/_examples/character_movement/main.py
index fbf257a32..9545b0c0a 100644
--- a/docs/source/reference-material/_examples/character_movement/app.py
+++ b/docs/source/reference/_examples/character_movement/main.py
@@ -1,9 +1,8 @@
from pathlib import Path
from typing import NamedTuple
-from idom import component, html, run, use_state
-from idom.widgets import image
-
+from reactpy import component, html, run, use_state
+from reactpy.widgets import image
HERE = Path(__file__)
CHARACTER_IMAGE = (HERE.parent / "static" / "bunny.png").read_bytes()
@@ -42,7 +41,7 @@ def Scene():
"style": {
"width": "200px",
"height": "200px",
- "backgroundColor": "slategray",
+ "background_color": "slategray",
}
},
image(
@@ -58,12 +57,16 @@ def Scene():
},
),
),
- html.button({"onClick": lambda e: set_position(translate(x=-10))}, "Move Left"),
- html.button({"onClick": lambda e: set_position(translate(x=10))}, "Move Right"),
- html.button({"onClick": lambda e: set_position(translate(y=-10))}, "Move Up"),
- html.button({"onClick": lambda e: set_position(translate(y=10))}, "Move Down"),
- html.button({"onClick": lambda e: set_position(rotate(-30))}, "Rotate Left"),
- html.button({"onClick": lambda e: set_position(rotate(30))}, "Rotate Right"),
+ html.button(
+ {"on_click": lambda e: set_position(translate(x=-10))}, "Move Left"
+ ),
+ html.button(
+ {"on_click": lambda e: set_position(translate(x=10))}, "Move Right"
+ ),
+ html.button({"on_click": lambda e: set_position(translate(y=-10))}, "Move Up"),
+ html.button({"on_click": lambda e: set_position(translate(y=10))}, "Move Down"),
+ html.button({"on_click": lambda e: set_position(rotate(-30))}, "Rotate Left"),
+ html.button({"on_click": lambda e: set_position(rotate(30))}, "Rotate Right"),
)
diff --git a/docs/source/reference-material/_examples/character_movement/static/bunny.png b/docs/source/reference/_examples/character_movement/static/bunny.png
similarity index 100%
rename from docs/source/reference-material/_examples/character_movement/static/bunny.png
rename to docs/source/reference/_examples/character_movement/static/bunny.png
diff --git a/docs/source/reference/_examples/click_count.py b/docs/source/reference/_examples/click_count.py
new file mode 100644
index 000000000..3ee2c89c5
--- /dev/null
+++ b/docs/source/reference/_examples/click_count.py
@@ -0,0 +1,13 @@
+import reactpy
+
+
+@reactpy.component
+def ClickCount():
+ count, set_count = reactpy.hooks.use_state(0)
+
+ return reactpy.html.button(
+ {"on_click": lambda event: set_count(count + 1)}, [f"Click count: {count}"]
+ )
+
+
+reactpy.run(ClickCount)
diff --git a/docs/source/reference/_examples/material_ui_switch.py b/docs/source/reference/_examples/material_ui_switch.py
new file mode 100644
index 000000000..704ae3145
--- /dev/null
+++ b/docs/source/reference/_examples/material_ui_switch.py
@@ -0,0 +1,22 @@
+import reactpy
+
+mui = reactpy.web.module_from_template("react", "@material-ui/core@^5.0", fallback="â")
+Switch = reactpy.web.export(mui, "Switch")
+
+
+@reactpy.component
+def DayNightSwitch():
+ checked, set_checked = reactpy.hooks.use_state(False)
+
+ return reactpy.html.div(
+ Switch(
+ {
+ "checked": checked,
+ "onChange": lambda event, checked: set_checked(checked),
+ }
+ ),
+ "đ" if checked else "đ",
+ )
+
+
+reactpy.run(DayNightSwitch)
diff --git a/docs/source/reference-material/_examples/matplotlib_plot.py b/docs/source/reference/_examples/matplotlib_plot.py
similarity index 62%
rename from docs/source/reference-material/_examples/matplotlib_plot.py
rename to docs/source/reference/_examples/matplotlib_plot.py
index 6dffb79db..5c4d616fe 100644
--- a/docs/source/reference-material/_examples/matplotlib_plot.py
+++ b/docs/source/reference/_examples/matplotlib_plot.py
@@ -2,24 +2,24 @@
import matplotlib.pyplot as plt
-import idom
-from idom.widgets import image
+import reactpy
+from reactpy.widgets import image
-@idom.component
+@reactpy.component
def PolynomialPlot():
- coefficients, set_coefficients = idom.hooks.use_state([0])
+ coefficients, set_coefficients = reactpy.hooks.use_state([0])
- x = [n for n in linspace(-1, 1, 50)]
+ x = list(linspace(-1, 1, 50))
y = [polynomial(value, coefficients) for value in x]
- return idom.html.div(
+ return reactpy.html.div(
plot(f"{len(coefficients)} Term Polynomial", x, y),
ExpandableNumberInputs(coefficients, set_coefficients),
)
-@idom.component
+@reactpy.component
def ExpandableNumberInputs(values, set_values):
inputs = []
for i in range(len(values)):
@@ -31,16 +31,16 @@ def set_value_at_index(event, index=i):
inputs.append(poly_coef_input(i + 1, set_value_at_index))
def add_input():
- set_values(values + [0])
+ set_values([*values, 0])
def del_input():
set_values(values[:-1])
- return idom.html.div(
- idom.html.div(
+ return reactpy.html.div(
+ reactpy.html.div(
"add/remove term:",
- idom.html.button({"onClick": lambda event: add_input()}, "+"),
- idom.html.button({"onClick": lambda event: del_input()}, "-"),
+ reactpy.html.button({"on_click": lambda event: add_input()}, "+"),
+ reactpy.html.button({"on_click": lambda event: del_input()}, "-"),
),
inputs,
)
@@ -57,21 +57,15 @@ def plot(title, x, y):
def poly_coef_input(index, callback):
- return idom.html.div(
- {"style": {"margin-top": "5px"}},
- idom.html.label(
+ return reactpy.html.div(
+ {"style": {"margin-top": "5px"}, "key": index},
+ reactpy.html.label(
"C",
- idom.html.sub(index),
- " Ã X",
- idom.html.sup(index),
+ reactpy.html.sub(index),
+ " x X",
+ reactpy.html.sup(index),
),
- idom.html.input(
- {
- "type": "number",
- "onChange": callback,
- },
- ),
- key=index,
+ reactpy.html.input({"type": "number", "on_change": callback}),
)
@@ -88,4 +82,4 @@ def linspace(start, stop, n):
yield start + h * i
-idom.run(PolynomialPlot)
+reactpy.run(PolynomialPlot)
diff --git a/docs/source/reference-material/_examples/network_graph.py b/docs/source/reference/_examples/network_graph.py
similarity index 72%
rename from docs/source/reference-material/_examples/network_graph.py
rename to docs/source/reference/_examples/network_graph.py
index 3dfb9ae87..79b1092f3 100644
--- a/docs/source/reference-material/_examples/network_graph.py
+++ b/docs/source/reference/_examples/network_graph.py
@@ -1,19 +1,16 @@
import random
-import idom
+import reactpy
-
-react_cytoscapejs = idom.web.module_from_template(
- # we need to use this template because react-cytoscapejs uses a default export
+react_cytoscapejs = reactpy.web.module_from_template(
"react",
"react-cytoscapejs",
- exports_default=True,
fallback="â",
)
-Cytoscape = idom.web.export(react_cytoscapejs, "default")
+Cytoscape = reactpy.web.export(react_cytoscapejs, "default")
-@idom.component
+@reactpy.component
def RandomNetworkGraph():
return Cytoscape(
{
@@ -40,4 +37,4 @@ def random_network(number_of_nodes):
return nodes + conns
-idom.run(RandomNetworkGraph)
+reactpy.run(RandomNetworkGraph)
diff --git a/docs/source/reference-material/_examples/pigeon_maps.py b/docs/source/reference/_examples/pigeon_maps.py
similarity index 52%
rename from docs/source/reference-material/_examples/pigeon_maps.py
rename to docs/source/reference/_examples/pigeon_maps.py
index ab0157f73..1ddf04fdc 100644
--- a/docs/source/reference-material/_examples/pigeon_maps.py
+++ b/docs/source/reference/_examples/pigeon_maps.py
@@ -1,26 +1,23 @@
-import idom
+import reactpy
+pigeon_maps = reactpy.web.module_from_template("react", "pigeon-maps", fallback="â")
+Map, Marker = reactpy.web.export(pigeon_maps, ["Map", "Marker"])
-pigeon_maps = idom.web.module_from_template("react", "pigeon-maps", fallback="â")
-Map, Marker = idom.web.export(pigeon_maps, ["Map", "Marker"])
-
-@idom.component
+@reactpy.component
def MapWithMarkers():
marker_anchor, add_marker_anchor, remove_marker_anchor = use_set()
- markers = list(
- map(
- lambda anchor: Marker(
- {
- "anchor": anchor,
- "onClick": lambda: remove_marker_anchor(anchor),
- },
- key=str(anchor),
- ),
- marker_anchor,
+ markers = [
+ Marker(
+ {
+ "anchor": anchor,
+ "onClick": lambda event, a=anchor: remove_marker_anchor(a),
+ },
+ key=str(anchor),
)
- )
+ for anchor in marker_anchor
+ ]
return Map(
{
@@ -35,7 +32,7 @@ def MapWithMarkers():
def use_set(initial_value=None):
- values, set_values = idom.hooks.use_state(initial_value or set())
+ values, set_values = reactpy.hooks.use_state(initial_value or set())
def add_value(lat_lon):
set_values(values.union({lat_lon}))
@@ -46,4 +43,4 @@ def remove_value(lat_lon):
return values, add_value, remove_value
-idom.run(MapWithMarkers)
+reactpy.run(MapWithMarkers)
diff --git a/docs/source/reference-material/_examples/simple_dashboard.py b/docs/source/reference/_examples/simple_dashboard.py
similarity index 73%
rename from docs/source/reference-material/_examples/simple_dashboard.py
rename to docs/source/reference/_examples/simple_dashboard.py
index 540082f58..66913fc84 100644
--- a/docs/source/reference-material/_examples/simple_dashboard.py
+++ b/docs/source/reference/_examples/simple_dashboard.py
@@ -2,28 +2,27 @@
import random
import time
-import idom
-from idom.widgets import Input
+import reactpy
+from reactpy.widgets import Input
-
-victory = idom.web.module_from_template(
+victory = reactpy.web.module_from_template(
"react",
"victory-line",
fallback="â",
# not usually required (see issue #461 for more info)
unmount_before_update=True,
)
-VictoryLine = idom.web.export(victory, "VictoryLine")
+VictoryLine = reactpy.web.export(victory, "VictoryLine")
-@idom.component
+@reactpy.component
def RandomWalk():
- mu = idom.hooks.use_ref(0)
- sigma = idom.hooks.use_ref(1)
+ mu = reactpy.hooks.use_ref(0)
+ sigma = reactpy.hooks.use_ref(1)
- return idom.html.div(
+ return reactpy.html.div(
RandomWalkGraph(mu, sigma),
- idom.html.style(
+ reactpy.html.style(
"""
.number-input-container {margin-bottom: 20px}
.number-input-container input {width: 48%;float: left}
@@ -45,12 +44,12 @@ def RandomWalk():
)
-@idom.component
+@reactpy.component
def RandomWalkGraph(mu, sigma):
interval = use_interval(0.5)
- data, set_data = idom.hooks.use_state([{"x": 0, "y": 0}] * 50)
+ data, set_data = reactpy.hooks.use_state([{"x": 0, "y": 0}] * 50)
- @idom.hooks.use_effect
+ @reactpy.hooks.use_effect
async def animate():
await interval
last_data_point = data[-1]
@@ -71,27 +70,27 @@ async def animate():
)
-@idom.component
+@reactpy.component
def NumberInput(label, value, set_value_callback, domain):
minimum, maximum, step = domain
attrs = {"min": minimum, "max": maximum, "step": step}
- value, set_value = idom.hooks.use_state(value)
+ value, set_value = reactpy.hooks.use_state(value)
def update_value(value):
set_value(value)
set_value_callback(value)
- return idom.html.fieldset(
- {"class": "number-input-container"},
- idom.html.legend({"style": {"font-size": "medium"}}, label),
+ return reactpy.html.fieldset(
+ {"class_name": "number-input-container"},
+ reactpy.html.legend({"style": {"font-size": "medium"}}, label),
Input(update_value, "number", value, attributes=attrs, cast=float),
Input(update_value, "range", value, attributes=attrs, cast=float),
)
def use_interval(rate):
- usage_time = idom.hooks.use_ref(time.time())
+ usage_time = reactpy.hooks.use_ref(time.time())
async def interval() -> None:
await asyncio.sleep(rate - (time.time() - usage_time.current))
@@ -100,4 +99,4 @@ async def interval() -> None:
return asyncio.ensure_future(interval())
-idom.run(RandomWalk)
+reactpy.run(RandomWalk)
diff --git a/docs/source/reference-material/_examples/slideshow.py b/docs/source/reference/_examples/slideshow.py
similarity index 55%
rename from docs/source/reference-material/_examples/slideshow.py
rename to docs/source/reference/_examples/slideshow.py
index 0d3116ac4..b490b3feb 100644
--- a/docs/source/reference-material/_examples/slideshow.py
+++ b/docs/source/reference/_examples/slideshow.py
@@ -1,20 +1,20 @@
-import idom
+import reactpy
-@idom.component
+@reactpy.component
def Slideshow():
- index, set_index = idom.hooks.use_state(0)
+ index, set_index = reactpy.hooks.use_state(0)
def next_image(event):
set_index(index + 1)
- return idom.html.img(
+ return reactpy.html.img(
{
"src": f"https://picsum.photos/id/{index}/800/300",
"style": {"cursor": "pointer"},
- "onClick": next_image,
+ "on_click": next_image,
}
)
-idom.run(Slideshow)
+reactpy.run(Slideshow)
diff --git a/docs/source/reference-material/_examples/snake_game.py b/docs/source/reference/_examples/snake_game.py
similarity index 74%
rename from docs/source/reference-material/_examples/snake_game.py
rename to docs/source/reference/_examples/snake_game.py
index 92fe054f0..36916410e 100644
--- a/docs/source/reference-material/_examples/snake_game.py
+++ b/docs/source/reference/_examples/snake_game.py
@@ -3,7 +3,7 @@
import random
import time
-import idom
+import reactpy
class GameState(enum.Enum):
@@ -13,26 +13,25 @@ class GameState(enum.Enum):
play = 3
-@idom.component
+@reactpy.component
def GameView():
- game_state, set_game_state = idom.hooks.use_state(GameState.init)
+ game_state, set_game_state = reactpy.hooks.use_state(GameState.init)
if game_state == GameState.play:
return GameLoop(grid_size=6, block_scale=50, set_game_state=set_game_state)
- start_button = idom.html.button(
- {"onClick": lambda event: set_game_state(GameState.play)},
- "Start",
+ start_button = reactpy.html.button(
+ {"on_click": lambda event: set_game_state(GameState.play)}, "Start"
)
if game_state == GameState.won:
- menu = idom.html.div(idom.html.h3("You won!"), start_button)
+ menu = reactpy.html.div(reactpy.html.h3("You won!"), start_button)
elif game_state == GameState.lost:
- menu = idom.html.div(idom.html.h3("You lost"), start_button)
+ menu = reactpy.html.div(reactpy.html.h3("You lost"), start_button)
else:
- menu = idom.html.div(idom.html.h3("Click to play"), start_button)
+ menu = reactpy.html.div(reactpy.html.h3("Click to play"), start_button)
- menu_style = idom.html.style(
+ menu_style = reactpy.html.style(
"""
.snake-game-menu h3 {
margin-top: 0px !important;
@@ -40,7 +39,7 @@ def GameView():
"""
)
- return idom.html.div({"className": "snake-game-menu"}, menu_style, menu)
+ return reactpy.html.div({"class_name": "snake-game-menu"}, menu_style, menu)
class Direction(enum.Enum):
@@ -50,19 +49,21 @@ class Direction(enum.Enum):
ArrowRight = (1, 0)
-@idom.component
+@reactpy.component
def GameLoop(grid_size, block_scale, set_game_state):
# we `use_ref` here to capture the latest direction press without any delay
- direction = idom.hooks.use_ref(Direction.ArrowRight.value)
+ direction = reactpy.hooks.use_ref(Direction.ArrowRight.value)
# capture the last direction of travel that was rendered
last_direction = direction.current
- snake, set_snake = idom.hooks.use_state([(grid_size // 2 - 1, grid_size // 2 - 1)])
+ snake, set_snake = reactpy.hooks.use_state(
+ [(grid_size // 2 - 1, grid_size // 2 - 1)]
+ )
food, set_food = use_snake_food(grid_size, snake)
grid = create_grid(grid_size, block_scale)
- @idom.event(prevent_default=True)
+ @reactpy.event(prevent_default=True)
def on_direction_change(event):
if hasattr(Direction, event["key"]):
maybe_new_direction = Direction[event["key"]].value
@@ -72,7 +73,7 @@ def on_direction_change(event):
if direction_vector_sum != (0, 0):
direction.current = maybe_new_direction
- grid_wrapper = idom.html.div({"onKeyDown": on_direction_change}, grid)
+ grid_wrapper = reactpy.html.div({"on_key_down": on_direction_change}, grid)
assign_grid_block_color(grid, food, "blue")
@@ -89,7 +90,7 @@ def on_direction_change(event):
interval = use_interval(0.5)
- @idom.hooks.use_effect
+ @reactpy.hooks.use_effect
async def animate():
if new_game_state is not None:
await asyncio.sleep(1)
@@ -106,7 +107,7 @@ async def animate():
if snake[-1] == food:
set_food()
- new_snake = snake + [new_snake_head]
+ new_snake = [*snake, new_snake_head]
else:
new_snake = snake[1:] + [new_snake_head]
@@ -119,7 +120,7 @@ def use_snake_food(grid_size, current_snake):
grid_points = {(x, y) for x in range(grid_size) for y in range(grid_size)}
points_not_in_snake = grid_points.difference(current_snake)
- food, _set_food = idom.hooks.use_state(current_snake[-1])
+ food, _set_food = reactpy.hooks.use_state(current_snake[-1])
def set_food():
_set_food(random.choice(list(points_not_in_snake)))
@@ -128,7 +129,7 @@ def set_food():
def use_interval(rate):
- usage_time = idom.hooks.use_ref(time.time())
+ usage_time = reactpy.hooks.use_ref(time.time())
async def interval() -> None:
await asyncio.sleep(rate - (time.time() - usage_time.current))
@@ -138,7 +139,7 @@ async def interval() -> None:
def create_grid(grid_size, block_scale):
- return idom.html.div(
+ return reactpy.html.div(
{
"style": {
"height": f"{block_scale * grid_size}px",
@@ -149,16 +150,15 @@ def create_grid(grid_size, block_scale):
"grid-template-columns": f"repeat({grid_size}, {block_scale}px)",
"grid-template-rows": f"repeat({grid_size}, {block_scale}px)",
},
- "tabIndex": -1,
+ "tab_index": -1,
},
[
- idom.html.div(
- {"style": {"height": f"{block_scale}px"}},
+ reactpy.html.div(
+ {"style": {"height": f"{block_scale}px"}, "key": i},
[
create_grid_block("black", block_scale, key=i)
for i in range(grid_size)
],
- key=i,
)
for i in range(grid_size)
],
@@ -166,16 +166,16 @@ def create_grid(grid_size, block_scale):
def create_grid_block(color, block_scale, key):
- return idom.html.div(
+ return reactpy.html.div(
{
"style": {
"height": f"{block_scale}px",
"width": f"{block_scale}px",
- "backgroundColor": color,
+ "background_color": color,
"outline": "1px solid grey",
- }
- },
- key=key,
+ },
+ "key": key,
+ }
)
@@ -185,4 +185,4 @@ def assign_grid_block_color(grid, point, color):
block["attributes"]["style"]["backgroundColor"] = color
-idom.run(GameView)
+reactpy.run(GameView)
diff --git a/docs/source/reference/_examples/todo.py b/docs/source/reference/_examples/todo.py
new file mode 100644
index 000000000..104ea59a9
--- /dev/null
+++ b/docs/source/reference/_examples/todo.py
@@ -0,0 +1,35 @@
+import reactpy
+
+
+@reactpy.component
+def Todo():
+ items, set_items = reactpy.hooks.use_state([])
+
+ async def add_new_task(event):
+ if event["key"] == "Enter":
+ set_items([*items, event["target"]["value"]])
+
+ tasks = []
+
+ for index, text in enumerate(items):
+
+ async def remove_task(event, index=index):
+ set_items(items[:index] + items[index + 1 :])
+
+ task_text = reactpy.html.td(reactpy.html.p(text))
+ delete_button = reactpy.html.td(
+ {"on_click": remove_task}, reactpy.html.button(["x"])
+ )
+ tasks.append(reactpy.html.tr(task_text, delete_button))
+
+ task_input = reactpy.html.input({"on_key_down": add_new_task})
+ task_table = reactpy.html.table(tasks)
+
+ return reactpy.html.div(
+ reactpy.html.p("press enter to add a task:"),
+ task_input,
+ task_table,
+ )
+
+
+reactpy.run(Todo)
diff --git a/docs/source/reference/_examples/use_reducer_counter.py b/docs/source/reference/_examples/use_reducer_counter.py
new file mode 100644
index 000000000..6f9581dfd
--- /dev/null
+++ b/docs/source/reference/_examples/use_reducer_counter.py
@@ -0,0 +1,27 @@
+import reactpy
+
+
+def reducer(count, action):
+ if action == "increment":
+ return count + 1
+ elif action == "decrement":
+ return count - 1
+ elif action == "reset":
+ return 0
+ else:
+ msg = f"Unknown action '{action}'"
+ raise ValueError(msg)
+
+
+@reactpy.component
+def Counter():
+ count, dispatch = reactpy.hooks.use_reducer(reducer, 0)
+ return reactpy.html.div(
+ f"Count: {count}",
+ reactpy.html.button({"on_click": lambda event: dispatch("reset")}, "Reset"),
+ reactpy.html.button({"on_click": lambda event: dispatch("increment")}, "+"),
+ reactpy.html.button({"on_click": lambda event: dispatch("decrement")}, "-"),
+ )
+
+
+reactpy.run(Counter)
diff --git a/docs/source/reference/_examples/use_state_counter.py b/docs/source/reference/_examples/use_state_counter.py
new file mode 100644
index 000000000..b2d8c84a9
--- /dev/null
+++ b/docs/source/reference/_examples/use_state_counter.py
@@ -0,0 +1,26 @@
+import reactpy
+
+
+def increment(last_count):
+ return last_count + 1
+
+
+def decrement(last_count):
+ return last_count - 1
+
+
+@reactpy.component
+def Counter():
+ initial_count = 0
+ count, set_count = reactpy.hooks.use_state(initial_count)
+ return reactpy.html.div(
+ f"Count: {count}",
+ reactpy.html.button(
+ {"on_click": lambda event: set_count(initial_count)}, "Reset"
+ ),
+ reactpy.html.button({"on_click": lambda event: set_count(increment)}, "+"),
+ reactpy.html.button({"on_click": lambda event: set_count(decrement)}, "-"),
+ )
+
+
+reactpy.run(Counter)
diff --git a/docs/source/reference/_examples/victory_chart.py b/docs/source/reference/_examples/victory_chart.py
new file mode 100644
index 000000000..ce37c522f
--- /dev/null
+++ b/docs/source/reference/_examples/victory_chart.py
@@ -0,0 +1,7 @@
+import reactpy
+
+victory = reactpy.web.module_from_template("react", "victory-bar", fallback="â")
+VictoryBar = reactpy.web.export(victory, "VictoryBar")
+
+bar_style = {"parent": {"width": "500px"}, "data": {"fill": "royalblue"}}
+reactpy.run(reactpy.component(lambda: VictoryBar({"style": bar_style})))
diff --git a/docs/source/reference-material/_static/vdom-json-schema.json b/docs/source/reference/_static/vdom-json-schema.json
similarity index 83%
rename from docs/source/reference-material/_static/vdom-json-schema.json
rename to docs/source/reference/_static/vdom-json-schema.json
index d3ab5bd6d..b1005d2ed 100644
--- a/docs/source/reference-material/_static/vdom-json-schema.json
+++ b/docs/source/reference/_static/vdom-json-schema.json
@@ -35,9 +35,7 @@
"type": "string"
}
},
- "required": [
- "tagName"
- ],
+ "required": ["tagName"],
"type": "object"
},
"elementChildren": {
@@ -61,12 +59,9 @@
"then": {
"$ref": "#/definitions/element"
},
- "type": [
- "object",
- "string"
- ]
+ "type": ["object", "string"]
},
- "eventHander": {
+ "eventHandler": {
"properties": {
"preventDefault": {
"type": "boolean"
@@ -78,9 +73,7 @@
"type": "string"
}
},
- "required": [
- "target"
- ],
+ "required": ["target"],
"type": "object"
},
"importSource": {
@@ -94,29 +87,20 @@
"then": {
"$ref": "#/definitions/elementOrString"
},
- "type": [
- "object",
- "string",
- "null"
- ]
+ "type": ["object", "string", "null"]
},
"source": {
"type": "string"
},
"sourceType": {
- "enum": [
- "URL",
- "NAME"
- ]
+ "enum": ["URL", "NAME"]
},
"unmountBeforeUpdate": {
"type": "boolean"
}
},
- "required": [
- "source"
- ],
+ "required": ["source"],
"type": "object"
}
}
-}
\ No newline at end of file
+}
diff --git a/docs/source/reference-material/browser-events.rst b/docs/source/reference/browser-events.rst
similarity index 100%
rename from docs/source/reference-material/browser-events.rst
rename to docs/source/reference/browser-events.rst
diff --git a/docs/source/reference-material/hooks-api.rst b/docs/source/reference/hooks-api.rst
similarity index 85%
rename from docs/source/reference-material/hooks-api.rst
rename to docs/source/reference/hooks-api.rst
index f2967376e..ca8123e85 100644
--- a/docs/source/reference-material/hooks-api.rst
+++ b/docs/source/reference/hooks-api.rst
@@ -7,12 +7,6 @@ Components. Their usage should always follow the :ref:`Rules of Hooks`. For most
cases the :ref:`Basic Hooks` should be enough, however the remaining
:ref:`Supplementary Hooks` should fulfill less common scenarios.
-.. note::
-
- Not all of React's built-in hooks have been implemented.
- `In the future `_ they will be
- added, but if you have a particular need for a missing hook post an issue.
-
Basic Hooks
===========
@@ -55,7 +49,7 @@ accepts a single argument (the previous state) and returns the next state. Consi
simply use case of a counter where we've pulled out logic for increment and
decremented the count:
-.. idom:: _examples/use_state_counter
+.. reactpy:: _examples/use_state_counter
We use the functional form for the "+" and "-" buttons since the next ``count`` depends
on the previous value, while for the "Reset" button we simple assign the
@@ -108,7 +102,7 @@ component render functions.
.. note::
Normally in React the ``did_render`` function is called once an update has been
- committed to the screen. Since no such action is performed by IDOM, and the time
+ committed to the screen. Since no such action is performed by ReactPy, and the time
at which the update is displayed cannot be known we are unable to achieve parity
with this behavior.
@@ -159,16 +153,16 @@ Here, a new connection will be established whenever a new ``url`` is set.
Async Effects
.............
-A behavior unique to IDOM's implementation of ``use_effect`` is that it natively
+A behavior unique to ReactPy's implementation of ``use_effect`` is that it natively
supports ``async`` functions:
.. code-block::
- async def nonblocking_effect():
+ async def non_blocking_effect():
resource = await do_something_asynchronously()
return lambda: blocking_close(resource)
- use_effect(nonblocking_effect)
+ use_effect(non_blocking_effect)
There are **three important subtleties** to note about using asynchronous effects:
@@ -186,9 +180,9 @@ There are **three important subtleties** to note about using asynchronous effect
Manual Effect Conditions
........................
-In some cases, you may want to explicitely declare when an effect should be triggered.
-You can do this by passing ``dependencies`` to ``use_effect``. Each of the following values
-produce different effect behaviors:
+In some cases, you may want to explicitly declare when an effect should be triggered.
+You can do this by passing ``dependencies`` to ``use_effect``. Each of the following
+values produce different effect behaviors:
- ``use_effect(..., dependencies=None)`` - triggers and cleans up on every render.
- ``use_effect(..., dependencies=[])`` - only triggers on the first and cleans up after
@@ -197,6 +191,24 @@ produce different effect behaviors:
``x`` or ``y`` have changed.
+Use Context
+-----------
+
+.. code-block::
+
+ value = use_context(MyContext)
+
+Accepts a context object (the value returned from
+:func:`reactpy.core.hooks.create_context`) and returns the current context value for that
+context. The current context value is determined by the ``value`` argument passed to the
+nearest ``MyContext`` in the tree.
+
+When the nearest above the component updates, this Hook will
+trigger a rerender with the latest context value passed to that MyContext provider. Even
+if an ancestor uses React.memo or shouldComponentUpdate, a rerender will still happen
+starting at the component itself using useContext.
+
+
Supplementary Hooks
===================
@@ -223,7 +235,7 @@ may be slightly more performant as well as being preferable since there is only
We can rework the :ref:`Functional Updates` counter example to use ``use_reducer``:
-.. idom:: _examples/use_reducer_counter
+.. reactpy:: _examples/use_reducer_counter
.. note::
@@ -286,14 +298,13 @@ Use Ref
ref_container = use_ref(initial_value)
-Returns a mutable :class:`~idom.utils.Ref` object that has a single
-:attr:`~idom.utils.Ref.current` attribute that at first contains the ``initial_state``.
+Returns a mutable :class:`~reactpy.utils.Ref` object that has a single
+:attr:`~reactpy.utils.Ref.current` attribute that at first contains the ``initial_state``.
The identity of the ``Ref`` object will be preserved for the lifetime of the component.
A ``Ref`` is most useful if you need to incur side effects since updating its
``.current`` attribute doesn't trigger a re-render of the component. You'll often use this
hook alongside :ref:`Use Effect` or in response to component event handlers.
-:ref:`The Game Snake` provides a good use case for ``use_ref``.
.. links
@@ -318,7 +329,7 @@ Only call outside flow controls
**Don't call hooks inside loops, conditions, or nested functions.** Instead you must
always call hooks at the top level of your functions. By adhering to this rule you
ensure that hooks are always called in the exact same order. This fact is what allows
-IDOM to preserve the state of hooks between multiple calls to ``useState`` and
+ReactPy to preserve the state of hooks between multiple calls to ``useState`` and
``useEffect`` calls.
@@ -331,33 +342,33 @@ Only call in render functions
- â
Call Hooks from another custom hook
-Following this rule ensures stateful logic for IDOM component is always clearly
+Following this rule ensures stateful logic for ReactPy component is always clearly
separated from the rest of your codebase.
Flake8 Plugin
-------------
-We provide a Flake8 plugin called `flake8-idom-hooks `_ that helps
+We provide a Flake8 plugin called `flake8-reactpy-hooks `_ that helps
to enforce the two rules described above. You can ``pip`` install it directly, or with
-the ``lint`` extra for IDOM:
+the ``lint`` extra for ReactPy:
.. code-block:: bash
- pip install flake8-idom-hooks
+ pip install flake8-reactpy-hooks
Once installed running, ``flake8`` on your code will start catching errors. For example:
.. code-block:: bash
- flake8 my_idom_components.py
+ flake8 my_reactpy_components.py
Might produce something like the following output:
.. code-block:: text
- ./my_idom_components:10:8 ROH102 hook 'use_effect' used inside if statement
- ./my_idom_components:23:4 ROH102 hook 'use_state' used outside component or hook definition
+ ./my_reactpy_components:10:8 ROH102 hook 'use_effect' used inside if statement
+ ./my_reactpy_components:23:4 ROH102 hook 'use_state' used outside component or hook definition
See the Flake8 docs for
`more info `__.
@@ -365,4 +376,4 @@ See the Flake8 docs for
.. links
.. =====
-.. _Flake8 Linter Plugin: https://github.com/idom-team/flake8-idom-hooks
+.. _Flake8 Linter Plugin: https://github.com/reactive-python/flake8-reactpy-hooks
diff --git a/docs/source/reference/html-attributes.rst b/docs/source/reference/html-attributes.rst
new file mode 100644
index 000000000..91813c355
--- /dev/null
+++ b/docs/source/reference/html-attributes.rst
@@ -0,0 +1,197 @@
+.. testcode::
+
+ from reactpy import html
+
+
+HTML Attributes
+===============
+
+In ReactPy, HTML attributes are specified using snake_case instead of dash-separated
+words. For example, ``tabindex`` and ``margin-left`` become ``tab_index`` and
+``margin_left`` respectively.
+
+
+Notable Attributes
+-------------------
+
+Some attributes in ReactPy are renamed, have special meaning, or are used differently
+than in HTML.
+
+``style``
+.........
+
+As mentioned above, instead of using a string to specify the ``style`` attribute, we use
+a dictionary to describe the CSS properties we want to apply to an element. For example,
+the following HTML:
+
+.. code-block:: html
+
+
+
My Todo List
+
+ Build a cool new app
+ Share it with the world!
+
+
+
+Would be written in ReactPy as:
+
+.. testcode::
+
+ html.div(
+ {
+ "style": {
+ "width": "50%",
+ "margin_left": "25%",
+ },
+ },
+ html.h1(
+ {
+ "style": {
+ "margin_top": "0px",
+ },
+ },
+ "My Todo List",
+ ),
+ html.ul(
+ html.li("Build a cool new app"),
+ html.li("Share it with the world!"),
+ ),
+ )
+
+``class`` vs ``class_name``
+...........................
+
+In HTML, the ``class`` attribute is used to specify a CSS class for an element. In
+ReactPy, this attribute is renamed to ``class_name`` to avoid conflicting with the
+``class`` keyword in Python. For example, the following HTML:
+
+.. code-block:: html
+
+
+
My Todo List
+
+ Build a cool new app
+ Share it with the world!
+
+
+
+Would be written in ReactPy as:
+
+.. testcode::
+
+ html.div(
+ {"class_name": "container"},
+ html.h1({"class_name": "title"}, "My Todo List"),
+ html.ul(
+ {"class_name": "list"},
+ html.li({"class_name": "item"}, "Build a cool new app"),
+ html.li({"class_name": "item"}, "Share it with the world!"),
+ ),
+ )
+
+``for`` vs ``html_for``
+.......................
+
+In HTML, the ``for`` attribute is used to specify the ``id`` of the element it's
+associated with. In ReactPy, this attribute is renamed to ``html_for`` to avoid
+conflicting with the ``for`` keyword in Python. For example, the following HTML:
+
+.. code-block:: html
+
+
+ Todo:
+
+
+
+Would be written in ReactPy as:
+
+.. testcode::
+
+ html.div(
+ html.label({"html_for": "todo"}, "Todo:"),
+ html.input({"id": "todo", "type": "text"}),
+ )
+
+``dangerously_set_inner_HTML``
+..............................
+
+This is used to set the ``innerHTML`` property of an element and should be provided a
+dictionary with a single key ``__html`` whose value is the HTML to be set. It should be
+used with **extreme caution** as it can lead to XSS attacks if the HTML inside isn't
+trusted (for example if it comes from user input).
+
+
+All Attributes
+--------------
+
+`access_key `__
+ A string. Specifies a keyboard shortcut for the element. Not generally recommended.
+
+`aria_* `__
+ ARIA attributes let you specify the accessibility tree information for this element.
+ See ARIA attributes for a complete reference. In ReactPy, all ARIA attribute names are
+ exactly the same as in HTML.
+
+`auto_capitalize `__
+ A string. Specifies whether and how the user input should be capitalized.
+
+`content_editable `__
+ A boolean. If true, the browser lets the user edit the rendered element directly. This
+ is used to implement rich text input libraries like Lexical. ReactPy warns if you try
+ to pass children to an element with ``content_editable = True`` because ReactPy will
+ not be able to update its content after user edits.
+
+`data_* `__
+ Data attributes let you attach some string data to the element, for example
+ data-fruit="banana". In ReactPy, they are not commonly used because you would usually
+ read data from props or state instead.
+
+`dir `__
+ Either ``"ltr"`` or ``"rtl"``. Specifies the text direction of the element.
+
+`draggable `__
+ A boolean. Specifies whether the element is draggable. Part of HTML Drag and Drop API.
+
+`enter_key_hint `__
+ A string. Specifies which action to present for the enter key on virtual keyboards.
+
+`hidden `__
+ A boolean or a string. Specifies whether the element should be hidden.
+
+- `id `__:
+ A string. Specifies a unique identifier for this element, which can be used to find it
+ later or connect it with other elements. Generate it with useId to avoid clashes
+ between multiple instances of the same component.
+
+`is `__
+ A string. If specified, the component will behave like a custom element.
+
+`input_mode `__
+ A string. Specifies what kind of keyboard to display (for example, text, number, or telephone).
+
+`item_prop `__
+ A string. Specifies which property the element represents for structured data crawlers.
+
+`lang `__
+ A string. Specifies the language of the element.
+
+`role `__
+ A string. Specifies the element role explicitly for assistive technologies.
+
+`slot `__
+ A string. Specifies the slot name when using shadow DOM. In ReactPy, an equivalent
+ pattern is typically achieved by passing JSX as props, for example
+ `` } right={ } />``.
+
+`spell_check `__
+ A boolean or null. If explicitly set to true or false, enables or disables spellchecking.
+
+`tab_index `__
+ A number. Overrides the default Tab button behavior. Avoid using values other than -1 and 0.
+
+`title `__
+ A string. Specifies the tooltip text for the element.
+
+`translate `__
+ Either 'yes' or 'no'. Passing 'no' excludes the element content from being translated.
diff --git a/docs/source/reference-material/javascript-api.rst b/docs/source/reference/javascript-api.rst
similarity index 100%
rename from docs/source/reference-material/javascript-api.rst
rename to docs/source/reference/javascript-api.rst
diff --git a/docs/source/reference-material/specifications.rst b/docs/source/reference/specifications.rst
similarity index 95%
rename from docs/source/reference-material/specifications.rst
rename to docs/source/reference/specifications.rst
index c2f9f68e4..3a5c94893 100644
--- a/docs/source/reference-material/specifications.rst
+++ b/docs/source/reference/specifications.rst
@@ -5,7 +5,7 @@ Describes various data structures and protocols used to define and communicate v
document object models (:ref:`VDOM`). The definitions below follow in the footsteps of
`a specification `_
created by `Nteract `_ and which was built into
-`JupyterLab `_. While IDOM's specification
+`JupyterLab `_. While ReactPy's specification
for VDOM is fairly well established, it should not be relied until it's been fully
adopted by the aforementioned organizations.
@@ -13,7 +13,7 @@ adopted by the aforementioned organizations.
VDOM
----
-A set of definitions that explain how IDOM creates a virtual representation of
+A set of definitions that explain how ReactPy creates a virtual representation of
the document object model. We'll begin by looking at a bit of HTML that we'll convert
into its VDOM representation:
@@ -35,13 +35,13 @@ into its VDOM representation:
.. code-block:: python
- import idom
+ import reactpy
async def a_python_callback(new):
...
- name_input_view = idom.html.div(
- idom.html.input(
+ name_input_view = reactpy.html.div(
+ reactpy.html.input(
{
"type": "text",
"minLength": 4,
diff --git a/docs/source/understanding-idom/why-idom-needs-keys.rst b/docs/source/understanding-idom/why-idom-needs-keys.rst
deleted file mode 100644
index 02d8613c4..000000000
--- a/docs/source/understanding-idom/why-idom-needs-keys.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-.. _Why IDOM Needs Keys:
-
-Why IDOM Needs Keys đ§
-======================
-
-.. note::
-
- Under construction đ§
diff --git a/noxfile.py b/noxfile.py
deleted file mode 100644
index 77b66c3b7..000000000
--- a/noxfile.py
+++ /dev/null
@@ -1,392 +0,0 @@
-from __future__ import annotations
-
-import functools
-import os
-import re
-from pathlib import Path
-from typing import Any, Callable, TypeVar
-
-import nox
-from nox.sessions import Session
-
-
-ROOT = Path(__file__).parent
-SRC = ROOT / "src"
-POSARGS_PATTERN = re.compile(r"^(\w+)\[(.+)\]$")
-TRUE_VALUES = {"true", "True", "TRUE", "1"}
-
-
-_Return = TypeVar("_Return")
-
-
-def do_first(
- first_session_func: Callable[[Session], None]
-) -> Callable[[Callable[[Session], _Return]], Callable[[Session], _Return]]:
- """Decorator for functions defining session actions that should happen first
-
- >>> @do_first
- >>> def setup(session):
- >>> ... # do some setup
- >>>
- >>> @setup
- >>> def the_actual_session(session):
- >>> ... # so actual work
-
- This makes it quick an easy to define common setup actions.
- """
-
- def setup(
- second_session_func: Callable[[Session], _Return]
- ) -> Callable[[Session], _Return]:
- @functools.wraps(second_session_func)
- def wrapper(session: Session) -> Any:
- first_session_func(session)
- return second_session_func(session)
-
- return wrapper
-
- return setup
-
-
-@do_first
-def apply_standard_pip_upgrades(session: Session) -> None:
- session.install("--upgrade", "pip")
-
-
-@do_first
-def install_latest_npm_in_ci(session: Session) -> None:
- if os.environ.get("CI") in TRUE_VALUES:
- session.log("Running in CI environment - installing latest NPM")
- session.run("npm", "install", "-g", "npm@latest", external=True)
-
-
-@nox.session(reuse_venv=True)
-@apply_standard_pip_upgrades
-def format(session: Session) -> None:
- """Auto format Python and Javascript code"""
- # format Python
- install_requirements_file(session, "check-style")
- session.run("black", ".")
- session.run("isort", ".")
-
- # format client Javascript
- session.chdir(SRC / "client")
- session.run("npm", "run", "format", external=True)
-
- # format docs Javascript
- session.chdir(ROOT / "docs" / "source" / "_custom_js")
- session.run("npm", "run", "format", external=True)
-
-
-@nox.session(reuse_venv=True)
-@apply_standard_pip_upgrades
-def example(session: Session) -> None:
- """Run an example"""
- session.install("matplotlib")
- install_idom_dev(session)
- session.run(
- "python",
- "scripts/one_example.py",
- *session.posargs,
- env=get_idom_script_env(),
- )
-
-
-@nox.session(reuse_venv=True)
-@install_latest_npm_in_ci
-@apply_standard_pip_upgrades
-def docs(session: Session) -> None:
- """Build and display documentation in the browser (automatically reloads on change)"""
- install_requirements_file(session, "build-docs")
- install_idom_dev(session, extras="all")
- session.run(
- "python",
- "scripts/live_docs.py",
- "--open-browser",
- # watch python source too
- "--watch=src/idom",
- # for some reason this matches absolute paths
- "--ignore=**/_auto/*",
- "--ignore=**/_static/custom.js",
- "--ignore=**/node_modules/*",
- "--ignore=**/package-lock.json",
- "-a",
- "-E",
- "-b",
- "html",
- "docs/source",
- "docs/build",
- env={**os.environ, **get_idom_script_env()},
- )
-
-
-@nox.session
-def docs_in_docker(session: Session) -> None:
- """Build a docker image for the documentation and run it to mimic production"""
- session.run(
- "docker",
- "build",
- ".",
- "--file",
- "docs/Dockerfile",
- "--tag",
- "idom-docs:latest",
- external=True,
- )
- session.run(
- "docker",
- "run",
- "-it",
- "-p",
- "5000:5000",
- "-e",
- "DEBUG=1",
- "--rm",
- "idom-docs:latest",
- external=True,
- )
-
-
-@nox.session
-def test(session: Session) -> None:
- """Run the complete test suite"""
- session.notify("test_python", posargs=session.posargs)
- session.notify("test_docs")
- session.notify("test_javascript")
-
-
-@nox.session
-def test_python(session: Session) -> None:
- """Run all Python checks"""
- session.notify("test_python_suite", posargs=session.posargs)
- session.notify("test_python_types")
- session.notify("test_python_style")
- session.notify("test_python_build")
-
-
-@nox.session
-def test_javascript(session: Session) -> None:
- """Run all Javascript checks"""
- session.notify("test_javascript_suite")
- session.notify("test_javascript_build")
- session.notify("test_javascript_style")
-
-
-@nox.session
-@install_latest_npm_in_ci
-@apply_standard_pip_upgrades
-def test_python_suite(session: Session) -> None:
- """Run the Python-based test suite"""
- session.env["IDOM_DEBUG_MODE"] = "1"
- install_requirements_file(session, "test-env")
-
- posargs = session.posargs
- posargs += ["--reruns", "3", "--reruns-delay", "1"]
-
- if "--no-cov" in session.posargs:
- session.log("Coverage won't be checked")
- session.install(".[all]")
- else:
- posargs += ["--cov=src/idom", "--cov-report", "term"]
- install_idom_dev(session, extras="all")
-
- session.run("pytest", *posargs)
-
-
-@nox.session
-@apply_standard_pip_upgrades
-def test_python_types(session: Session) -> None:
- """Perform a static type analysis of the Python codebase"""
- install_requirements_file(session, "check-types")
- install_requirements_file(session, "pkg-deps")
- install_requirements_file(session, "pkg-extras")
- session.run("mypy", "--strict", "src/idom")
-
-
-@nox.session
-@apply_standard_pip_upgrades
-def test_python_style(session: Session) -> None:
- """Check that Python style guidelines are being followed"""
- install_requirements_file(session, "check-style")
- session.run("flake8", "src/idom", "tests", "docs")
- black_default_exclude = r"\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|\.svn|_build|buck-out|build|dist"
- session.run(
- "black",
- ".",
- "--check",
- "--exclude",
- rf"/({black_default_exclude}|venv|node_modules)/",
- )
- session.run("isort", ".", "--check-only")
-
-
-@nox.session
-@apply_standard_pip_upgrades
-def test_python_build(session: Session) -> None:
- """Test whether the Python package can be build for distribution"""
- install_requirements_file(session, "build-pkg")
- session.run("python", "-m", "build", "--sdist", "--wheel", "--outdir", "dist", ".")
-
-
-@nox.session
-@install_latest_npm_in_ci
-@apply_standard_pip_upgrades
-def test_docs(session: Session) -> None:
- """Verify that the docs build and that doctests pass"""
- install_requirements_file(session, "build-docs")
- install_idom_dev(session, extras="all")
- session.run(
- "sphinx-build",
- "-a", # re-write all output files
- "-T", # show full tracebacks
- "-W", # turn warnings into errors
- "--keep-going", # complete the build, but still report warnings as errors
- "-b",
- "html",
- "docs/source",
- "docs/build",
- )
- session.run("sphinx-build", "-b", "doctest", "docs/source", "docs/build")
- # ensure docker image build works too
- session.run("docker", "build", ".", "--file", "docs/Dockerfile", external=True)
-
-
-@do_first
-@install_latest_npm_in_ci
-def setup_client_env(session: Session) -> None:
- session.chdir(SRC / "client")
- session.run("npm", "install", external=True)
-
-
-@nox.session
-@setup_client_env
-def test_javascript_suite(session: Session) -> None:
- """Run the Javascript-based test suite and ensure it bundles succesfully"""
- session.run("npm", "run", "test", external=True)
-
-
-@nox.session
-@setup_client_env
-def test_javascript_build(session: Session) -> None:
- """Run the Javascript-based test suite and ensure it bundles succesfully"""
- session.run("npm", "run", "test", external=True)
-
-
-@nox.session
-@setup_client_env
-def test_javascript_style(session: Session) -> None:
- """Check that Javascript style guidelines are being followed"""
- session.run("npm", "run", "check-format", external=True)
-
-
-@nox.session
-def build_js(session: Session) -> None:
- """Build javascript client code"""
- session.chdir(SRC / "client")
- session.run("npm", "run", "build", external=True)
-
-
-@nox.session
-def tag(session: Session) -> None:
- """Create a new git tag"""
- try:
- session.run(
- "git",
- "diff",
- "--cached",
- "--exit-code",
- silent=True,
- external=True,
- )
- except Exception:
- session.error("Cannot create a tag - there are uncommited changes")
-
- version = get_version()
- install_requirements_file(session, "make-release")
- session.run("pysemver", "check", version)
-
- changelog_file = ROOT / "docs" / "source" / "developing-idom" / "changelog.rst"
- for line in changelog_file.read_text().splitlines():
- if line == version:
- session.log(f"Found changelog section for version {version}")
- break
- else:
- session.error(
- f"No changelog entry for {version} in {changelog_file} - "
- f"make sure you have a title section called {version}."
- )
-
- session.run("git", "tag", version, external=True)
-
- if "push" in session.posargs:
- session.run("git", "push", "--tags", external=True)
-
-
-@nox.session
-def update_version(session: Session) -> None:
- """Update the version of all Python and Javascript packages in this repo"""
- if len(session.posargs) > 1:
- session.error("To many arguments")
-
- try:
- new_version = session.posargs[0]
- except IndexError:
- session.error("No version tag given")
-
- install_requirements_file(session, "make-release")
-
- # check that version is valid semver
- session.run("pysemver", "check", new_version)
-
- old_version = get_version()
- session.log(f"Old version: {old_version}")
- session.log(f"New version: {new_version}")
- set_version(new_version)
-
- session.run("python", "scripts/update_versions.py")
-
- # trigger npm install to update package-lock.json
- session.install("-e", ".")
-
-
-@nox.session(reuse_venv=True)
-def latest_pull_requests(session: Session) -> None:
- """A basic script for outputing changelog info"""
- session.install("requests", "python-dateutil")
- session.run("python", "scripts/latest_pull_requests.py", *session.posargs)
-
-
-@nox.session(reuse_venv=True)
-def latest_closed_issues(session: Session) -> None:
- """A basic script for outputing changelog info"""
- session.install("requests", "python-dateutil")
- session.run("python", "scripts/latest_closed_issues.py", *session.posargs)
-
-
-def install_requirements_file(session: Session, name: str) -> None:
- file_path = ROOT / "requirements" / (name + ".txt")
- assert file_path.exists(), f"requirements file {file_path} does not exist"
- session.install("-r", str(file_path))
-
-
-def install_idom_dev(session: Session, extras: str = "stable") -> None:
- if "--no-install" not in session.posargs:
- session.install("-e", f".[{extras}]")
- else:
- session.posargs.remove("--no-install")
-
-
-def get_version() -> str:
- return (ROOT / "VERSION").read_text().strip()
-
-
-def set_version(new: str) -> None:
- (ROOT / "VERSION").write_text(new.strip() + "\n")
-
-
-def get_idom_script_env() -> dict[str, str]:
- return {
- "PYTHONPATH": os.getcwd(),
- "IDOM_DEBUG_MODE": os.environ.get("IDOM_DEBUG_MODE", "1"),
- "IDOM_CHECK_VDOM_SPEC": os.environ.get("IDOM_CHECK_VDOM_SPEC", "0"),
- }
diff --git a/pyproject.toml b/pyproject.toml
index 18a77f5a7..775ab01a2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,12 +1,157 @@
-[build-system]
-requires = ["setuptools>=42", "wheel"]
-build-backend = "setuptools.build_meta"
-
-[tool.isort]
-multi_line_output = 3
-force_grid_wrap = 0
-use_parentheses = "True"
-ensure_newline_before_comments = "True"
-include_trailing_comma = "True"
-line_length = 88
-lines_after_imports = 2
+# --- Project ----------------------------------------------------------------------------
+
+[project]
+name = "scripts"
+version = "0.0.0"
+description = "Scripts for managing the ReactPy repository"
+
+# --- Hatch ----------------------------------------------------------------------------
+
+[tool.hatch.envs.default]
+detached = true
+dependencies = [
+ "invoke",
+ # lint
+ "black==24.1.1", # Pin lint tools we don't control to avoid breaking changes
+ "ruff==0.0.278", # Pin lint tools we don't control to avoid breaking changes
+ "toml",
+ "flake8==7.0.0", # Pin lint tools we don't control to avoid breaking changes
+ "flake8-pyproject",
+ "reactpy-flake8 >=0.7",
+ # types
+ "mypy",
+ "types-toml",
+ # publish
+ "semver >=2, <3",
+ "twine",
+ "pre-commit",
+]
+
+[tool.hatch.envs.default.scripts]
+publish = "invoke publish {args}"
+docs = "invoke docs {args}"
+check = ["lint-py", "lint-js", "test-py", "test-js", "test-docs"]
+
+lint = ["lint-py", "lint-js"]
+lint-py = "invoke lint-py {args}"
+lint-js = "invoke lint-js {args}"
+
+test = ["test-py", "test-js", "test-docs"]
+test-py = "invoke test-py {args}"
+test-js = "invoke test-js"
+test-docs = "invoke test-docs"
+
+# --- Black ----------------------------------------------------------------------------
+
+[tool.black]
+target-version = ["py39"]
+line-length = 88
+
+# --- MyPy -----------------------------------------------------------------------------
+
+[tool.mypy]
+ignore_missing_imports = true
+warn_unused_configs = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+
+# --- Flake8 ---------------------------------------------------------------------------
+
+[tool.flake8]
+select = ["RPY"] # only need to check with reactpy-flake8
+exclude = ["**/node_modules/*", ".eggs/*", ".tox/*", "**/venv/*"]
+
+# --- Ruff -----------------------------------------------------------------------------
+
+[tool.ruff]
+target-version = "py39"
+line-length = 88
+select = [
+ "A",
+ "ARG",
+ "B",
+ "C",
+ "DTZ",
+ "E",
+ # error message linting is overkill
+ # "EM",
+ "F",
+ # TODO: turn this on later
+ # "FBT",
+ "I",
+ "ICN",
+ "ISC",
+ "N",
+ "PLC",
+ "PLE",
+ "PLR",
+ "PLW",
+ "Q",
+ "RUF",
+ "S",
+ "T",
+ "TID",
+ "UP",
+ "W",
+ "YTT",
+]
+ignore = [
+ # TODO: turn this on later
+ "N802",
+ "N806", # allow TitleCase functions/variables
+ # We're not any cryptography
+ "S311",
+ # For loop variable re-assignment seems like an uncommon mistake
+ "PLW2901",
+ # Let Black deal with line-length
+ "E501",
+ # Allow args/attrs to shadow built-ins
+ "A002",
+ "A003",
+ # Allow unused args (useful for documenting what the parameter is for later)
+ "ARG001",
+ "ARG002",
+ "ARG005",
+ # Allow non-abstract empty methods in abstract base classes
+ "B027",
+ # Allow boolean positional values in function calls, like `dict.get(... True)`
+ "FBT003",
+ # If we're making an explicit comparison to a falsy value it was probably intentional
+ "PLC1901",
+ # Ignore checks for possible passwords
+ "S105",
+ "S106",
+ "S107",
+ # Ignore complexity
+ "C901",
+ "PLR0911",
+ "PLR0912",
+ "PLR0913",
+ "PLR0915",
+]
+unfixable = [
+ # Don't touch unused imports
+ "F401",
+]
+
+[tool.ruff.isort]
+known-first-party = ["reactpy"]
+
+[tool.ruff.flake8-tidy-imports]
+ban-relative-imports = "all"
+
+[tool.ruff.per-file-ignores]
+# Tests can use magic values, assertions, and relative imports
+"**/tests/**/*" = ["PLR2004", "S101", "TID252"]
+"docs/**/*.py" = [
+ # Examples require some extra setup before import
+ "E402",
+ # Allow exec
+ "S102",
+ # Allow print
+ "T201",
+]
+"scripts/**/*.py" = [
+ # Allow print
+ "T201",
+]
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index dab76855e..000000000
--- a/requirements.txt
+++ /dev/null
@@ -1,9 +0,0 @@
--r requirements/build-docs.txt
--r requirements/build-pkg.txt
--r requirements/check-style.txt
--r requirements/check-types.txt
--r requirements/make-release.txt
--r requirements/pkg-deps.txt
--r requirements/pkg-extras.txt
--r requirements/test-env.txt
--r requirements/nox-deps.txt
diff --git a/requirements/build-docs.txt b/requirements/build-docs.txt
deleted file mode 100644
index c47d862c0..000000000
--- a/requirements/build-docs.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-sphinx ==4.1.2
-sphinx-autodoc-typehints ==1.7.0
-furo ==2021.10.09
-setuptools_scm
-sphinx-copybutton ==0.3.0
-sphinx-autobuild ==2020.9.1
-sphinx-reredirects ==0.0.1
-sphinx-design ==0.0.13
-sphinx-resolve-py-references ==0.1.0
-sphinxext-opengraph ==0.5.1
diff --git a/requirements/build-pkg.txt b/requirements/build-pkg.txt
deleted file mode 100644
index 82f40eafa..000000000
--- a/requirements/build-pkg.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-twine
-wheel
-build
diff --git a/requirements/check-style.txt b/requirements/check-style.txt
deleted file mode 100644
index df22cfea8..000000000
--- a/requirements/check-style.txt
+++ /dev/null
@@ -1,7 +0,0 @@
-black
-flake8
-flake8-idom-hooks >=0.5.0
-flake8-print
-flake8-tidy-imports
-isort >=5.7.0
-pep8-naming
diff --git a/requirements/check-types.txt b/requirements/check-types.txt
deleted file mode 100644
index 2ae26d60a..000000000
--- a/requirements/check-types.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-mypy
-types-click
-types-tornado
-types-pkg-resources
-types-flask
-types-requests
diff --git a/requirements/make-release.txt b/requirements/make-release.txt
deleted file mode 100644
index da7b791ef..000000000
--- a/requirements/make-release.txt
+++ /dev/null
@@ -1 +0,0 @@
-semver >=2, <3
diff --git a/requirements/nox-deps.txt b/requirements/nox-deps.txt
deleted file mode 100644
index 816817c67..000000000
--- a/requirements/nox-deps.txt
+++ /dev/null
@@ -1 +0,0 @@
-nox
diff --git a/requirements/pkg-deps.txt b/requirements/pkg-deps.txt
deleted file mode 100644
index f4d440e32..000000000
--- a/requirements/pkg-deps.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-typing-extensions >=3.10
-mypy-extensions >=0.4.3
-anyio >=3.0
-jsonpatch >=1.32
-fastjsonschema >=2.14.5
-requests >=2.0
diff --git a/requirements/pkg-extras.txt b/requirements/pkg-extras.txt
deleted file mode 100644
index 4cc2425eb..000000000
--- a/requirements/pkg-extras.txt
+++ /dev/null
@@ -1,25 +0,0 @@
-# extra=stable,sanic
-sanic <19.12.0
-sanic-cors
-
-# extra=fastapi
-fastapi >=0.63.0
-uvicorn[standard] >=0.13.4
-
-# extra=starlette
-fastapi >=0.16.0
-uvicorn[standard] >=0.13.4
-
-# extra=flask
-flask<2.0
-flask-cors
-flask-sockets
-
-# tornado
-tornado
-
-# extra=testing
-selenium
-
-# extra=matplotlib
-matplotlib
diff --git a/requirements/test-env.txt b/requirements/test-env.txt
deleted file mode 100644
index c277b335c..000000000
--- a/requirements/test-env.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-ipython
-pytest
-pytest-asyncio
-pytest-cov
-pytest-mock
-pytest-rerunfailures
-pytest-timeout
-responses
-selenium
diff --git a/scripts/README.md b/scripts/README.md
deleted file mode 100644
index a9e8fa35c..000000000
--- a/scripts/README.md
+++ /dev/null
@@ -1,5 +0,0 @@
-# Scripts
-
-All scripts should be run from the repository root (typically using
-[`nox`](https://nox.thea.codes/en/stable/)). Use `nox --list` to see the list of
-available scripts.
diff --git a/scripts/common/github_utils.py b/scripts/common/github_utils.py
deleted file mode 100644
index d7e892894..000000000
--- a/scripts/common/github_utils.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from __future__ import annotations
-
-from datetime import datetime
-from typing import Any, Iterator, Optional
-
-import requests
-from dateutil.parser import isoparse
-
-
-REPO_NAME = "idom-team/idom"
-
-
-def last_release_date() -> datetime:
- response = requests.get(f"https://api.github.com/repos/{REPO_NAME}/releases/latest")
- return isoparse(response.json()["published_at"])
-
-
-def date_range_query(
- start: Optional[datetime] = None,
- stop: Optional[datetime] = None,
-) -> str:
- assert start or stop, "No date range given"
-
- if stop is None:
- return ">" + start.isoformat()
-
- if start is None:
- return "<=" + stop.isoformat()
-
- return start.isoformat() + ".." + stop.isoformat()
-
-
-def search_idom_repo(query: str) -> Iterator[Any]:
- page = 0
- while True:
- page += 1
- response = requests.get(
- "https://api.github.com/search/issues",
- {"q": f"repo:{REPO_NAME} " + query, "per_page": 15, "page": page},
- )
-
- response_json = response.json()
-
- try:
- if response_json["incomplete_results"]:
- raise RuntimeError(response)
- except KeyError:
- raise RuntimeError(response_json)
-
- items = response_json["items"]
- if items:
- yield from items
- else:
- break
diff --git a/scripts/latest_closed_issues.py b/scripts/latest_closed_issues.py
deleted file mode 100644
index ae39b40eb..000000000
--- a/scripts/latest_closed_issues.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from __future__ import annotations
-
-import sys
-
-from common.github_utils import (
- REPO_NAME,
- date_range_query,
- last_release_date,
- search_idom_repo,
-)
-
-
-FORMAT_TEMPLATES = {
- "md": f"- {{title}} - [#{{number}}](https://github.com/{REPO_NAME}/issues/{{number}})",
- "rst": "- {title} - :issue:`{number}`",
- "text": "- {title} - #{number}",
-}
-
-
-def main(format: str = "text"):
- template = FORMAT_TEMPLATES[format]
- query = f"type:issue closed:{date_range_query(last_release_date())}"
- for pr in search_idom_repo(query):
- print(template.format(**pr))
-
-
-if __name__ == "__main__":
- main(*sys.argv[1:])
diff --git a/scripts/latest_pull_requests.py b/scripts/latest_pull_requests.py
deleted file mode 100644
index 0daae3e5a..000000000
--- a/scripts/latest_pull_requests.py
+++ /dev/null
@@ -1,28 +0,0 @@
-from __future__ import annotations
-
-import sys
-
-from common.github_utils import (
- REPO_NAME,
- date_range_query,
- last_release_date,
- search_idom_repo,
-)
-
-
-FORMAT_TEMPLATES = {
- "md": f"- {{title}} - [#{{number}}](https://github.com/{REPO_NAME}/pull/{{number}})",
- "rst": "- {title} - :pull:`{number}`",
- "text": "- {title} - #{number}",
-}
-
-
-def main(format: str = "text"):
- template = FORMAT_TEMPLATES[format]
- query = f"type:pr merged:{date_range_query(last_release_date())}"
- for pr in search_idom_repo(query):
- print(template.format(**pr))
-
-
-if __name__ == "__main__":
- main(*sys.argv[1:])
diff --git a/scripts/live_docs.py b/scripts/live_docs.py
deleted file mode 100644
index f0173d436..000000000
--- a/scripts/live_docs.py
+++ /dev/null
@@ -1,80 +0,0 @@
-import os
-
-from sphinx_autobuild.cli import (
- Server,
- _get_build_args,
- _get_ignore_handler,
- find_free_port,
- get_builder,
- get_parser,
-)
-
-from docs.app import IDOM_MODEL_SERVER_URL_PREFIX, make_app, make_examples_component
-from idom.server.sanic import PerClientStateServer
-from idom.testing import clear_idom_web_modules_dir
-
-
-# these environment variable are used in custom Sphinx extensions
-os.environ["IDOM_DOC_EXAMPLE_SERVER_HOST"] = "127.0.0.1:5555"
-os.environ["IDOM_DOC_STATIC_SERVER_HOST"] = ""
-
-_running_idom_servers = []
-
-
-def wrap_builder(old_builder):
- # This is the bit that we're injecting to get the example components to reload too
- def new_builder():
- [s.stop() for s in _running_idom_servers]
- clear_idom_web_modules_dir()
-
- server = PerClientStateServer(
- make_examples_component(),
- {"cors": True, "url_prefix": IDOM_MODEL_SERVER_URL_PREFIX},
- make_app(),
- )
- server.run_in_thread("127.0.0.1", 5555, debug=True)
- _running_idom_servers.append(server)
- server.wait_until_started()
-
- old_builder()
-
- return new_builder
-
-
-def main():
- # Mostly copied from https://github.com/executablebooks/sphinx-autobuild/blob/b54fb08afc5112bfcda1d844a700c5a20cd6ba5e/src/sphinx_autobuild/cli.py
- parser = get_parser()
- args = parser.parse_args()
-
- srcdir = os.path.realpath(args.sourcedir)
- outdir = os.path.realpath(args.outdir)
- if not os.path.exists(outdir):
- os.makedirs(outdir)
-
- server = Server()
-
- build_args, pre_build_commands = _get_build_args(args)
- builder = wrap_builder(
- get_builder(server.watcher, build_args, pre_build_commands=pre_build_commands)
- )
-
- ignore_handler = _get_ignore_handler(args)
- server.watch(srcdir, builder, ignore=ignore_handler)
- for dirpath in args.additional_watched_dirs:
- dirpath = os.path.realpath(dirpath)
- server.watch(dirpath, builder, ignore=ignore_handler)
- server.watch(outdir, ignore=ignore_handler)
-
- if not args.no_initial_build:
- builder()
-
- # Find the free port
- portn = args.port or find_free_port()
- if args.openbrowser is True:
- server.serve(port=portn, host=args.host, root=outdir, open_url_delay=args.delay)
- else:
- server.serve(port=portn, host=args.host, root=outdir)
-
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/one_example.py b/scripts/one_example.py
deleted file mode 100644
index 745265e7a..000000000
--- a/scripts/one_example.py
+++ /dev/null
@@ -1,103 +0,0 @@
-import sys
-import time
-from os.path import getmtime
-from threading import Event, Thread
-
-import idom
-from docs.examples import all_example_names, get_example_files_by_name, load_one_example
-from idom.widgets import hotswap
-
-
-EXAMPLE_NAME_SET = all_example_names()
-EXAMPLE_NAME_LIST = tuple(sorted(EXAMPLE_NAME_SET))
-
-
-def on_file_change(path, callback):
- did_call_back_once = Event()
-
- def watch_for_change():
- last_modified = 0
- while True:
- modified_at = getmtime(path)
- if modified_at != last_modified:
- callback()
- did_call_back_once.set()
- last_modified = modified_at
- time.sleep(1)
-
- Thread(target=watch_for_change, daemon=True).start()
- did_call_back_once.wait()
-
-
-def main():
- ex_name = _example_name_input()
-
- mount, component = hotswap(update_on_change=True)
-
- def update_component():
- print(f"Loading example: {ex_name!r}")
- mount(load_one_example(ex_name))
-
- for file in get_example_files_by_name(ex_name):
- on_file_change(file, update_component)
-
- idom.run(component)
-
-
-def _example_name_input() -> str:
- if len(sys.argv) == 1:
- _print_error(
- "No example argument given. Provide an example's number from above."
- )
- sys.exit(1)
-
- ex_num = sys.argv[1]
-
- try:
- ex_num = int(ex_num)
- except ValueError:
- _print_error(
- f"No example {ex_num!r} exists. Provide an example's number as an integer."
- )
- sys.exit(1)
-
- ex_index = ex_num - 1
- try:
- return EXAMPLE_NAME_LIST[ex_index]
- except IndexError:
- _print_error(f"No example #{ex_num} exists. Choose from an option above.")
- sys.exit(1)
-
-
-def _print_error(*args) -> None:
- _print_available_options()
- print(*args)
-
-
-def _print_available_options():
- examples_by_path = {}
- for i, name in enumerate(EXAMPLE_NAME_LIST):
- if "/" not in name:
- path = ""
- else:
- path, name = name.rsplit("/", 1)
- examples_by_path.setdefault(path, []).append(name)
-
- number = 1
- print()
- for path, names in examples_by_path.items():
- title = " > ".join(
- section.replace("-", " ").replace("_", " ").title()
- for section in path.split("/")
- if not section.startswith("_")
- )
- print(title)
- print("-" * len(title))
- for name in names:
- print(f"{number}. ", name)
- number += 1
- print()
-
-
-if __name__ == "__main__":
- main()
diff --git a/scripts/run_docs.py b/scripts/run_docs.py
deleted file mode 100644
index 4a224ece8..000000000
--- a/scripts/run_docs.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import os
-import sys
-
-
-# all scripts should be run from the repository root so we need to insert cwd to path
-# to import docs
-sys.path.insert(0, os.getcwd())
-
-
-if __name__ == "__main__":
- from docs import run
-
- run()
diff --git a/scripts/update_versions.py b/scripts/update_versions.py
deleted file mode 100644
index 5da160625..000000000
--- a/scripts/update_versions.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import json
-from pathlib import Path
-
-import semver
-
-
-ROOT = Path("__file__").parent.parent
-VERSION_FILE = ROOT / Path("VERSION")
-PY_PKG_INIT_FILE = ROOT / "src" / "idom" / "__init__.py"
-JS_ROOT_DIR = ROOT / "src" / "client"
-JS_PACKAGE_JSON_FILES = [
- pkg_dir / "package.json" for pkg_dir in (JS_ROOT_DIR / "packages").iterdir()
-] + [JS_ROOT_DIR / "package.json"]
-
-VERSION_INFO = semver.VersionInfo.parse(VERSION_FILE.read_text().strip())
-
-
-def main() -> None:
- version_str = str(VERSION_INFO)
- update_py_version(version_str)
- update_js_versions(version_str)
-
-
-def update_py_version(new_version: str) -> None:
- new_lines = PY_PKG_INIT_FILE.read_text().splitlines()
- for index, line in enumerate(new_lines):
- if line.startswith('__version__ = "') and line.endswith('" # DO NOT MODIFY'):
- line = f'__version__ = "{new_version}" # DO NOT MODIFY'
- new_lines[index] = line
- break
- else:
- raise RuntimeError(f"No __version__ assignment found in {PY_PKG_INIT_FILE}")
- PY_PKG_INIT_FILE.write_text("\n".join(new_lines) + "\n")
-
-
-def update_js_versions(new_version: str) -> None:
- for pkg_json_file in JS_PACKAGE_JSON_FILES:
- pkg_json = json.loads(pkg_json_file.read_text())
- pkg_json["version"] = new_version
- pkg_json_file.write_text(json.dumps(pkg_json, indent=2, sort_keys=True) + "\n")
-
-
-if __name__ == "__main__":
- main()
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 4359702ff..000000000
--- a/setup.cfg
+++ /dev/null
@@ -1,44 +0,0 @@
-[bdist_wheel]
-universal=1
-
-[mypy]
-ignore_missing_imports = True
-warn_unused_configs = True
-warn_redundant_casts = True
-warn_unused_ignores = True
-
-[flake8]
-ignore = E203, E266, E501, W503, F811, N802, N806
-max-line-length = 88
-max-complexity = 18
-select = B,C,E,F,W,T4,B9,N,ROH
-exclude =
- **/node_modules/*
- .eggs/*
- .tox/*
-# -- flake8-tidy-imports --
-ban-relative-imports = parents
-
-[tool:pytest]
-testpaths = tests
-xfail_strict = True
-markers =
- slow: marks tests as slow (deselect with '-m "not slow"')
-
-[coverage:report]
-fail_under = 100
-show_missing = True
-skip_covered = True
-sort = Name
-exclude_lines =
- pragma: no cover
- \.\.\.
- raise NotImplementedError
-omit =
- src/idom/__main__.py
- src/idom/core/_fixed_jsonpatch.py
-
-[build_sphinx]
-all-files = true
-source-dir = docs/source
-build-dir = docs/build
diff --git a/setup.py b/setup.py
deleted file mode 100644
index f89c04e07..000000000
--- a/setup.py
+++ /dev/null
@@ -1,181 +0,0 @@
-from __future__ import print_function
-
-import pipes
-import shutil
-import subprocess
-import sys
-import traceback
-from distutils import log
-from distutils.command.build import build # type: ignore
-from distutils.command.sdist import sdist # type: ignore
-from pathlib import Path
-
-from setuptools import find_packages, setup
-from setuptools.command.develop import develop
-
-
-if sys.platform == "win32":
- from subprocess import list2cmdline
-else:
-
- def list2cmdline(cmd_list):
- return " ".join(map(pipes.quote, cmd_list))
-
-
-# the name of the project
-NAME = "idom"
-
-# basic paths used to gather files
-ROOT_DIR = Path(__file__).parent
-SRC_DIR = ROOT_DIR / "src"
-PKG_DIR = SRC_DIR / NAME
-JS_DIR = SRC_DIR / "client"
-
-
-# -----------------------------------------------------------------------------
-# Package Definition
-# -----------------------------------------------------------------------------
-
-
-package = {
- "name": NAME,
- "python_requires": ">=3.7",
- "packages": find_packages(str(SRC_DIR)),
- "package_dir": {"": "src"},
- "description": "It's React, but in Python",
- "author": "Ryan Morshead",
- "author_email": "ryan.morshead@gmail.com",
- "url": "https://github.com/rmorshea/idom",
- "license": "MIT",
- "platforms": "Linux, Mac OS X, Windows",
- "keywords": ["interactive", "widgets", "DOM", "React"],
- "include_package_data": True,
- "zip_safe": False,
- "classifiers": [
- "Environment :: Web Environment",
- "Framework :: AsyncIO",
- "Framework :: Flask",
- "Intended Audience :: Developers",
- "Intended Audience :: Science/Research",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Topic :: Software Development :: User Interfaces",
- "Topic :: Software Development :: Widget Sets",
- "Typing :: Typed",
- ],
-}
-
-
-# -----------------------------------------------------------------------------
-# Library Version
-# -----------------------------------------------------------------------------
-
-pkg_root_init_file = PKG_DIR / "__init__.py"
-for line in pkg_root_init_file.read_text().split("\n"):
- if line.startswith('__version__ = "') and line.endswith('" # DO NOT MODIFY'):
- package["version"] = (
- line
- # get assignment value
- .split("=", 1)[1]
- # remove "DO NOT MODIFY" comment
- .split("#", 1)[0]
- # clean up leading/trailing space
- .strip()
- # remove the quotes
- [1:-1]
- )
- break
-else:
- print(f"No version found in {pkg_root_init_file}")
- sys.exit(1)
-
-
-# -----------------------------------------------------------------------------
-# Requirements
-# -----------------------------------------------------------------------------
-
-
-requirements = []
-with (ROOT_DIR / "requirements" / "pkg-deps.txt").open() as f:
- for line in map(str.strip, f):
- if not line.startswith("#"):
- requirements.append(line)
-package["install_requires"] = requirements
-
-_current_extras = []
-extra_requirements = {"all": []} # type: ignore
-extra_requirements_path = ROOT_DIR / "requirements" / "pkg-extras.txt"
-with extra_requirements_path.open() as f:
- for line in map(str.strip, f):
- if line.startswith("#") and line[1:].strip().startswith("extra="):
- _current_extras = [e.strip() for e in line.split("=", 1)[1].split(",")]
- if "all" in _current_extras:
- raise ValueError("%r uses the reserved extra name 'all'")
- for e in _current_extras:
- extra_requirements[e] = []
- elif _current_extras:
- for e in _current_extras:
- extra_requirements[e].append(line)
- extra_requirements["all"].append(line)
- elif line:
- raise ValueError(
- f"No '# extra=' header before requirements in {extra_requirements_path}"
- )
-package["extras_require"] = extra_requirements
-
-
-# -----------------------------------------------------------------------------
-# Library Description
-# -----------------------------------------------------------------------------
-
-
-with (ROOT_DIR / "README.md").open() as f:
- long_description = f.read()
-
-package["long_description"] = long_description
-package["long_description_content_type"] = "text/markdown"
-
-
-# ----------------------------------------------------------------------------
-# Build Javascript
-# ----------------------------------------------------------------------------
-
-
-def build_javascript_first(cls):
- class Command(cls):
- def run(self):
- log.info("Installing Javascript...")
- try:
- npm = shutil.which("npm") # this is required on windows
- if npm is None:
- raise RuntimeError("NPM is not installed.")
- for args in (f"{npm} install", f"{npm} run build"):
- args_list = args.split()
- log.info(f"> {list2cmdline(args_list)}")
- subprocess.run(args_list, cwd=str(JS_DIR), check=True)
- except Exception:
- log.error("Failed to install Javascript")
- log.error(traceback.format_exc())
- raise
- else:
- log.info("Successfully installed Javascript")
- super().run()
-
- return Command
-
-
-package["cmdclass"] = {
- "sdist": build_javascript_first(sdist),
- "build": build_javascript_first(build),
- "develop": build_javascript_first(develop),
-}
-
-
-# -----------------------------------------------------------------------------
-# Install It
-# -----------------------------------------------------------------------------
-
-
-if __name__ == "__main__":
- setup(**package)
diff --git a/src/client/README.md b/src/client/README.md
deleted file mode 100644
index 1cfd1079e..000000000
--- a/src/client/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# IDOM Client
-
-An ES6 Javascript client for IDOM
diff --git a/src/client/idom-logo-square-small.svg b/src/client/idom-logo-square-small.svg
deleted file mode 100644
index eb36c7b11..000000000
--- a/src/client/idom-logo-square-small.svg
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- image/svg+xml
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/client/index.html b/src/client/index.html
deleted file mode 100644
index 8f2669605..000000000
--- a/src/client/index.html
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
- IDOM
-
-
-
-
-
-
diff --git a/src/client/package-lock.json b/src/client/package-lock.json
deleted file mode 100644
index d4b4eb225..000000000
--- a/src/client/package-lock.json
+++ /dev/null
@@ -1,4733 +0,0 @@
-{
- "name": "client",
- "version": "0.36.0",
- "lockfileVersion": 2,
- "requires": true,
- "packages": {
- "": {
- "version": "0.36.0",
- "license": "MIT",
- "workspaces": [
- "./packages/*"
- ],
- "devDependencies": {
- "snowpack": "^3.5.2"
- }
- },
- "node_modules/@npmcli/git": {
- "version": "2.0.9",
- "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-2.0.9.tgz",
- "integrity": "sha512-hTMbMryvOqGLwnmMBKs5usbPsJtyEsMsgXwJbmNrsEuQQh1LAIMDU77IoOrwkCg+NgQWl+ySlarJASwM3SutCA==",
- "dev": true,
- "dependencies": {
- "@npmcli/promise-spawn": "^1.3.2",
- "lru-cache": "^6.0.0",
- "mkdirp": "^1.0.4",
- "npm-pick-manifest": "^6.1.1",
- "promise-inflight": "^1.0.1",
- "promise-retry": "^2.0.1",
- "semver": "^7.3.5",
- "which": "^2.0.2"
- }
- },
- "node_modules/@npmcli/installed-package-contents": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz",
- "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==",
- "dev": true,
- "dependencies": {
- "npm-bundled": "^1.1.1",
- "npm-normalize-package-bin": "^1.0.1"
- },
- "bin": {
- "installed-package-contents": "index.js"
- },
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/@npmcli/move-file": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
- "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
- "dev": true,
- "dependencies": {
- "mkdirp": "^1.0.4",
- "rimraf": "^3.0.2"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/@npmcli/node-gyp": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.2.tgz",
- "integrity": "sha512-yrJUe6reVMpktcvagumoqD9r08fH1iRo01gn1u0zoCApa9lnZGEigVKUd2hzsCId4gdtkZZIVscLhNxMECKgRg==",
- "dev": true
- },
- "node_modules/@npmcli/promise-spawn": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz",
- "integrity": "sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg==",
- "dev": true,
- "dependencies": {
- "infer-owner": "^1.0.4"
- }
- },
- "node_modules/@npmcli/run-script": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.5.tgz",
- "integrity": "sha512-NQspusBCpTjNwNRFMtz2C5MxoxyzlbuJ4YEhxAKrIonTiirKDtatsZictx9RgamQIx6+QuHMNmPl0wQdoESs9A==",
- "dev": true,
- "dependencies": {
- "@npmcli/node-gyp": "^1.0.2",
- "@npmcli/promise-spawn": "^1.3.2",
- "infer-owner": "^1.0.4",
- "node-gyp": "^7.1.0",
- "read-package-json-fast": "^2.0.1"
- }
- },
- "node_modules/@tootallnate/once": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
- "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
- "dev": true,
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/abab": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
- "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==",
- "dev": true
- },
- "node_modules/abbrev": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "dev": true
- },
- "node_modules/acorn": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
- "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
- "dev": true,
- "bin": {
- "acorn": "bin/acorn"
- },
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/acorn-globals": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz",
- "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==",
- "dev": true,
- "dependencies": {
- "acorn": "^7.1.1",
- "acorn-walk": "^7.1.1"
- }
- },
- "node_modules/acorn-walk": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
- "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
- "dev": true,
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "dev": true,
- "dependencies": {
- "debug": "4"
- },
- "engines": {
- "node": ">= 6.0.0"
- }
- },
- "node_modules/agentkeepalive": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.4.tgz",
- "integrity": "sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ==",
- "dev": true,
- "dependencies": {
- "debug": "^4.1.0",
- "depd": "^1.1.2",
- "humanize-ms": "^1.2.1"
- },
- "engines": {
- "node": ">= 8.0.0"
- }
- },
- "node_modules/aggregate-error": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
- "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
- "dev": true,
- "dependencies": {
- "clean-stack": "^2.0.0",
- "indent-string": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "dependencies": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- },
- "funding": {
- "type": "github",
- "url": "https://github.com/sponsors/epoberezkin"
- }
- },
- "node_modules/ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/aproba": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
- "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
- "dev": true
- },
- "node_modules/are-we-there-yet": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
- "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
- "dev": true,
- "dependencies": {
- "delegates": "^1.0.0",
- "readable-stream": "^2.0.6"
- }
- },
- "node_modules/asn1": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
- "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
- "dev": true,
- "dependencies": {
- "safer-buffer": "~2.1.0"
- }
- },
- "node_modules/assert-plus": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
- "dev": true,
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
- "dev": true
- },
- "node_modules/aws-sign2": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
- "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
- "dev": true,
- "engines": {
- "node": "*"
- }
- },
- "node_modules/aws4": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
- "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
- "dev": true
- },
- "node_modules/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
- },
- "node_modules/bcrypt-pbkdf": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
- "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
- "dev": true,
- "dependencies": {
- "tweetnacl": "^0.14.3"
- }
- },
- "node_modules/big-integer": {
- "version": "1.6.48",
- "dev": true,
- "license": "Unlicense",
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/bplist-parser": {
- "version": "0.1.1",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "big-integer": "^1.6.7"
- }
- },
- "node_modules/brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "dependencies": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "node_modules/browser-process-hrtime": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
- "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
- "dev": true
- },
- "node_modules/builtins": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
- "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
- "dev": true
- },
- "node_modules/cacache": {
- "version": "15.2.0",
- "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.2.0.tgz",
- "integrity": "sha512-uKoJSHmnrqXgthDFx/IU6ED/5xd+NNGe+Bb+kLZy7Ku4P+BaiWEUflAKPZ7eAzsYGcsAGASJZsybXp+quEcHTw==",
- "dev": true,
- "dependencies": {
- "@npmcli/move-file": "^1.0.1",
- "chownr": "^2.0.0",
- "fs-minipass": "^2.0.0",
- "glob": "^7.1.4",
- "infer-owner": "^1.0.4",
- "lru-cache": "^6.0.0",
- "minipass": "^3.1.1",
- "minipass-collect": "^1.0.2",
- "minipass-flush": "^1.0.5",
- "minipass-pipeline": "^1.2.2",
- "mkdirp": "^1.0.3",
- "p-map": "^4.0.0",
- "promise-inflight": "^1.0.1",
- "rimraf": "^3.0.2",
- "ssri": "^8.0.1",
- "tar": "^6.0.2",
- "unique-filename": "^1.1.1"
- },
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/caseless": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
- "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
- "dev": true
- },
- "node_modules/chownr": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
- "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
- "dev": true,
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/clean-stack": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
- "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/cli-spinners": {
- "version": "2.5.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=6"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/code-point-at": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
- "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dev": true,
- "dependencies": {
- "delayed-stream": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
- "node_modules/concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
- "dev": true
- },
- "node_modules/console-control-strings": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
- "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
- "dev": true
- },
- "node_modules/core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
- "dev": true
- },
- "node_modules/cssom": {
- "version": "0.4.4",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
- "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==",
- "dev": true
- },
- "node_modules/cssstyle": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
- "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
- "dev": true,
- "dependencies": {
- "cssom": "~0.3.6"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/cssstyle/node_modules/cssom": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
- "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
- "dev": true
- },
- "node_modules/dashdash": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
- "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
- "dev": true,
- "dependencies": {
- "assert-plus": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10"
- }
- },
- "node_modules/data-urls": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz",
- "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==",
- "dev": true,
- "dependencies": {
- "abab": "^2.0.3",
- "whatwg-mimetype": "^2.3.0",
- "whatwg-url": "^8.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/debug": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
- "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
- "dev": true,
- "dependencies": {
- "ms": "2.1.2"
- },
- "engines": {
- "node": ">=6.0"
- },
- "peerDependenciesMeta": {
- "supports-color": {
- "optional": true
- }
- }
- },
- "node_modules/decimal.js": {
- "version": "10.2.1",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz",
- "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==",
- "dev": true
- },
- "node_modules/deep-is": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
- "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
- "dev": true
- },
- "node_modules/default-browser-id": {
- "version": "2.0.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "bplist-parser": "^0.1.0",
- "pify": "^2.3.0",
- "untildify": "^2.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
- "dev": true,
- "engines": {
- "node": ">=0.4.0"
- }
- },
- "node_modules/delegates": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
- "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
- "dev": true
- },
- "node_modules/depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
- "dev": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/dequal": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz",
- "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/diff": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
- "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
- "dev": true,
- "engines": {
- "node": ">=0.3.1"
- }
- },
- "node_modules/domexception": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
- "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==",
- "dev": true,
- "dependencies": {
- "webidl-conversions": "^5.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/domexception/node_modules/webidl-conversions": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
- "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/ecc-jsbn": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
- "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
- "dev": true,
- "dependencies": {
- "jsbn": "~0.1.0",
- "safer-buffer": "^2.1.0"
- }
- },
- "node_modules/encoding": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
- "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "dev": true,
- "optional": true,
- "dependencies": {
- "iconv-lite": "^0.6.2"
- }
- },
- "node_modules/encoding/node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dev": true,
- "optional": true,
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/env-paths": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
- "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/err-code": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
- "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
- "dev": true
- },
- "node_modules/esbuild": {
- "version": "0.9.7",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.9.7.tgz",
- "integrity": "sha512-VtUf6aQ89VTmMLKrWHYG50uByMF4JQlVysb8dmg6cOgW8JnFCipmz7p+HNBl+RR3LLCuBxFGVauAe2wfnF9bLg==",
- "dev": true,
- "hasInstallScript": true,
- "bin": {
- "esbuild": "bin/esbuild"
- }
- },
- "node_modules/escodegen": {
- "version": "1.14.3",
- "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
- "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
- "dev": true,
- "dependencies": {
- "esprima": "^4.0.1",
- "estraverse": "^4.2.0",
- "esutils": "^2.0.2",
- "optionator": "^0.8.1"
- },
- "bin": {
- "escodegen": "bin/escodegen.js",
- "esgenerate": "bin/esgenerate.js"
- },
- "engines": {
- "node": ">=4.0"
- },
- "optionalDependencies": {
- "source-map": "~0.6.1"
- }
- },
- "node_modules/esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true,
- "bin": {
- "esparse": "bin/esparse.js",
- "esvalidate": "bin/esvalidate.js"
- },
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/estraverse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
- "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
- "dev": true,
- "engines": {
- "node": ">=4.0"
- }
- },
- "node_modules/esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/extend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "dev": true
- },
- "node_modules/extsprintf": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
- "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
- "dev": true,
- "engines": [
- "node >=0.6.0"
- ]
- },
- "node_modules/fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
- },
- "node_modules/fast-json-patch": {
- "version": "3.0.0-1",
- "license": "MIT"
- },
- "node_modules/fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true
- },
- "node_modules/fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
- "dev": true
- },
- "node_modules/fdir": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-5.1.0.tgz",
- "integrity": "sha512-IgTtZwL52tx2wqWeuGDzXYTnNsEjNLahZpJw30hCQDyVnoHXwY5acNDnjGImTTL1R0z1PCyLw20VAbE5qLic3Q==",
- "dev": true
- },
- "node_modules/forever-agent": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
- "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
- "dev": true,
- "engines": {
- "node": "*"
- }
- },
- "node_modules/form-data": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
- "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
- "dev": true,
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.6",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 0.12"
- }
- },
- "node_modules/fs-minipass": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
- "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
- "dev": true,
- "dependencies": {
- "minipass": "^3.0.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
- "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==",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.1",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/gauge": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
- "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
- "dev": true,
- "dependencies": {
- "aproba": "^1.0.3",
- "console-control-strings": "^1.0.0",
- "has-unicode": "^2.0.0",
- "object-assign": "^4.1.0",
- "signal-exit": "^3.0.0",
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wide-align": "^1.1.0"
- }
- },
- "node_modules/getpass": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
- "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
- "dev": true,
- "dependencies": {
- "assert-plus": "^1.0.0"
- }
- },
- "node_modules/glob": {
- "version": "7.1.7",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
- "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
- "dev": true,
- "dependencies": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- },
- "engines": {
- "node": "*"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/graceful-fs": {
- "version": "4.2.6",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
- "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
- "dev": true
- },
- "node_modules/har-schema": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
- "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/har-validator": {
- "version": "5.1.5",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
- "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
- "deprecated": "this library is no longer supported",
- "dev": true,
- "dependencies": {
- "ajv": "^6.12.3",
- "har-schema": "^2.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/has": {
- "version": "1.0.3",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.1"
- },
- "engines": {
- "node": ">= 0.4.0"
- }
- },
- "node_modules/has-unicode": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
- "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
- "dev": true
- },
- "node_modules/hosted-git-info": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz",
- "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==",
- "dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/htm": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/htm/-/htm-3.0.4.tgz",
- "integrity": "sha512-VRdvxX3tmrXuT/Ovt59NMp/ORMFi4bceFMDjos1PV4E0mV+5votuID8R60egR9A4U8nLt238R/snlJGz3UYiTQ=="
- },
- "node_modules/html-encoding-sniffer": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
- "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==",
- "dev": true,
- "dependencies": {
- "whatwg-encoding": "^1.0.5"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/http-cache-semantics": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
- "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
- "dev": true
- },
- "node_modules/http-proxy-agent": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
- "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==",
- "dev": true,
- "dependencies": {
- "@tootallnate/once": "1",
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/http-signature": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
- "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
- "dev": true,
- "dependencies": {
- "assert-plus": "^1.0.0",
- "jsprim": "^1.2.2",
- "sshpk": "^1.7.0"
- },
- "engines": {
- "node": ">=0.8",
- "npm": ">=1.3.7"
- }
- },
- "node_modules/https-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
- "dev": true,
- "dependencies": {
- "agent-base": "6",
- "debug": "4"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/humanize-ms": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
- "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
- "dev": true,
- "dependencies": {
- "ms": "^2.0.0"
- }
- },
- "node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dev": true,
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/idom-app-react": {
- "resolved": "packages/idom-app-react",
- "link": true
- },
- "node_modules/idom-client-react": {
- "resolved": "packages/idom-client-react",
- "link": true
- },
- "node_modules/ignore-walk": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz",
- "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==",
- "dev": true,
- "dependencies": {
- "minimatch": "^3.0.4"
- }
- },
- "node_modules/imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
- "dev": true,
- "engines": {
- "node": ">=0.8.19"
- }
- },
- "node_modules/indent-string": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
- "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
- "dev": true,
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/infer-owner": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
- "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
- "dev": true
- },
- "node_modules/inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "dev": true,
- "dependencies": {
- "once": "^1.3.0",
- "wrappy": "1"
- }
- },
- "node_modules/inherits": {
- "version": "2.0.4",
- "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
- "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
- "dev": true
- },
- "node_modules/ip": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
- "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
- "dev": true
- },
- "node_modules/ip-regex": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
- "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/is-core-module": {
- "version": "2.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "has": "^1.0.3"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/is-docker": {
- "version": "2.1.1",
- "dev": true,
- "license": "MIT",
- "bin": {
- "is-docker": "cli.js"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "dev": true,
- "dependencies": {
- "number-is-nan": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/is-lambda": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
- "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=",
- "dev": true
- },
- "node_modules/is-potential-custom-element-name": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
- "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
- "dev": true
- },
- "node_modules/is-typedarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
- "dev": true
- },
- "node_modules/is-wsl": {
- "version": "2.2.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-docker": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "dev": true
- },
- "node_modules/isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
- "dev": true
- },
- "node_modules/isstream": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
- "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
- "dev": true
- },
- "node_modules/js-tokens": {
- "version": "4.0.0",
- "license": "MIT"
- },
- "node_modules/jsbn": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
- "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
- "dev": true
- },
- "node_modules/jsdom": {
- "version": "16.3.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.3.0.tgz",
- "integrity": "sha512-zggeX5UuEknpdZzv15+MS1dPYG0J/TftiiNunOeNxSl3qr8Z6cIlQpN0IdJa44z9aFxZRIVqRncvEhQ7X5DtZg==",
- "dev": true,
- "dependencies": {
- "abab": "^2.0.3",
- "acorn": "^7.1.1",
- "acorn-globals": "^6.0.0",
- "cssom": "^0.4.4",
- "cssstyle": "^2.2.0",
- "data-urls": "^2.0.0",
- "decimal.js": "^10.2.0",
- "domexception": "^2.0.1",
- "escodegen": "^1.14.1",
- "html-encoding-sniffer": "^2.0.1",
- "is-potential-custom-element-name": "^1.0.0",
- "nwsapi": "^2.2.0",
- "parse5": "5.1.1",
- "request": "^2.88.2",
- "request-promise-native": "^1.0.8",
- "saxes": "^5.0.0",
- "symbol-tree": "^3.2.4",
- "tough-cookie": "^3.0.1",
- "w3c-hr-time": "^1.0.2",
- "w3c-xmlserializer": "^2.0.0",
- "webidl-conversions": "^6.1.0",
- "whatwg-encoding": "^1.0.5",
- "whatwg-mimetype": "^2.3.0",
- "whatwg-url": "^8.0.0",
- "ws": "^7.2.3",
- "xml-name-validator": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "peerDependencies": {
- "canvas": "^2.5.0"
- },
- "peerDependenciesMeta": {
- "canvas": {
- "optional": true
- }
- }
- },
- "node_modules/json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
- "dev": true
- },
- "node_modules/json-schema": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
- "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
- "dev": true
- },
- "node_modules/json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
- },
- "node_modules/json-stringify-safe": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
- "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
- "dev": true
- },
- "node_modules/jsonparse": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
- "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
- "dev": true,
- "engines": [
- "node >= 0.2.0"
- ]
- },
- "node_modules/jsprim": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
- "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
- "dev": true,
- "engines": [
- "node >=0.6.0"
- ],
- "dependencies": {
- "assert-plus": "1.0.0",
- "extsprintf": "1.3.0",
- "json-schema": "0.2.3",
- "verror": "1.10.0"
- }
- },
- "node_modules/kleur": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz",
- "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/levn": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
- "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
- "dev": true,
- "dependencies": {
- "prelude-ls": "~1.1.2",
- "type-check": "~0.3.2"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true
- },
- "node_modules/loose-envify": {
- "version": "1.4.0",
- "license": "MIT",
- "dependencies": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- },
- "bin": {
- "loose-envify": "cli.js"
- }
- },
- "node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/make-fetch-happen": {
- "version": "8.0.14",
- "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz",
- "integrity": "sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ==",
- "dev": true,
- "dependencies": {
- "agentkeepalive": "^4.1.3",
- "cacache": "^15.0.5",
- "http-cache-semantics": "^4.1.0",
- "http-proxy-agent": "^4.0.1",
- "https-proxy-agent": "^5.0.0",
- "is-lambda": "^1.0.1",
- "lru-cache": "^6.0.0",
- "minipass": "^3.1.3",
- "minipass-collect": "^1.0.2",
- "minipass-fetch": "^1.3.2",
- "minipass-flush": "^1.0.5",
- "minipass-pipeline": "^1.2.4",
- "promise-retry": "^2.0.1",
- "socks-proxy-agent": "^5.0.0",
- "ssri": "^8.0.0"
- },
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/mime-db": {
- "version": "1.47.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
- "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==",
- "dev": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.30",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
- "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
- "dev": true,
- "dependencies": {
- "mime-db": "1.47.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "dev": true,
- "dependencies": {
- "brace-expansion": "^1.1.7"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/minipass": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
- "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
- "dev": true,
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/minipass-collect": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
- "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
- "dev": true,
- "dependencies": {
- "minipass": "^3.0.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/minipass-fetch": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.3.3.tgz",
- "integrity": "sha512-akCrLDWfbdAWkMLBxJEeWTdNsjML+dt5YgOI4gJ53vuO0vrmYQkUPxa6j6V65s9CcePIr2SSWqjT2EcrNseryQ==",
- "dev": true,
- "dependencies": {
- "minipass": "^3.1.0",
- "minipass-sized": "^1.0.3",
- "minizlib": "^2.0.0"
- },
- "engines": {
- "node": ">=8"
- },
- "optionalDependencies": {
- "encoding": "^0.1.12"
- }
- },
- "node_modules/minipass-flush": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
- "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
- "dev": true,
- "dependencies": {
- "minipass": "^3.0.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/minipass-json-stream": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz",
- "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==",
- "dev": true,
- "dependencies": {
- "jsonparse": "^1.3.1",
- "minipass": "^3.0.0"
- }
- },
- "node_modules/minipass-pipeline": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
- "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
- "dev": true,
- "dependencies": {
- "minipass": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/minipass-sized": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
- "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
- "dev": true,
- "dependencies": {
- "minipass": "^3.0.0"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/minizlib": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
- "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
- "dev": true,
- "dependencies": {
- "minipass": "^3.0.0",
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/mkdirp": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
- "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
- "dev": true,
- "bin": {
- "mkdirp": "bin/cmd.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/mri": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz",
- "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node_modules/node-gyp": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz",
- "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==",
- "dev": true,
- "dependencies": {
- "env-paths": "^2.2.0",
- "glob": "^7.1.4",
- "graceful-fs": "^4.2.3",
- "nopt": "^5.0.0",
- "npmlog": "^4.1.2",
- "request": "^2.88.2",
- "rimraf": "^3.0.2",
- "semver": "^7.3.2",
- "tar": "^6.0.2",
- "which": "^2.0.2"
- },
- "bin": {
- "node-gyp": "bin/node-gyp.js"
- },
- "engines": {
- "node": ">= 10.12.0"
- }
- },
- "node_modules/nopt": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
- "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
- "dev": true,
- "dependencies": {
- "abbrev": "1"
- },
- "bin": {
- "nopt": "bin/nopt.js"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/npm-bundled": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz",
- "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==",
- "dev": true,
- "dependencies": {
- "npm-normalize-package-bin": "^1.0.1"
- }
- },
- "node_modules/npm-install-checks": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz",
- "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==",
- "dev": true,
- "dependencies": {
- "semver": "^7.1.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/npm-normalize-package-bin": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
- "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
- "dev": true
- },
- "node_modules/npm-package-arg": {
- "version": "8.1.2",
- "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.2.tgz",
- "integrity": "sha512-6Eem455JsSMJY6Kpd3EyWE+n5hC+g9bSyHr9K9U2zqZb7+02+hObQ2c0+8iDk/mNF+8r1MhY44WypKJAkySIYA==",
- "dev": true,
- "dependencies": {
- "hosted-git-info": "^4.0.1",
- "semver": "^7.3.4",
- "validate-npm-package-name": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/npm-packlist": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz",
- "integrity": "sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg==",
- "dev": true,
- "dependencies": {
- "glob": "^7.1.6",
- "ignore-walk": "^3.0.3",
- "npm-bundled": "^1.1.1",
- "npm-normalize-package-bin": "^1.0.1"
- },
- "bin": {
- "npm-packlist": "bin/index.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/npm-pick-manifest": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz",
- "integrity": "sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA==",
- "dev": true,
- "dependencies": {
- "npm-install-checks": "^4.0.0",
- "npm-normalize-package-bin": "^1.0.1",
- "npm-package-arg": "^8.1.2",
- "semver": "^7.3.4"
- }
- },
- "node_modules/npm-registry-fetch": {
- "version": "10.1.2",
- "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-10.1.2.tgz",
- "integrity": "sha512-KsM/TdPmntqgBFlfsbkOLkkE9ovZo7VpVcd+/eTdYszCrgy5zFl5JzWm+OxavFaEWlbkirpkou+ZYI00RmOBFA==",
- "dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0",
- "make-fetch-happen": "^8.0.9",
- "minipass": "^3.1.3",
- "minipass-fetch": "^1.3.0",
- "minipass-json-stream": "^1.0.1",
- "minizlib": "^2.0.0",
- "npm-package-arg": "^8.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/npmlog": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
- "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
- "dev": true,
- "dependencies": {
- "are-we-there-yet": "~1.1.2",
- "console-control-strings": "~1.1.0",
- "gauge": "~2.7.3",
- "set-blocking": "~2.0.0"
- }
- },
- "node_modules/number-is-nan": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
- "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/nwsapi": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
- "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==",
- "dev": true
- },
- "node_modules/oauth-sign": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
- "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
- "dev": true,
- "engines": {
- "node": "*"
- }
- },
- "node_modules/object-assign": {
- "version": "4.1.1",
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "dev": true,
- "dependencies": {
- "wrappy": "1"
- }
- },
- "node_modules/open": {
- "version": "7.4.2",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-docker": "^2.0.0",
- "is-wsl": "^2.1.1"
- },
- "engines": {
- "node": ">=8"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/optionator": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
- "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
- "dev": true,
- "dependencies": {
- "deep-is": "~0.1.3",
- "fast-levenshtein": "~2.0.6",
- "levn": "~0.3.0",
- "prelude-ls": "~1.1.2",
- "type-check": "~0.3.2",
- "word-wrap": "~1.2.3"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/os-homedir": {
- "version": "1.0.2",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/p-map": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
- "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
- "dev": true,
- "dependencies": {
- "aggregate-error": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pacote": {
- "version": "11.3.3",
- "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.3.3.tgz",
- "integrity": "sha512-GQxBX+UcVZrrJRYMK2HoG+gPeSUX/rQhnbPkkGrCYa4n2F/bgClFPaMm0nsdnYrxnmUy85uMHoFXZ0jTD0drew==",
- "dev": true,
- "dependencies": {
- "@npmcli/git": "^2.0.1",
- "@npmcli/installed-package-contents": "^1.0.6",
- "@npmcli/promise-spawn": "^1.2.0",
- "@npmcli/run-script": "^1.8.2",
- "cacache": "^15.0.5",
- "chownr": "^2.0.0",
- "fs-minipass": "^2.1.0",
- "infer-owner": "^1.0.4",
- "minipass": "^3.1.3",
- "mkdirp": "^1.0.3",
- "npm-package-arg": "^8.0.1",
- "npm-packlist": "^2.1.4",
- "npm-pick-manifest": "^6.0.0",
- "npm-registry-fetch": "^10.0.0",
- "promise-retry": "^2.0.1",
- "read-package-json-fast": "^2.0.1",
- "rimraf": "^3.0.2",
- "ssri": "^8.0.1",
- "tar": "^6.1.0"
- },
- "bin": {
- "pacote": "lib/bin.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/parse5": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
- "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
- "dev": true
- },
- "node_modules/path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/path-parse": {
- "version": "1.0.6",
- "dev": true,
- "license": "MIT"
- },
- "node_modules/performance-now": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
- "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
- "dev": true
- },
- "node_modules/picomatch": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
- "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
- "dev": true,
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/pify": {
- "version": "2.3.0",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/prelude-ls": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
- "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
- "dev": true,
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/prettier": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
- "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
- "dev": true,
- "bin": {
- "prettier": "bin-prettier.js"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
- "node_modules/process-nextick-args": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
- "dev": true
- },
- "node_modules/promise-inflight": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
- "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
- "dev": true
- },
- "node_modules/promise-retry": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
- "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
- "dev": true,
- "dependencies": {
- "err-code": "^2.0.2",
- "retry": "^0.12.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/prop-types": {
- "version": "15.7.2",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.8.1"
- }
- },
- "node_modules/psl": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
- "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
- "dev": true
- },
- "node_modules/punycode": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/qs": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
- "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
- "dev": true,
- "engines": {
- "node": ">=0.6"
- }
- },
- "node_modules/react": {
- "version": "16.14.0",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/react-dom": {
- "version": "16.14.0",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.19.1"
- },
- "peerDependencies": {
- "react": "^16.14.0"
- }
- },
- "node_modules/react-is": {
- "version": "16.13.1",
- "license": "MIT"
- },
- "node_modules/read-package-json-fast": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.2.tgz",
- "integrity": "sha512-5fyFUyO9B799foVk4n6ylcoAktG/FbE3jwRKxvwaeSrIunaoMc0u81dzXxjeAFKOce7O5KncdfwpGvvs6r5PsQ==",
- "dev": true,
- "dependencies": {
- "json-parse-even-better-errors": "^2.3.0",
- "npm-normalize-package-bin": "^1.0.1"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/readable-stream": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
- "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
- "dev": true,
- "dependencies": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- }
- },
- "node_modules/readable-stream/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
- },
- "node_modules/request": {
- "version": "2.88.2",
- "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
- "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
- "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142",
- "dev": true,
- "dependencies": {
- "aws-sign2": "~0.7.0",
- "aws4": "^1.8.0",
- "caseless": "~0.12.0",
- "combined-stream": "~1.0.6",
- "extend": "~3.0.2",
- "forever-agent": "~0.6.1",
- "form-data": "~2.3.2",
- "har-validator": "~5.1.3",
- "http-signature": "~1.2.0",
- "is-typedarray": "~1.0.0",
- "isstream": "~0.1.2",
- "json-stringify-safe": "~5.0.1",
- "mime-types": "~2.1.19",
- "oauth-sign": "~0.9.0",
- "performance-now": "^2.1.0",
- "qs": "~6.5.2",
- "safe-buffer": "^5.1.2",
- "tough-cookie": "~2.5.0",
- "tunnel-agent": "^0.6.0",
- "uuid": "^3.3.2"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/request-promise-core": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
- "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
- "dev": true,
- "dependencies": {
- "lodash": "^4.17.19"
- },
- "engines": {
- "node": ">=0.10.0"
- },
- "peerDependencies": {
- "request": "^2.34"
- }
- },
- "node_modules/request-promise-native": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz",
- "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==",
- "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142",
- "dev": true,
- "dependencies": {
- "request-promise-core": "1.1.4",
- "stealthy-require": "^1.1.1",
- "tough-cookie": "^2.3.3"
- },
- "engines": {
- "node": ">=0.12.0"
- },
- "peerDependencies": {
- "request": "^2.34"
- }
- },
- "node_modules/request-promise-native/node_modules/tough-cookie": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
- "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
- "dev": true,
- "dependencies": {
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/request/node_modules/tough-cookie": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
- "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
- "dev": true,
- "dependencies": {
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
- },
- "engines": {
- "node": ">=0.8"
- }
- },
- "node_modules/resolve": {
- "version": "1.20.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "is-core-module": "^2.2.0",
- "path-parse": "^1.0.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/retry": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
- "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
- "dev": true,
- "engines": {
- "node": ">= 4"
- }
- },
- "node_modules/rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dev": true,
- "dependencies": {
- "glob": "^7.1.3"
- },
- "bin": {
- "rimraf": "bin.js"
- },
- "funding": {
- "url": "https://github.com/sponsors/isaacs"
- }
- },
- "node_modules/rollup": {
- "version": "2.37.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.37.1.tgz",
- "integrity": "sha512-V3ojEeyGeSdrMSuhP3diBb06P+qV4gKQeanbDv+Qh/BZbhdZ7kHV0xAt8Yjk4GFshq/WjO7R4c7DFM20AwTFVQ==",
- "dev": true,
- "bin": {
- "rollup": "dist/bin/rollup"
- },
- "engines": {
- "node": ">=10.0.0"
- },
- "optionalDependencies": {
- "fsevents": "~2.1.2"
- }
- },
- "node_modules/rollup/node_modules/fsevents": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
- "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
- "deprecated": "\"Please update to latest v2.3 or v2.2\"",
- "dev": true,
- "hasInstallScript": true,
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
- }
- },
- "node_modules/sade": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz",
- "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==",
- "dev": true,
- "dependencies": {
- "mri": "^1.1.0"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ]
- },
- "node_modules/safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true
- },
- "node_modules/saxes": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
- "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
- "dev": true,
- "dependencies": {
- "xmlchars": "^2.2.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/scheduler": {
- "version": "0.19.1",
- "license": "MIT",
- "dependencies": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
- }
- },
- "node_modules/semver": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
- "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
- "dev": true,
- "dependencies": {
- "lru-cache": "^6.0.0"
- },
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/set-blocking": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
- "dev": true
- },
- "node_modules/signal-exit": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
- "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
- "dev": true
- },
- "node_modules/smart-buffer": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
- "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==",
- "dev": true,
- "engines": {
- "node": ">= 6.0.0",
- "npm": ">= 3.0.0"
- }
- },
- "node_modules/snowpack": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/snowpack/-/snowpack-3.5.2.tgz",
- "integrity": "sha512-TQQT5PXxeDr4gaMbp6nQrTDLX+Y8G5qI2wLqQdHLrpQEnq7W+gysn94+0xbOhnx0pFoVlSoFPjdQ83sETWl/9A==",
- "dev": true,
- "dependencies": {
- "cli-spinners": "^2.5.0",
- "default-browser-id": "^2.0.0",
- "esbuild": "^0.9.3",
- "fdir": "^5.0.0",
- "open": "^7.0.4",
- "pacote": "^11.3.1",
- "picomatch": "^2.2.2",
- "resolve": "^1.20.0",
- "rollup": "~2.37.1"
- },
- "bin": {
- "snowpack": "index.bin.js",
- "sp": "index.bin.js"
- },
- "engines": {
- "node": ">=10.19.0"
- },
- "optionalDependencies": {
- "fsevents": "^2.2.0"
- }
- },
- "node_modules/socks": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz",
- "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==",
- "dev": true,
- "dependencies": {
- "ip": "^1.1.5",
- "smart-buffer": "^4.1.0"
- },
- "engines": {
- "node": ">= 10.13.0",
- "npm": ">= 3.0.0"
- }
- },
- "node_modules/socks-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==",
- "dev": true,
- "dependencies": {
- "agent-base": "6",
- "debug": "4",
- "socks": "^2.3.3"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/sshpk": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
- "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
- "dev": true,
- "dependencies": {
- "asn1": "~0.2.3",
- "assert-plus": "^1.0.0",
- "bcrypt-pbkdf": "^1.0.0",
- "dashdash": "^1.12.0",
- "ecc-jsbn": "~0.1.1",
- "getpass": "^0.1.1",
- "jsbn": "~0.1.0",
- "safer-buffer": "^2.0.2",
- "tweetnacl": "~0.14.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/ssri": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
- "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
- "dev": true,
- "dependencies": {
- "minipass": "^3.1.1"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/stealthy-require": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
- "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dev": true,
- "dependencies": {
- "safe-buffer": "~5.1.0"
- }
- },
- "node_modules/string_decoder/node_modules/safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
- },
- "node_modules/string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "dev": true,
- "dependencies": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "dependencies": {
- "ansi-regex": "^2.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/symbol-tree": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
- "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
- "dev": true
- },
- "node_modules/tar": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
- "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
- "dev": true,
- "dependencies": {
- "chownr": "^2.0.0",
- "fs-minipass": "^2.0.0",
- "minipass": "^3.0.0",
- "minizlib": "^2.1.1",
- "mkdirp": "^1.0.3",
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">= 10"
- }
- },
- "node_modules/totalist": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz",
- "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==",
- "dev": true,
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/tough-cookie": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
- "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==",
- "dev": true,
- "dependencies": {
- "ip-regex": "^2.1.0",
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
- },
- "engines": {
- "node": ">=6"
- }
- },
- "node_modules/tr46": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz",
- "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==",
- "dev": true,
- "dependencies": {
- "punycode": "^2.1.1"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/tunnel-agent": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
- "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
- "dev": true,
- "dependencies": {
- "safe-buffer": "^5.0.1"
- },
- "engines": {
- "node": "*"
- }
- },
- "node_modules/tweetnacl": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
- "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
- "dev": true
- },
- "node_modules/type-check": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
- "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
- "dev": true,
- "dependencies": {
- "prelude-ls": "~1.1.2"
- },
- "engines": {
- "node": ">= 0.8.0"
- }
- },
- "node_modules/unique-filename": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
- "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
- "dev": true,
- "dependencies": {
- "unique-slug": "^2.0.0"
- }
- },
- "node_modules/unique-slug": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
- "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
- "dev": true,
- "dependencies": {
- "imurmurhash": "^0.1.4"
- }
- },
- "node_modules/untildify": {
- "version": "2.1.0",
- "dev": true,
- "license": "MIT",
- "dependencies": {
- "os-homedir": "^1.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "dependencies": {
- "punycode": "^2.1.0"
- }
- },
- "node_modules/util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "dev": true
- },
- "node_modules/uuid": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
- "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
- "dev": true,
- "bin": {
- "uuid": "bin/uuid"
- }
- },
- "node_modules/uvu": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.1.tgz",
- "integrity": "sha512-JGxttnOGDFs77FaZ0yMUHIzczzQ5R1IlDeNW6Wymw6gAscwMdAffVOP6TlxLIfReZyK8tahoGwWZaTCJzNFDkg==",
- "dev": true,
- "dependencies": {
- "dequal": "^2.0.0",
- "diff": "^5.0.0",
- "kleur": "^4.0.3",
- "sade": "^1.7.3",
- "totalist": "^2.0.0"
- },
- "bin": {
- "uvu": "bin.js"
- },
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/validate-npm-package-name": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
- "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
- "dev": true,
- "dependencies": {
- "builtins": "^1.0.3"
- }
- },
- "node_modules/verror": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
- "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
- "dev": true,
- "engines": [
- "node >=0.6.0"
- ],
- "dependencies": {
- "assert-plus": "^1.0.0",
- "core-util-is": "1.0.2",
- "extsprintf": "^1.2.0"
- }
- },
- "node_modules/w3c-hr-time": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
- "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==",
- "dev": true,
- "dependencies": {
- "browser-process-hrtime": "^1.0.0"
- }
- },
- "node_modules/w3c-xmlserializer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz",
- "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==",
- "dev": true,
- "dependencies": {
- "xml-name-validator": "^3.0.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/webidl-conversions": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
- "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
- "dev": true,
- "engines": {
- "node": ">=10.4"
- }
- },
- "node_modules/whatwg-encoding": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",
- "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==",
- "dev": true,
- "dependencies": {
- "iconv-lite": "0.4.24"
- }
- },
- "node_modules/whatwg-mimetype": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
- "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==",
- "dev": true
- },
- "node_modules/whatwg-url": {
- "version": "8.5.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz",
- "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==",
- "dev": true,
- "dependencies": {
- "lodash": "^4.7.0",
- "tr46": "^2.0.2",
- "webidl-conversions": "^6.1.0"
- },
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "dependencies": {
- "isexe": "^2.0.0"
- },
- "bin": {
- "node-which": "bin/node-which"
- },
- "engines": {
- "node": ">= 8"
- }
- },
- "node_modules/wide-align": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
- "dev": true,
- "dependencies": {
- "string-width": "^1.0.2 || 2"
- }
- },
- "node_modules/word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
- "node_modules/wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
- "dev": true
- },
- "node_modules/ws": {
- "version": "7.4.5",
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
- "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
- "dev": true,
- "engines": {
- "node": ">=8.3.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": "^5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
- "node_modules/xml-name-validator": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
- "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==",
- "dev": true
- },
- "node_modules/xmlchars": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
- "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
- "dev": true
- },
- "node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
- },
- "packages/idom-app-react": {
- "version": "0.36.0",
- "license": "MIT",
- "dependencies": {
- "idom-client-react": "file:packages/idom-client-react",
- "react": "^16.13.1",
- "react-dom": "^16.13.1"
- },
- "devDependencies": {
- "prettier": "^2.5.1"
- }
- },
- "packages/idom-app-react/node_modules/idom-client-react": {
- "resolved": "packages/idom-app-react/packages/idom-client-react",
- "link": true
- },
- "packages/idom-app-react/packages/idom-client-react": {},
- "packages/idom-client-react": {
- "version": "0.36.0",
- "license": "MIT",
- "dependencies": {
- "fast-json-patch": "^3.0.0-1",
- "htm": "^3.0.3"
- },
- "devDependencies": {
- "jsdom": "16.3.0",
- "lodash": "^4.17.21",
- "prettier": "^2.5.1",
- "uvu": "^0.5.1"
- },
- "peerDependencies": {
- "react": ">=16",
- "react-dom": ">=16"
- }
- }
- },
- "dependencies": {
- "@npmcli/git": {
- "version": "2.0.9",
- "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-2.0.9.tgz",
- "integrity": "sha512-hTMbMryvOqGLwnmMBKs5usbPsJtyEsMsgXwJbmNrsEuQQh1LAIMDU77IoOrwkCg+NgQWl+ySlarJASwM3SutCA==",
- "dev": true,
- "requires": {
- "@npmcli/promise-spawn": "^1.3.2",
- "lru-cache": "^6.0.0",
- "mkdirp": "^1.0.4",
- "npm-pick-manifest": "^6.1.1",
- "promise-inflight": "^1.0.1",
- "promise-retry": "^2.0.1",
- "semver": "^7.3.5",
- "which": "^2.0.2"
- }
- },
- "@npmcli/installed-package-contents": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz",
- "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==",
- "dev": true,
- "requires": {
- "npm-bundled": "^1.1.1",
- "npm-normalize-package-bin": "^1.0.1"
- }
- },
- "@npmcli/move-file": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
- "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
- "dev": true,
- "requires": {
- "mkdirp": "^1.0.4",
- "rimraf": "^3.0.2"
- }
- },
- "@npmcli/node-gyp": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.2.tgz",
- "integrity": "sha512-yrJUe6reVMpktcvagumoqD9r08fH1iRo01gn1u0zoCApa9lnZGEigVKUd2hzsCId4gdtkZZIVscLhNxMECKgRg==",
- "dev": true
- },
- "@npmcli/promise-spawn": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz",
- "integrity": "sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg==",
- "dev": true,
- "requires": {
- "infer-owner": "^1.0.4"
- }
- },
- "@npmcli/run-script": {
- "version": "1.8.5",
- "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-1.8.5.tgz",
- "integrity": "sha512-NQspusBCpTjNwNRFMtz2C5MxoxyzlbuJ4YEhxAKrIonTiirKDtatsZictx9RgamQIx6+QuHMNmPl0wQdoESs9A==",
- "dev": true,
- "requires": {
- "@npmcli/node-gyp": "^1.0.2",
- "@npmcli/promise-spawn": "^1.3.2",
- "infer-owner": "^1.0.4",
- "node-gyp": "^7.1.0",
- "read-package-json-fast": "^2.0.1"
- }
- },
- "@tootallnate/once": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
- "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
- "dev": true
- },
- "abab": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz",
- "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==",
- "dev": true
- },
- "abbrev": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
- "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
- "dev": true
- },
- "acorn": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
- "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
- "dev": true
- },
- "acorn-globals": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz",
- "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==",
- "dev": true,
- "requires": {
- "acorn": "^7.1.1",
- "acorn-walk": "^7.1.1"
- }
- },
- "acorn-walk": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz",
- "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
- "dev": true
- },
- "agent-base": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
- "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
- "dev": true,
- "requires": {
- "debug": "4"
- }
- },
- "agentkeepalive": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.1.4.tgz",
- "integrity": "sha512-+V/rGa3EuU74H6wR04plBb7Ks10FbtUQgRj/FQOG7uUIEuaINI+AiqJR1k6t3SVNs7o7ZjIdus6706qqzVq8jQ==",
- "dev": true,
- "requires": {
- "debug": "^4.1.0",
- "depd": "^1.1.2",
- "humanize-ms": "^1.2.1"
- }
- },
- "aggregate-error": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
- "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
- "dev": true,
- "requires": {
- "clean-stack": "^2.0.0",
- "indent-string": "^4.0.0"
- }
- },
- "ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
- "dev": true,
- "requires": {
- "fast-deep-equal": "^3.1.1",
- "fast-json-stable-stringify": "^2.0.0",
- "json-schema-traverse": "^0.4.1",
- "uri-js": "^4.2.2"
- }
- },
- "ansi-regex": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
- "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
- "dev": true
- },
- "aproba": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
- "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
- "dev": true
- },
- "are-we-there-yet": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
- "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
- "dev": true,
- "requires": {
- "delegates": "^1.0.0",
- "readable-stream": "^2.0.6"
- }
- },
- "asn1": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
- "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
- "dev": true,
- "requires": {
- "safer-buffer": "~2.1.0"
- }
- },
- "assert-plus": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
- "dev": true
- },
- "asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
- "dev": true
- },
- "aws-sign2": {
- "version": "0.7.0",
- "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
- "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
- "dev": true
- },
- "aws4": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
- "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
- "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
- },
- "bcrypt-pbkdf": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
- "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
- "dev": true,
- "requires": {
- "tweetnacl": "^0.14.3"
- }
- },
- "big-integer": {
- "version": "1.6.48",
- "dev": true
- },
- "bplist-parser": {
- "version": "0.1.1",
- "dev": true,
- "requires": {
- "big-integer": "^1.6.7"
- }
- },
- "brace-expansion": {
- "version": "1.1.11",
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
- "dev": true,
- "requires": {
- "balanced-match": "^1.0.0",
- "concat-map": "0.0.1"
- }
- },
- "browser-process-hrtime": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
- "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==",
- "dev": true
- },
- "builtins": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz",
- "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
- "dev": true
- },
- "cacache": {
- "version": "15.2.0",
- "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.2.0.tgz",
- "integrity": "sha512-uKoJSHmnrqXgthDFx/IU6ED/5xd+NNGe+Bb+kLZy7Ku4P+BaiWEUflAKPZ7eAzsYGcsAGASJZsybXp+quEcHTw==",
- "dev": true,
- "requires": {
- "@npmcli/move-file": "^1.0.1",
- "chownr": "^2.0.0",
- "fs-minipass": "^2.0.0",
- "glob": "^7.1.4",
- "infer-owner": "^1.0.4",
- "lru-cache": "^6.0.0",
- "minipass": "^3.1.1",
- "minipass-collect": "^1.0.2",
- "minipass-flush": "^1.0.5",
- "minipass-pipeline": "^1.2.2",
- "mkdirp": "^1.0.3",
- "p-map": "^4.0.0",
- "promise-inflight": "^1.0.1",
- "rimraf": "^3.0.2",
- "ssri": "^8.0.1",
- "tar": "^6.0.2",
- "unique-filename": "^1.1.1"
- }
- },
- "caseless": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
- "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
- "dev": true
- },
- "chownr": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
- "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
- "dev": true
- },
- "clean-stack": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
- "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
- "dev": true
- },
- "cli-spinners": {
- "version": "2.5.0",
- "dev": true
- },
- "code-point-at": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
- "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
- "dev": true
- },
- "combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "dev": true,
- "requires": {
- "delayed-stream": "~1.0.0"
- }
- },
- "concat-map": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
- "dev": true
- },
- "console-control-strings": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
- "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
- "dev": true
- },
- "core-util-is": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
- "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
- "dev": true
- },
- "cssom": {
- "version": "0.4.4",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz",
- "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==",
- "dev": true
- },
- "cssstyle": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz",
- "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==",
- "dev": true,
- "requires": {
- "cssom": "~0.3.6"
- },
- "dependencies": {
- "cssom": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
- "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
- "dev": true
- }
- }
- },
- "dashdash": {
- "version": "1.14.1",
- "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
- "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
- "dev": true,
- "requires": {
- "assert-plus": "^1.0.0"
- }
- },
- "data-urls": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz",
- "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==",
- "dev": true,
- "requires": {
- "abab": "^2.0.3",
- "whatwg-mimetype": "^2.3.0",
- "whatwg-url": "^8.0.0"
- }
- },
- "debug": {
- "version": "4.3.1",
- "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
- "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
- "dev": true,
- "requires": {
- "ms": "2.1.2"
- }
- },
- "decimal.js": {
- "version": "10.2.1",
- "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz",
- "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==",
- "dev": true
- },
- "deep-is": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
- "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
- "dev": true
- },
- "default-browser-id": {
- "version": "2.0.0",
- "dev": true,
- "requires": {
- "bplist-parser": "^0.1.0",
- "pify": "^2.3.0",
- "untildify": "^2.0.0"
- }
- },
- "delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
- "dev": true
- },
- "delegates": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
- "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
- "dev": true
- },
- "depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
- "dev": true
- },
- "dequal": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.2.tgz",
- "integrity": "sha512-q9K8BlJVxK7hQYqa6XISGmBZbtQQWVXSrRrWreHC94rMt1QL/Impruc+7p2CYSYuVIUr+YCt6hjrs1kkdJRTug==",
- "dev": true
- },
- "diff": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
- "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
- "dev": true
- },
- "domexception": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz",
- "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==",
- "dev": true,
- "requires": {
- "webidl-conversions": "^5.0.0"
- },
- "dependencies": {
- "webidl-conversions": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
- "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==",
- "dev": true
- }
- }
- },
- "ecc-jsbn": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
- "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
- "dev": true,
- "requires": {
- "jsbn": "~0.1.0",
- "safer-buffer": "^2.1.0"
- }
- },
- "encoding": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
- "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "dev": true,
- "optional": true,
- "requires": {
- "iconv-lite": "^0.6.2"
- },
- "dependencies": {
- "iconv-lite": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "dev": true,
- "optional": true,
- "requires": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- }
- }
- }
- },
- "env-paths": {
- "version": "2.2.1",
- "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
- "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
- "dev": true
- },
- "err-code": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
- "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
- "dev": true
- },
- "esbuild": {
- "version": "0.9.7",
- "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.9.7.tgz",
- "integrity": "sha512-VtUf6aQ89VTmMLKrWHYG50uByMF4JQlVysb8dmg6cOgW8JnFCipmz7p+HNBl+RR3LLCuBxFGVauAe2wfnF9bLg==",
- "dev": true
- },
- "escodegen": {
- "version": "1.14.3",
- "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz",
- "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==",
- "dev": true,
- "requires": {
- "esprima": "^4.0.1",
- "estraverse": "^4.2.0",
- "esutils": "^2.0.2",
- "optionator": "^0.8.1",
- "source-map": "~0.6.1"
- }
- },
- "esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
- "dev": true
- },
- "estraverse": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
- "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
- "dev": true
- },
- "esutils": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
- "dev": true
- },
- "extend": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "dev": true
- },
- "extsprintf": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
- "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
- "dev": true
- },
- "fast-deep-equal": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
- },
- "fast-json-patch": {
- "version": "3.0.0-1"
- },
- "fast-json-stable-stringify": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
- "dev": true
- },
- "fast-levenshtein": {
- "version": "2.0.6",
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
- "dev": true
- },
- "fdir": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/fdir/-/fdir-5.1.0.tgz",
- "integrity": "sha512-IgTtZwL52tx2wqWeuGDzXYTnNsEjNLahZpJw30hCQDyVnoHXwY5acNDnjGImTTL1R0z1PCyLw20VAbE5qLic3Q==",
- "dev": true
- },
- "forever-agent": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
- "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
- "dev": true
- },
- "form-data": {
- "version": "2.3.3",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
- "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
- "dev": true,
- "requires": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.6",
- "mime-types": "^2.1.12"
- }
- },
- "fs-minipass": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
- "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
- "dev": true,
- "requires": {
- "minipass": "^3.0.0"
- }
- },
- "fs.realpath": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
- "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",
- "dev": true
- },
- "gauge": {
- "version": "2.7.4",
- "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
- "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
- "dev": true,
- "requires": {
- "aproba": "^1.0.3",
- "console-control-strings": "^1.0.0",
- "has-unicode": "^2.0.0",
- "object-assign": "^4.1.0",
- "signal-exit": "^3.0.0",
- "string-width": "^1.0.1",
- "strip-ansi": "^3.0.1",
- "wide-align": "^1.1.0"
- }
- },
- "getpass": {
- "version": "0.1.7",
- "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
- "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
- "dev": true,
- "requires": {
- "assert-plus": "^1.0.0"
- }
- },
- "glob": {
- "version": "7.1.7",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
- "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
- "dev": true,
- "requires": {
- "fs.realpath": "^1.0.0",
- "inflight": "^1.0.4",
- "inherits": "2",
- "minimatch": "^3.0.4",
- "once": "^1.3.0",
- "path-is-absolute": "^1.0.0"
- }
- },
- "graceful-fs": {
- "version": "4.2.6",
- "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
- "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==",
- "dev": true
- },
- "har-schema": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
- "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
- "dev": true
- },
- "har-validator": {
- "version": "5.1.5",
- "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
- "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
- "dev": true,
- "requires": {
- "ajv": "^6.12.3",
- "har-schema": "^2.0.0"
- }
- },
- "has": {
- "version": "1.0.3",
- "dev": true,
- "requires": {
- "function-bind": "^1.1.1"
- }
- },
- "has-unicode": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
- "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
- "dev": true
- },
- "hosted-git-info": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.0.2.tgz",
- "integrity": "sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg==",
- "dev": true,
- "requires": {
- "lru-cache": "^6.0.0"
- }
- },
- "htm": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/htm/-/htm-3.0.4.tgz",
- "integrity": "sha512-VRdvxX3tmrXuT/Ovt59NMp/ORMFi4bceFMDjos1PV4E0mV+5votuID8R60egR9A4U8nLt238R/snlJGz3UYiTQ=="
- },
- "html-encoding-sniffer": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz",
- "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==",
- "dev": true,
- "requires": {
- "whatwg-encoding": "^1.0.5"
- }
- },
- "http-cache-semantics": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
- "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==",
- "dev": true
- },
- "http-proxy-agent": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
- "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==",
- "dev": true,
- "requires": {
- "@tootallnate/once": "1",
- "agent-base": "6",
- "debug": "4"
- }
- },
- "http-signature": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
- "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
- "dev": true,
- "requires": {
- "assert-plus": "^1.0.0",
- "jsprim": "^1.2.2",
- "sshpk": "^1.7.0"
- }
- },
- "https-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
- "dev": true,
- "requires": {
- "agent-base": "6",
- "debug": "4"
- }
- },
- "humanize-ms": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
- "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
- "dev": true,
- "requires": {
- "ms": "^2.0.0"
- }
- },
- "iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
- "dev": true,
- "requires": {
- "safer-buffer": ">= 2.1.2 < 3"
- }
- },
- "idom-app-react": {
- "version": "file:packages/idom-app-react",
- "requires": {
- "idom-client-react": "file:packages/idom-client-react",
- "prettier": "^2.5.1",
- "react": "^16.13.1",
- "react-dom": "^16.13.1"
- },
- "dependencies": {
- "idom-client-react": {
- "version": "file:packages/idom-app-react/packages/idom-client-react"
- }
- }
- },
- "idom-client-react": {
- "version": "file:packages/idom-client-react",
- "requires": {
- "fast-json-patch": "^3.0.0-1",
- "htm": "^3.0.3",
- "jsdom": "16.3.0",
- "lodash": "^4.17.21",
- "prettier": "^2.5.1",
- "uvu": "^0.5.1"
- }
- },
- "ignore-walk": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz",
- "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==",
- "dev": true,
- "requires": {
- "minimatch": "^3.0.4"
- }
- },
- "imurmurhash": {
- "version": "0.1.4",
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
- "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
- "dev": true
- },
- "indent-string": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
- "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
- "dev": true
- },
- "infer-owner": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
- "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
- "dev": true
- },
- "inflight": {
- "version": "1.0.6",
- "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
- "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
- "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
- },
- "ip": {
- "version": "1.1.5",
- "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
- "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
- "dev": true
- },
- "ip-regex": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz",
- "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=",
- "dev": true
- },
- "is-core-module": {
- "version": "2.2.0",
- "dev": true,
- "requires": {
- "has": "^1.0.3"
- }
- },
- "is-docker": {
- "version": "2.1.1",
- "dev": true
- },
- "is-fullwidth-code-point": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
- "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
- "dev": true,
- "requires": {
- "number-is-nan": "^1.0.0"
- }
- },
- "is-lambda": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
- "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=",
- "dev": true
- },
- "is-potential-custom-element-name": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
- "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
- "dev": true
- },
- "is-typedarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
- "dev": true
- },
- "is-wsl": {
- "version": "2.2.0",
- "dev": true,
- "requires": {
- "is-docker": "^2.0.0"
- }
- },
- "isarray": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
- "dev": true
- },
- "isexe": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
- "dev": true
- },
- "isstream": {
- "version": "0.1.2",
- "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
- "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
- "dev": true
- },
- "js-tokens": {
- "version": "4.0.0"
- },
- "jsbn": {
- "version": "0.1.1",
- "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
- "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
- "dev": true
- },
- "jsdom": {
- "version": "16.3.0",
- "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.3.0.tgz",
- "integrity": "sha512-zggeX5UuEknpdZzv15+MS1dPYG0J/TftiiNunOeNxSl3qr8Z6cIlQpN0IdJa44z9aFxZRIVqRncvEhQ7X5DtZg==",
- "dev": true,
- "requires": {
- "abab": "^2.0.3",
- "acorn": "^7.1.1",
- "acorn-globals": "^6.0.0",
- "cssom": "^0.4.4",
- "cssstyle": "^2.2.0",
- "data-urls": "^2.0.0",
- "decimal.js": "^10.2.0",
- "domexception": "^2.0.1",
- "escodegen": "^1.14.1",
- "html-encoding-sniffer": "^2.0.1",
- "is-potential-custom-element-name": "^1.0.0",
- "nwsapi": "^2.2.0",
- "parse5": "5.1.1",
- "request": "^2.88.2",
- "request-promise-native": "^1.0.8",
- "saxes": "^5.0.0",
- "symbol-tree": "^3.2.4",
- "tough-cookie": "^3.0.1",
- "w3c-hr-time": "^1.0.2",
- "w3c-xmlserializer": "^2.0.0",
- "webidl-conversions": "^6.1.0",
- "whatwg-encoding": "^1.0.5",
- "whatwg-mimetype": "^2.3.0",
- "whatwg-url": "^8.0.0",
- "ws": "^7.2.3",
- "xml-name-validator": "^3.0.0"
- }
- },
- "json-parse-even-better-errors": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
- "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
- "dev": true
- },
- "json-schema": {
- "version": "0.2.3",
- "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
- "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
- "dev": true
- },
- "json-schema-traverse": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
- },
- "json-stringify-safe": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
- "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
- "dev": true
- },
- "jsonparse": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
- "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=",
- "dev": true
- },
- "jsprim": {
- "version": "1.4.1",
- "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
- "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
- "dev": true,
- "requires": {
- "assert-plus": "1.0.0",
- "extsprintf": "1.3.0",
- "json-schema": "0.2.3",
- "verror": "1.10.0"
- }
- },
- "kleur": {
- "version": "4.1.4",
- "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.4.tgz",
- "integrity": "sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==",
- "dev": true
- },
- "levn": {
- "version": "0.3.0",
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
- "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
- "dev": true,
- "requires": {
- "prelude-ls": "~1.1.2",
- "type-check": "~0.3.2"
- }
- },
- "lodash": {
- "version": "4.17.21",
- "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
- "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
- "dev": true
- },
- "loose-envify": {
- "version": "1.4.0",
- "requires": {
- "js-tokens": "^3.0.0 || ^4.0.0"
- }
- },
- "lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "requires": {
- "yallist": "^4.0.0"
- }
- },
- "make-fetch-happen": {
- "version": "8.0.14",
- "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-8.0.14.tgz",
- "integrity": "sha512-EsS89h6l4vbfJEtBZnENTOFk8mCRpY5ru36Xe5bcX1KYIli2mkSHqoFsp5O1wMDvTJJzxe/4THpCTtygjeeGWQ==",
- "dev": true,
- "requires": {
- "agentkeepalive": "^4.1.3",
- "cacache": "^15.0.5",
- "http-cache-semantics": "^4.1.0",
- "http-proxy-agent": "^4.0.1",
- "https-proxy-agent": "^5.0.0",
- "is-lambda": "^1.0.1",
- "lru-cache": "^6.0.0",
- "minipass": "^3.1.3",
- "minipass-collect": "^1.0.2",
- "minipass-fetch": "^1.3.2",
- "minipass-flush": "^1.0.5",
- "minipass-pipeline": "^1.2.4",
- "promise-retry": "^2.0.1",
- "socks-proxy-agent": "^5.0.0",
- "ssri": "^8.0.0"
- }
- },
- "mime-db": {
- "version": "1.47.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz",
- "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==",
- "dev": true
- },
- "mime-types": {
- "version": "2.1.30",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz",
- "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==",
- "dev": true,
- "requires": {
- "mime-db": "1.47.0"
- }
- },
- "minimatch": {
- "version": "3.0.4",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
- "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
- "dev": true,
- "requires": {
- "brace-expansion": "^1.1.7"
- }
- },
- "minipass": {
- "version": "3.1.3",
- "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
- "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
- "dev": true,
- "requires": {
- "yallist": "^4.0.0"
- }
- },
- "minipass-collect": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
- "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
- "dev": true,
- "requires": {
- "minipass": "^3.0.0"
- }
- },
- "minipass-fetch": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.3.3.tgz",
- "integrity": "sha512-akCrLDWfbdAWkMLBxJEeWTdNsjML+dt5YgOI4gJ53vuO0vrmYQkUPxa6j6V65s9CcePIr2SSWqjT2EcrNseryQ==",
- "dev": true,
- "requires": {
- "encoding": "^0.1.12",
- "minipass": "^3.1.0",
- "minipass-sized": "^1.0.3",
- "minizlib": "^2.0.0"
- }
- },
- "minipass-flush": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
- "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
- "dev": true,
- "requires": {
- "minipass": "^3.0.0"
- }
- },
- "minipass-json-stream": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz",
- "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==",
- "dev": true,
- "requires": {
- "jsonparse": "^1.3.1",
- "minipass": "^3.0.0"
- }
- },
- "minipass-pipeline": {
- "version": "1.2.4",
- "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
- "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
- "dev": true,
- "requires": {
- "minipass": "^3.0.0"
- }
- },
- "minipass-sized": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
- "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
- "dev": true,
- "requires": {
- "minipass": "^3.0.0"
- }
- },
- "minizlib": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
- "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
- "dev": true,
- "requires": {
- "minipass": "^3.0.0",
- "yallist": "^4.0.0"
- }
- },
- "mkdirp": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
- "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
- "dev": true
- },
- "mri": {
- "version": "1.1.6",
- "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.6.tgz",
- "integrity": "sha512-oi1b3MfbyGa7FJMP9GmLTttni5JoICpYBRlq+x5V16fZbLsnL9N3wFqqIm/nIG43FjUFkFh9Epzp/kzUGUnJxQ==",
- "dev": true
- },
- "ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
- "dev": true
- },
- "node-gyp": {
- "version": "7.1.2",
- "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz",
- "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==",
- "dev": true,
- "requires": {
- "env-paths": "^2.2.0",
- "glob": "^7.1.4",
- "graceful-fs": "^4.2.3",
- "nopt": "^5.0.0",
- "npmlog": "^4.1.2",
- "request": "^2.88.2",
- "rimraf": "^3.0.2",
- "semver": "^7.3.2",
- "tar": "^6.0.2",
- "which": "^2.0.2"
- }
- },
- "nopt": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
- "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
- "dev": true,
- "requires": {
- "abbrev": "1"
- }
- },
- "npm-bundled": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz",
- "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==",
- "dev": true,
- "requires": {
- "npm-normalize-package-bin": "^1.0.1"
- }
- },
- "npm-install-checks": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz",
- "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==",
- "dev": true,
- "requires": {
- "semver": "^7.1.1"
- }
- },
- "npm-normalize-package-bin": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz",
- "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==",
- "dev": true
- },
- "npm-package-arg": {
- "version": "8.1.2",
- "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.2.tgz",
- "integrity": "sha512-6Eem455JsSMJY6Kpd3EyWE+n5hC+g9bSyHr9K9U2zqZb7+02+hObQ2c0+8iDk/mNF+8r1MhY44WypKJAkySIYA==",
- "dev": true,
- "requires": {
- "hosted-git-info": "^4.0.1",
- "semver": "^7.3.4",
- "validate-npm-package-name": "^3.0.0"
- }
- },
- "npm-packlist": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-2.2.2.tgz",
- "integrity": "sha512-Jt01acDvJRhJGthnUJVF/w6gumWOZxO7IkpY/lsX9//zqQgnF7OJaxgQXcerd4uQOLu7W5bkb4mChL9mdfm+Zg==",
- "dev": true,
- "requires": {
- "glob": "^7.1.6",
- "ignore-walk": "^3.0.3",
- "npm-bundled": "^1.1.1",
- "npm-normalize-package-bin": "^1.0.1"
- }
- },
- "npm-pick-manifest": {
- "version": "6.1.1",
- "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz",
- "integrity": "sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA==",
- "dev": true,
- "requires": {
- "npm-install-checks": "^4.0.0",
- "npm-normalize-package-bin": "^1.0.1",
- "npm-package-arg": "^8.1.2",
- "semver": "^7.3.4"
- }
- },
- "npm-registry-fetch": {
- "version": "10.1.2",
- "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-10.1.2.tgz",
- "integrity": "sha512-KsM/TdPmntqgBFlfsbkOLkkE9ovZo7VpVcd+/eTdYszCrgy5zFl5JzWm+OxavFaEWlbkirpkou+ZYI00RmOBFA==",
- "dev": true,
- "requires": {
- "lru-cache": "^6.0.0",
- "make-fetch-happen": "^8.0.9",
- "minipass": "^3.1.3",
- "minipass-fetch": "^1.3.0",
- "minipass-json-stream": "^1.0.1",
- "minizlib": "^2.0.0",
- "npm-package-arg": "^8.0.0"
- }
- },
- "npmlog": {
- "version": "4.1.2",
- "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
- "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
- "dev": true,
- "requires": {
- "are-we-there-yet": "~1.1.2",
- "console-control-strings": "~1.1.0",
- "gauge": "~2.7.3",
- "set-blocking": "~2.0.0"
- }
- },
- "number-is-nan": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
- "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
- "dev": true
- },
- "nwsapi": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz",
- "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==",
- "dev": true
- },
- "oauth-sign": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
- "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
- "dev": true
- },
- "object-assign": {
- "version": "4.1.1"
- },
- "once": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
- "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
- "dev": true,
- "requires": {
- "wrappy": "1"
- }
- },
- "open": {
- "version": "7.4.2",
- "dev": true,
- "requires": {
- "is-docker": "^2.0.0",
- "is-wsl": "^2.1.1"
- }
- },
- "optionator": {
- "version": "0.8.3",
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
- "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
- "dev": true,
- "requires": {
- "deep-is": "~0.1.3",
- "fast-levenshtein": "~2.0.6",
- "levn": "~0.3.0",
- "prelude-ls": "~1.1.2",
- "type-check": "~0.3.2",
- "word-wrap": "~1.2.3"
- }
- },
- "os-homedir": {
- "version": "1.0.2",
- "dev": true
- },
- "p-map": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
- "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
- "dev": true,
- "requires": {
- "aggregate-error": "^3.0.0"
- }
- },
- "pacote": {
- "version": "11.3.3",
- "resolved": "https://registry.npmjs.org/pacote/-/pacote-11.3.3.tgz",
- "integrity": "sha512-GQxBX+UcVZrrJRYMK2HoG+gPeSUX/rQhnbPkkGrCYa4n2F/bgClFPaMm0nsdnYrxnmUy85uMHoFXZ0jTD0drew==",
- "dev": true,
- "requires": {
- "@npmcli/git": "^2.0.1",
- "@npmcli/installed-package-contents": "^1.0.6",
- "@npmcli/promise-spawn": "^1.2.0",
- "@npmcli/run-script": "^1.8.2",
- "cacache": "^15.0.5",
- "chownr": "^2.0.0",
- "fs-minipass": "^2.1.0",
- "infer-owner": "^1.0.4",
- "minipass": "^3.1.3",
- "mkdirp": "^1.0.3",
- "npm-package-arg": "^8.0.1",
- "npm-packlist": "^2.1.4",
- "npm-pick-manifest": "^6.0.0",
- "npm-registry-fetch": "^10.0.0",
- "promise-retry": "^2.0.1",
- "read-package-json-fast": "^2.0.1",
- "rimraf": "^3.0.2",
- "ssri": "^8.0.1",
- "tar": "^6.1.0"
- }
- },
- "parse5": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz",
- "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==",
- "dev": true
- },
- "path-is-absolute": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
- "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
- "dev": true
- },
- "path-parse": {
- "version": "1.0.6",
- "dev": true
- },
- "performance-now": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
- "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
- "dev": true
- },
- "picomatch": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz",
- "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==",
- "dev": true
- },
- "pify": {
- "version": "2.3.0",
- "dev": true
- },
- "prelude-ls": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
- "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
- "dev": true
- },
- "prettier": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
- "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
- "dev": true
- },
- "process-nextick-args": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
- "dev": true
- },
- "promise-inflight": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
- "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=",
- "dev": true
- },
- "promise-retry": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
- "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
- "dev": true,
- "requires": {
- "err-code": "^2.0.2",
- "retry": "^0.12.0"
- }
- },
- "prop-types": {
- "version": "15.7.2",
- "requires": {
- "loose-envify": "^1.4.0",
- "object-assign": "^4.1.1",
- "react-is": "^16.8.1"
- }
- },
- "psl": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
- "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==",
- "dev": true
- },
- "punycode": {
- "version": "2.1.1",
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
- "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
- "dev": true
- },
- "qs": {
- "version": "6.5.2",
- "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
- "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
- "dev": true
- },
- "react": {
- "version": "16.14.0",
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2"
- }
- },
- "react-dom": {
- "version": "16.14.0",
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1",
- "prop-types": "^15.6.2",
- "scheduler": "^0.19.1"
- }
- },
- "react-is": {
- "version": "16.13.1"
- },
- "read-package-json-fast": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.2.tgz",
- "integrity": "sha512-5fyFUyO9B799foVk4n6ylcoAktG/FbE3jwRKxvwaeSrIunaoMc0u81dzXxjeAFKOce7O5KncdfwpGvvs6r5PsQ==",
- "dev": true,
- "requires": {
- "json-parse-even-better-errors": "^2.3.0",
- "npm-normalize-package-bin": "^1.0.1"
- }
- },
- "readable-stream": {
- "version": "2.3.7",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
- "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
- "dev": true,
- "requires": {
- "core-util-is": "~1.0.0",
- "inherits": "~2.0.3",
- "isarray": "~1.0.0",
- "process-nextick-args": "~2.0.0",
- "safe-buffer": "~5.1.1",
- "string_decoder": "~1.1.1",
- "util-deprecate": "~1.0.1"
- },
- "dependencies": {
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
- }
- }
- },
- "request": {
- "version": "2.88.2",
- "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
- "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
- "dev": true,
- "requires": {
- "aws-sign2": "~0.7.0",
- "aws4": "^1.8.0",
- "caseless": "~0.12.0",
- "combined-stream": "~1.0.6",
- "extend": "~3.0.2",
- "forever-agent": "~0.6.1",
- "form-data": "~2.3.2",
- "har-validator": "~5.1.3",
- "http-signature": "~1.2.0",
- "is-typedarray": "~1.0.0",
- "isstream": "~0.1.2",
- "json-stringify-safe": "~5.0.1",
- "mime-types": "~2.1.19",
- "oauth-sign": "~0.9.0",
- "performance-now": "^2.1.0",
- "qs": "~6.5.2",
- "safe-buffer": "^5.1.2",
- "tough-cookie": "~2.5.0",
- "tunnel-agent": "^0.6.0",
- "uuid": "^3.3.2"
- },
- "dependencies": {
- "tough-cookie": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
- "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
- "dev": true,
- "requires": {
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
- }
- }
- }
- },
- "request-promise-core": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz",
- "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==",
- "dev": true,
- "requires": {
- "lodash": "^4.17.19"
- }
- },
- "request-promise-native": {
- "version": "1.0.9",
- "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz",
- "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==",
- "dev": true,
- "requires": {
- "request-promise-core": "1.1.4",
- "stealthy-require": "^1.1.1",
- "tough-cookie": "^2.3.3"
- },
- "dependencies": {
- "tough-cookie": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
- "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
- "dev": true,
- "requires": {
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
- }
- }
- }
- },
- "resolve": {
- "version": "1.20.0",
- "dev": true,
- "requires": {
- "is-core-module": "^2.2.0",
- "path-parse": "^1.0.6"
- }
- },
- "retry": {
- "version": "0.12.0",
- "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
- "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=",
- "dev": true
- },
- "rimraf": {
- "version": "3.0.2",
- "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
- "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
- "dev": true,
- "requires": {
- "glob": "^7.1.3"
- }
- },
- "rollup": {
- "version": "2.37.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.37.1.tgz",
- "integrity": "sha512-V3ojEeyGeSdrMSuhP3diBb06P+qV4gKQeanbDv+Qh/BZbhdZ7kHV0xAt8Yjk4GFshq/WjO7R4c7DFM20AwTFVQ==",
- "dev": true,
- "requires": {
- "fsevents": "~2.1.2"
- },
- "dependencies": {
- "fsevents": {
- "version": "2.1.3",
- "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
- "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
- "dev": true,
- "optional": true
- }
- }
- },
- "sade": {
- "version": "1.7.4",
- "resolved": "https://registry.npmjs.org/sade/-/sade-1.7.4.tgz",
- "integrity": "sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==",
- "dev": true,
- "requires": {
- "mri": "^1.1.0"
- }
- },
- "safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "dev": true
- },
- "safer-buffer": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
- "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "dev": true
- },
- "saxes": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz",
- "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==",
- "dev": true,
- "requires": {
- "xmlchars": "^2.2.0"
- }
- },
- "scheduler": {
- "version": "0.19.1",
- "requires": {
- "loose-envify": "^1.1.0",
- "object-assign": "^4.1.1"
- }
- },
- "semver": {
- "version": "7.3.5",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
- "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
- "dev": true,
- "requires": {
- "lru-cache": "^6.0.0"
- }
- },
- "set-blocking": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
- "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
- "dev": true
- },
- "signal-exit": {
- "version": "3.0.3",
- "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
- "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
- "dev": true
- },
- "smart-buffer": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.1.0.tgz",
- "integrity": "sha512-iVICrxOzCynf/SNaBQCw34eM9jROU/s5rzIhpOvzhzuYHfJR/DhZfDkXiZSgKXfgv26HT3Yni3AV/DGw0cGnnw==",
- "dev": true
- },
- "snowpack": {
- "version": "3.5.2",
- "resolved": "https://registry.npmjs.org/snowpack/-/snowpack-3.5.2.tgz",
- "integrity": "sha512-TQQT5PXxeDr4gaMbp6nQrTDLX+Y8G5qI2wLqQdHLrpQEnq7W+gysn94+0xbOhnx0pFoVlSoFPjdQ83sETWl/9A==",
- "dev": true,
- "requires": {
- "cli-spinners": "^2.5.0",
- "default-browser-id": "^2.0.0",
- "esbuild": "^0.9.3",
- "fdir": "^5.0.0",
- "fsevents": "^2.2.0",
- "open": "^7.0.4",
- "pacote": "^11.3.1",
- "picomatch": "^2.2.2",
- "resolve": "^1.20.0",
- "rollup": "~2.37.1"
- }
- },
- "socks": {
- "version": "2.6.1",
- "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz",
- "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==",
- "dev": true,
- "requires": {
- "ip": "^1.1.5",
- "smart-buffer": "^4.1.0"
- }
- },
- "socks-proxy-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz",
- "integrity": "sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==",
- "dev": true,
- "requires": {
- "agent-base": "6",
- "debug": "4",
- "socks": "^2.3.3"
- }
- },
- "sshpk": {
- "version": "1.16.1",
- "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
- "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
- "dev": true,
- "requires": {
- "asn1": "~0.2.3",
- "assert-plus": "^1.0.0",
- "bcrypt-pbkdf": "^1.0.0",
- "dashdash": "^1.12.0",
- "ecc-jsbn": "~0.1.1",
- "getpass": "^0.1.1",
- "jsbn": "~0.1.0",
- "safer-buffer": "^2.0.2",
- "tweetnacl": "~0.14.0"
- }
- },
- "ssri": {
- "version": "8.0.1",
- "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
- "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
- "dev": true,
- "requires": {
- "minipass": "^3.1.1"
- }
- },
- "stealthy-require": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
- "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
- "dev": true
- },
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "dev": true,
- "requires": {
- "safe-buffer": "~5.1.0"
- },
- "dependencies": {
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
- "dev": true
- }
- }
- },
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
- "dev": true,
- "requires": {
- "code-point-at": "^1.0.0",
- "is-fullwidth-code-point": "^1.0.0",
- "strip-ansi": "^3.0.0"
- }
- },
- "strip-ansi": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
- "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
- "dev": true,
- "requires": {
- "ansi-regex": "^2.0.0"
- }
- },
- "symbol-tree": {
- "version": "3.2.4",
- "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
- "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
- "dev": true
- },
- "tar": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.0.tgz",
- "integrity": "sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==",
- "dev": true,
- "requires": {
- "chownr": "^2.0.0",
- "fs-minipass": "^2.0.0",
- "minipass": "^3.0.0",
- "minizlib": "^2.1.1",
- "mkdirp": "^1.0.3",
- "yallist": "^4.0.0"
- }
- },
- "totalist": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/totalist/-/totalist-2.0.0.tgz",
- "integrity": "sha512-+Y17F0YzxfACxTyjfhnJQEe7afPA0GSpYlFkl2VFMxYP7jshQf9gXV7cH47EfToBumFThfKBvfAcoUn6fdNeRQ==",
- "dev": true
- },
- "tough-cookie": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",
- "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==",
- "dev": true,
- "requires": {
- "ip-regex": "^2.1.0",
- "psl": "^1.1.28",
- "punycode": "^2.1.1"
- }
- },
- "tr46": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz",
- "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==",
- "dev": true,
- "requires": {
- "punycode": "^2.1.1"
- }
- },
- "tunnel-agent": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
- "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
- "dev": true,
- "requires": {
- "safe-buffer": "^5.0.1"
- }
- },
- "tweetnacl": {
- "version": "0.14.5",
- "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
- "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
- "dev": true
- },
- "type-check": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
- "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
- "dev": true,
- "requires": {
- "prelude-ls": "~1.1.2"
- }
- },
- "unique-filename": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
- "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
- "dev": true,
- "requires": {
- "unique-slug": "^2.0.0"
- }
- },
- "unique-slug": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
- "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
- "dev": true,
- "requires": {
- "imurmurhash": "^0.1.4"
- }
- },
- "untildify": {
- "version": "2.1.0",
- "dev": true,
- "requires": {
- "os-homedir": "^1.0.0"
- }
- },
- "uri-js": {
- "version": "4.4.1",
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
- "requires": {
- "punycode": "^2.1.0"
- }
- },
- "util-deprecate": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
- "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
- "dev": true
- },
- "uuid": {
- "version": "3.4.0",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
- "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
- "dev": true
- },
- "uvu": {
- "version": "0.5.1",
- "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.1.tgz",
- "integrity": "sha512-JGxttnOGDFs77FaZ0yMUHIzczzQ5R1IlDeNW6Wymw6gAscwMdAffVOP6TlxLIfReZyK8tahoGwWZaTCJzNFDkg==",
- "dev": true,
- "requires": {
- "dequal": "^2.0.0",
- "diff": "^5.0.0",
- "kleur": "^4.0.3",
- "sade": "^1.7.3",
- "totalist": "^2.0.0"
- }
- },
- "validate-npm-package-name": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz",
- "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=",
- "dev": true,
- "requires": {
- "builtins": "^1.0.3"
- }
- },
- "verror": {
- "version": "1.10.0",
- "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
- "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
- "dev": true,
- "requires": {
- "assert-plus": "^1.0.0",
- "core-util-is": "1.0.2",
- "extsprintf": "^1.2.0"
- }
- },
- "w3c-hr-time": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
- "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==",
- "dev": true,
- "requires": {
- "browser-process-hrtime": "^1.0.0"
- }
- },
- "w3c-xmlserializer": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz",
- "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==",
- "dev": true,
- "requires": {
- "xml-name-validator": "^3.0.0"
- }
- },
- "webidl-conversions": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz",
- "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==",
- "dev": true
- },
- "whatwg-encoding": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",
- "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==",
- "dev": true,
- "requires": {
- "iconv-lite": "0.4.24"
- }
- },
- "whatwg-mimetype": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
- "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==",
- "dev": true
- },
- "whatwg-url": {
- "version": "8.5.0",
- "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz",
- "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==",
- "dev": true,
- "requires": {
- "lodash": "^4.7.0",
- "tr46": "^2.0.2",
- "webidl-conversions": "^6.1.0"
- }
- },
- "which": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
- "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
- "dev": true,
- "requires": {
- "isexe": "^2.0.0"
- }
- },
- "wide-align": {
- "version": "1.1.3",
- "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
- "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
- "dev": true,
- "requires": {
- "string-width": "^1.0.2 || 2"
- }
- },
- "word-wrap": {
- "version": "1.2.3",
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
- "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
- "dev": true
- },
- "wrappy": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
- "dev": true
- },
- "ws": {
- "version": "7.4.5",
- "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.5.tgz",
- "integrity": "sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g==",
- "dev": true,
- "requires": {}
- },
- "xml-name-validator": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
- "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==",
- "dev": true
- },
- "xmlchars": {
- "version": "2.2.0",
- "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
- "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
- "dev": true
- },
- "yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
- "dev": true
- }
- }
-}
diff --git a/src/client/package.json b/src/client/package.json
deleted file mode 100644
index 3f95403b3..000000000
--- a/src/client/package.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
- "devDependencies": {
- "snowpack": "^3.5.2"
- },
- "license": "MIT",
- "repository": {
- "type": "git",
- "url": "https://github.com/idom-team/idom"
- },
- "scripts": {
- "build": "snowpack build",
- "check-format": "npm --workspaces run check-format",
- "format": "npm --workspaces run format",
- "publish": "npm --workspaces publish",
- "test": "npm --workspaces test"
- },
- "version": "0.36.0",
- "workspaces": [
- "./packages/*"
- ]
-}
diff --git a/src/client/packages/idom-app-react/package.json b/src/client/packages/idom-app-react/package.json
deleted file mode 100644
index faf8bee43..000000000
--- a/src/client/packages/idom-app-react/package.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "author": "Ryan Morshead",
- "dependencies": {
- "idom-client-react": "file:packages/idom-client-react",
- "react": "^16.13.1",
- "react-dom": "^16.13.1"
- },
- "description": "A client application for IDOM implemented in React",
- "devDependencies": {
- "prettier": "^2.5.1"
- },
- "license": "MIT",
- "main": "src/index.js",
- "name": "idom-app-react",
- "repository": {
- "type": "git",
- "url": "https://github.com/idom-team/idom"
- },
- "scripts": {
- "check-format": "prettier --check ./src",
- "format": "prettier --write ./src",
- "test": "echo 'no tests'"
- },
- "version": "0.36.0"
-}
diff --git a/src/client/packages/idom-app-react/src/index.js b/src/client/packages/idom-app-react/src/index.js
deleted file mode 100644
index 311f5aeed..000000000
--- a/src/client/packages/idom-app-react/src/index.js
+++ /dev/null
@@ -1,36 +0,0 @@
-import { mountWithLayoutServer, LayoutServerInfo } from "idom-client-react";
-
-export function mount(mountPoint) {
- const serverInfo = new LayoutServerInfo({
- host: document.location.hostname,
- port: document.location.port,
- path: "../",
- query: queryParams.user.toString(),
- secture: document.location.protocol == "https",
- });
-
- mountWithLayoutServer(mountPoint, serverInfo, shouldReconnect() ? 45 : 0);
-}
-
-function shouldReconnect() {
- return queryParams.reserved.get("noReconnect") === null;
-}
-
-const queryParams = (() => {
- const reservedParams = new URLSearchParams();
- const userParams = new URLSearchParams(window.location.search);
-
- const reservedParamNames = ["noReconnect"];
- reservedParamNames.forEach((name) => {
- const value = userParams.get(name);
- if (value !== null) {
- reservedParams.append(name, userParams.get(name));
- userParams.delete(name);
- }
- });
-
- return {
- reserved: reservedParams,
- user: userParams,
- };
-})();
diff --git a/src/client/packages/idom-client-react/README.md b/src/client/packages/idom-client-react/README.md
deleted file mode 100644
index b4366c5c4..000000000
--- a/src/client/packages/idom-client-react/README.md
+++ /dev/null
@@ -1,2 +0,0 @@
-# idom-client-react
-A client for IDOM implemented in React
diff --git a/src/client/packages/idom-client-react/package.json b/src/client/packages/idom-client-react/package.json
deleted file mode 100644
index e4d5593cf..000000000
--- a/src/client/packages/idom-client-react/package.json
+++ /dev/null
@@ -1,35 +0,0 @@
-{
- "author": "Ryan Morshead",
- "dependencies": {
- "fast-json-patch": "^3.0.0-1",
- "htm": "^3.0.3"
- },
- "description": "A client for IDOM implemented in React",
- "devDependencies": {
- "jsdom": "16.3.0",
- "lodash": "^4.17.21",
- "prettier": "^2.5.1",
- "uvu": "^0.5.1"
- },
- "files": [
- "src/**/*.js"
- ],
- "license": "MIT",
- "main": "src/index.js",
- "name": "idom-client-react",
- "peerDependencies": {
- "react": ">=16",
- "react-dom": ">=16"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/idom-team/idom"
- },
- "scripts": {
- "check-format": "prettier --check ./src ./tests",
- "format": "prettier --write ./src ./tests",
- "test": "uvu tests"
- },
- "type": "module",
- "version": "0.36.0"
-}
diff --git a/src/client/packages/idom-client-react/src/components.js b/src/client/packages/idom-client-react/src/components.js
deleted file mode 100644
index 465f94801..000000000
--- a/src/client/packages/idom-client-react/src/components.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import React from "react";
-import ReactDOM from "react-dom";
-import htm from "htm";
-
-import { useJsonPatchCallback } from "./json-patch.js";
-import { loadModelImportSource } from "./import-source.js";
-import {
- createElementAttributes,
- createElementChildren,
-} from "./element-utils.js";
-
-const html = htm.bind(React.createElement);
-export const LayoutContext = React.createContext({
- sendEvent: undefined,
- loadImportSource: undefined,
-});
-
-export function Layout({ saveUpdateHook, sendEvent, loadImportSource }) {
- const [model, patchModel] = useJsonPatchCallback({});
-
- React.useEffect(() => saveUpdateHook(patchModel), [patchModel]);
-
- return html`
- <${LayoutContext.Provider} value=${{ sendEvent, loadImportSource }}>
- <${Element} model=${model} />
- />
- `;
-}
-
-export function Element({ model }) {
- if (!model.tagName) {
- if (model.error) {
- return html`${model.error} `;
- } else {
- return null;
- }
- } else if (model.tagName == "script") {
- return html`<${ScriptElement} model=${model} />`;
- } else if (model.importSource) {
- return html`<${ImportedElement} model=${model} />`;
- } else {
- return html`<${StandardElement} model=${model} />`;
- }
-}
-
-function StandardElement({ model }) {
- const layoutContext = React.useContext(LayoutContext);
- // Use createElement here to avoid warning about variable numbers of children not
- // having keys. Warning about this must now be the responsibility of the server
- // providing the models instead of the client rendering them.
- return React.createElement(
- model.tagName,
- createElementAttributes(model, layoutContext.sendEvent),
- ...createElementChildren(
- model,
- (model) => html`<${Element} key=${model.key} model=${model} />`
- )
- );
-}
-
-function ScriptElement({ model }) {
- const ref = React.useRef();
- React.useEffect(() => {
- if (model?.children?.length > 1) {
- console.error("Too many children for 'script' element.");
- }
-
- let scriptContent = model?.children?.[0];
-
- let scriptElement;
- if (model.attributes) {
- scriptElement = document.createElement("script");
- for (const [k, v] of Object.entries(model.attributes)) {
- scriptElement.setAttribute(k, v);
- }
- scriptElement.appendChild(document.createTextNode(scriptContent));
- ref.current.appendChild(scriptElement);
- } else {
- let scriptResult = eval(scriptContent);
- if (typeof scriptResult == "function") {
- return scriptResult();
- }
- }
- }, [model.key]);
- return html`
`;
-}
-
-function ImportedElement({ model }) {
- const layoutContext = React.useContext(LayoutContext);
-
- const importSourceFallback = model.importSource.fallback;
- const [importSource, setImportSource] = React.useState(null);
-
- if (!importSource) {
- // load the import source in the background
- loadModelImportSource(layoutContext, model.importSource).then(
- setImportSource
- );
-
- // display a fallback if one was given
- if (!importSourceFallback) {
- return html`
`;
- } else if (typeof importSourceFallback === "string") {
- return html`${importSourceFallback}
`;
- } else {
- return html`<${StandardElement} model=${importSourceFallback} />`;
- }
- } else {
- return html`<${_ImportedElement}
- model=${model}
- importSource=${importSource}
- />`;
- }
-}
-
-function _ImportedElement({ model, importSource }) {
- const layoutContext = React.useContext(LayoutContext);
- const mountPoint = React.useRef(null);
- const sourceBinding = React.useRef(null);
-
- React.useEffect(() => {
- sourceBinding.current = importSource.bind(mountPoint.current);
- if (!importSource.data.unmountBeforeUpdate) {
- return sourceBinding.current.unmount;
- }
- }, []);
-
- // this effect must run every time in case the model has changed
- React.useEffect(() => {
- sourceBinding.current.render(model);
- if (importSource.data.unmountBeforeUpdate) {
- return sourceBinding.current.unmount;
- }
- });
-
- return html`
`;
-}
diff --git a/src/client/packages/idom-client-react/src/element-utils.js b/src/client/packages/idom-client-react/src/element-utils.js
deleted file mode 100644
index 55709301f..000000000
--- a/src/client/packages/idom-client-react/src/element-utils.js
+++ /dev/null
@@ -1,52 +0,0 @@
-import { serializeEvent } from "./event-to-object.js";
-
-export function createElementChildren(model, createElement) {
- if (!model.children) {
- return [];
- } else {
- return model.children
- .filter((x) => x) // filter nulls
- .map((child) => {
- switch (typeof child) {
- case "object":
- return createElement(child);
- case "string":
- return child;
- }
- });
- }
-}
-
-export function createElementAttributes(model, sendEvent) {
- const attributes = Object.assign({}, model.attributes);
-
- if (model.eventHandlers) {
- for (const [eventName, eventSpec] of Object.entries(model.eventHandlers)) {
- attributes[eventName] = createEventHandler(sendEvent, eventSpec);
- }
- }
-
- return attributes;
-}
-
-function createEventHandler(sendEvent, eventSpec) {
- return function () {
- const data = Array.from(arguments).map((value) => {
- if (typeof value === "object" && value.nativeEvent) {
- if (eventSpec["preventDefault"]) {
- value.preventDefault();
- }
- if (eventSpec["stopPropagation"]) {
- value.stopPropagation();
- }
- return serializeEvent(value);
- } else {
- return value;
- }
- });
- sendEvent({
- data: data,
- target: eventSpec["target"],
- });
- };
-}
diff --git a/src/client/packages/idom-client-react/src/event-to-object.js b/src/client/packages/idom-client-react/src/event-to-object.js
deleted file mode 100644
index b11e65366..000000000
--- a/src/client/packages/idom-client-react/src/event-to-object.js
+++ /dev/null
@@ -1,210 +0,0 @@
-export function serializeEvent(event) {
- const data = {};
-
- if (event.type in eventTransforms) {
- Object.assign(data, eventTransforms[event.type](event));
- }
-
- data.target = serializeDomElement(event.target);
- data.currentTarget =
- event.target === event.currentTarget
- ? data.target
- : serializeDomElement(event.currentTarget);
- data.relatedTarget = serializeDomElement(event.relatedTarget);
-
- return data;
-}
-
-function serializeDomElement(element) {
- let elementData = null;
- if (element) {
- elementData = defaultElementTransform(element);
- if (element.tagName in elementTransforms) {
- elementTransforms[element.tagName].forEach((trans) =>
- Object.assign(elementData, trans(element))
- );
- }
- }
- return elementData;
-}
-
-const elementTransformCategories = {
- hasValue: (element) => ({
- value: element.value,
- }),
- hasCurrentTime: (element) => ({
- currentTime: element.currentTime,
- }),
- hasFiles: (element) => {
- if (element?.type === "file") {
- return {
- files: Array.from(element.files).map((file) => ({
- lastModified: file.lastModified,
- name: file.name,
- size: file.size,
- type: file.type,
- })),
- };
- } else {
- return {};
- }
- },
-};
-
-function defaultElementTransform(element) {
- return { boundingClientRect: element.getBoundingClientRect() };
-}
-
-const elementTagCategories = {
- hasValue: [
- "BUTTON",
- "INPUT",
- "OPTION",
- "LI",
- "METER",
- "PROGRESS",
- "PARAM",
- "SELECT",
- "TEXTAREA",
- ],
- hasCurrentTime: ["AUDIO", "VIDEO"],
- hasFiles: ["INPUT"],
-};
-
-const elementTransforms = {};
-
-Object.keys(elementTagCategories).forEach((category) => {
- elementTagCategories[category].forEach((type) => {
- const transforms =
- elementTransforms[type] || (elementTransforms[type] = []);
- transforms.push(elementTransformCategories[category]);
- });
-});
-
-function EventTransformCategories() {
- this.clipboard = (event) => ({
- clipboardData: event.clipboardData,
- });
- this.composition = (event) => ({
- data: event.data,
- });
- this.keyboard = (event) => ({
- altKey: event.altKey,
- charCode: event.charCode,
- ctrlKey: event.ctrlKey,
- key: event.key,
- keyCode: event.keyCode,
- locale: event.locale,
- location: event.location,
- metaKey: event.metaKey,
- repeat: event.repeat,
- shiftKey: event.shiftKey,
- which: event.which,
- });
- this.mouse = (event) => ({
- altKey: event.altKey,
- button: event.button,
- buttons: event.buttons,
- clientX: event.clientX,
- clientY: event.clientY,
- ctrlKey: event.ctrlKey,
- metaKey: event.metaKey,
- pageX: event.pageX,
- pageY: event.pageY,
- screenX: event.screenX,
- screenY: event.screenY,
- shiftKey: event.shiftKey,
- });
- this.pointer = (event) => ({
- ...this.mouse(event),
- pointerId: event.pointerId,
- width: event.width,
- height: event.height,
- pressure: event.pressure,
- tiltX: event.tiltX,
- tiltY: event.tiltY,
- pointerType: event.pointerType,
- isPrimary: event.isPrimary,
- });
- this.selection = () => {
- return { selectedText: window.getSelection().toString() };
- };
- this.touch = (event) => ({
- altKey: event.altKey,
- ctrlKey: event.ctrlKey,
- metaKey: event.metaKey,
- shiftKey: event.shiftKey,
- });
- this.ui = (event) => ({
- detail: event.detail,
- });
- this.wheel = (event) => ({
- deltaMode: event.deltaMode,
- deltaX: event.deltaX,
- deltaY: event.deltaY,
- deltaZ: event.deltaZ,
- });
- this.animation = (event) => ({
- animationName: event.animationName,
- pseudoElement: event.pseudoElement,
- elapsedTime: event.elapsedTime,
- });
- this.transition = (event) => ({
- propertyName: event.propertyName,
- pseudoElement: event.pseudoElement,
- elapsedTime: event.elapsedTime,
- });
-}
-
-const eventTypeCategories = {
- clipboard: ["copy", "cut", "paste"],
- composition: ["compositionend", "compositionstart", "compositionupdate"],
- keyboard: ["keydown", "keypress", "keyup"],
- mouse: [
- "click",
- "contextmenu",
- "doubleclick",
- "drag",
- "dragend",
- "dragenter",
- "dragexit",
- "dragleave",
- "dragover",
- "dragstart",
- "drop",
- "mousedown",
- "mouseenter",
- "mouseleave",
- "mousemove",
- "mouseout",
- "mouseover",
- "mouseup",
- ],
- pointer: [
- "pointerdown",
- "pointermove",
- "pointerup",
- "pointercancel",
- "gotpointercapture",
- "lostpointercapture",
- "pointerenter",
- "pointerleave",
- "pointerover",
- "pointerout",
- ],
- selection: ["select"],
- touch: ["touchcancel", "touchend", "touchmove", "touchstart"],
- ui: ["scroll"],
- wheel: ["wheel"],
- animation: ["animationstart", "animationend", "animationiteration"],
- transition: ["transitionend"],
-};
-
-const eventTransforms = {};
-
-const eventTransformCategories = new EventTransformCategories();
-Object.keys(eventTypeCategories).forEach((category) => {
- eventTypeCategories[category].forEach((type) => {
- eventTransforms[type] = eventTransformCategories[category];
- });
-});
diff --git a/src/client/packages/idom-client-react/src/import-source.js b/src/client/packages/idom-client-react/src/import-source.js
deleted file mode 100644
index de6ebdda6..000000000
--- a/src/client/packages/idom-client-react/src/import-source.js
+++ /dev/null
@@ -1,109 +0,0 @@
-import {
- createElementAttributes,
- createElementChildren,
-} from "./element-utils.js";
-
-export function loadModelImportSource(layoutContext, importSource) {
- return layoutContext
- .loadImportSource(importSource.source, importSource.sourceType)
- .then((module) => {
- if (typeof module.bind === "function") {
- return {
- data: importSource,
- bind: (node) => {
- const shortImportSource = {
- source: importSource.source,
- sourceType: importSource.sourceType,
- };
- const binding = module.bind(node, layoutContext);
- if (
- typeof binding.create === "function" &&
- typeof binding.render === "function" &&
- typeof binding.unmount === "function"
- ) {
- return {
- render: (model) =>
- binding.render(
- createElementFromModuleBinding(
- layoutContext,
- importSource,
- module,
- binding,
- model
- )
- ),
- unmount: binding.unmount,
- };
- } else {
- console.error(
- `${importSource.source} returned an impropper binding`
- );
- }
- },
- };
- } else {
- console.error(
- `${importSource.source} did not export a function 'bind'`
- );
- }
- });
-}
-
-function createElementFromModuleBinding(
- layoutContext,
- currentImportSource,
- module,
- binding,
- model
-) {
- let type;
- if (model.importSource) {
- if (!isImportSourceEqual(currentImportSource, model.importSource)) {
- console.error(
- "Parent element import source " +
- stringifyImportSource(currentImportSource) +
- " does not match child's import source " +
- stringifyImportSource(model.importSource)
- );
- return null;
- } else if (!module[model.tagName]) {
- console.error(
- "Module from source " +
- stringifyImportSource(currentImportSource) +
- ` does not export ${model.tagName}`
- );
- return null;
- } else {
- type = module[model.tagName];
- }
- } else {
- type = model.tagName;
- }
- return binding.create(
- type,
- createElementAttributes(model, layoutContext.sendEvent),
- createElementChildren(model, (child) =>
- createElementFromModuleBinding(
- layoutContext,
- currentImportSource,
- module,
- binding,
- child
- )
- )
- );
-}
-
-function isImportSourceEqual(source1, source2) {
- return (
- source1.source === source2.source &&
- source1.sourceType === source2.sourceType
- );
-}
-
-function stringifyImportSource(importSource) {
- return JSON.stringify({
- source: importSource.source,
- sourceType: importSource.sourceType,
- });
-}
diff --git a/src/client/packages/idom-client-react/src/index.js b/src/client/packages/idom-client-react/src/index.js
deleted file mode 100644
index bee7de4fd..000000000
--- a/src/client/packages/idom-client-react/src/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export * from "./mount.js";
-export * from "./components.js";
-export * from "./server.js";
diff --git a/src/client/packages/idom-client-react/src/json-patch.js b/src/client/packages/idom-client-react/src/json-patch.js
deleted file mode 100644
index 5323f11a9..000000000
--- a/src/client/packages/idom-client-react/src/json-patch.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import React from "react";
-import jsonpatch from "fast-json-patch";
-
-export function useJsonPatchCallback(initial) {
- const doc = React.useRef(initial);
- const forceUpdate = useForceUpdate();
-
- const applyPatch = React.useCallback(
- (path, patch) => {
- if (!path) {
- // We CANNOT mutate the part of the document because React checks some
- // attributes of the model (e.g. model.attributes.style is checked for
- // identity).
- doc.current = applyNonMutativePatch(
- doc.current,
- patch,
- false,
- false,
- true
- );
- } else {
- // We CAN mutate the document here though because we know that nothing above
- // The patch `path` is changing. Thus, maintaining the identity for that section
- // of the model is accurate.
- applyMutativePatch(doc.current, [
- {
- op: "replace",
- path: path,
- // We CANNOT mutate the part of the document where the actual patch is being
- // applied. Instead we create a copy because React checks some attributes of
- // the model (e.g. model.attributes.style is checked for identity). The part
- // of the document above the `path` can be mutated though because we know it
- // has not changed.
- value: applyNonMutativePatch(
- jsonpatch.getValueByPointer(doc.current, path),
- patch
- ),
- },
- ]);
- }
- forceUpdate();
- },
- [doc]
- );
-
- return [doc.current, applyPatch];
-}
-
-function applyNonMutativePatch(doc, patch) {
- return jsonpatch.applyPatch(doc, patch, false, false, true).newDocument;
-}
-
-function applyMutativePatch(doc, patch) {
- jsonpatch.applyPatch(doc, patch, false, true, true).newDocument;
-}
-
-function useForceUpdate() {
- const [, updateState] = React.useState();
- return React.useCallback(() => updateState({}), []);
-}
diff --git a/src/client/packages/idom-client-react/src/mount.js b/src/client/packages/idom-client-react/src/mount.js
deleted file mode 100644
index 926f2a8ae..000000000
--- a/src/client/packages/idom-client-react/src/mount.js
+++ /dev/null
@@ -1,105 +0,0 @@
-import React from "react";
-import ReactDOM from "react-dom";
-import { Layout } from "./components.js";
-
-export function mountLayout(mountElement, layoutProps) {
- ReactDOM.render(React.createElement(Layout, layoutProps), mountElement);
-}
-
-export function mountLayoutWithWebSocket(
- element,
- endpoint,
- loadImportSource,
- maxReconnectTimeout
-) {
- mountLayoutWithReconnectingWebSocket(
- element,
- endpoint,
- loadImportSource,
- maxReconnectTimeout
- );
-}
-
-function mountLayoutWithReconnectingWebSocket(
- element,
- endpoint,
- loadImportSource,
- maxReconnectTimeout,
- mountState = {
- everMounted: false,
- reconnectAttempts: 0,
- reconnectTimeoutRange: 0,
- }
-) {
- const socket = new WebSocket(endpoint);
-
- const updateHookPromise = new LazyPromise();
-
- socket.onopen = (event) => {
- console.info(`IDOM WebSocket connected.`);
-
- if (mountState.everMounted) {
- ReactDOM.unmountComponentAtNode(element);
- }
- _resetOpenMountState(mountState);
-
- mountLayout(element, {
- loadImportSource,
- saveUpdateHook: updateHookPromise.resolve,
- sendEvent: (event) => socket.send(JSON.stringify(event)),
- });
- };
-
- socket.onmessage = (event) => {
- const [pathPrefix, patch] = JSON.parse(event.data);
- updateHookPromise.promise.then((update) => update(pathPrefix, patch));
- };
-
- socket.onclose = (event) => {
- if (!maxReconnectTimeout) {
- console.info(`IDOM WebSocket connection lost.`);
- return;
- }
-
- const reconnectTimeout = _nextReconnectTimeout(
- maxReconnectTimeout,
- mountState
- );
-
- console.info(
- `IDOM WebSocket connection lost. Reconnecting in ${reconnectTimeout} seconds...`
- );
-
- setTimeout(function () {
- mountState.reconnectAttempts++;
- mountLayoutWithReconnectingWebSocket(
- element,
- endpoint,
- loadImportSource,
- maxReconnectTimeout,
- mountState
- );
- }, reconnectTimeout * 1000);
- };
-}
-
-function _resetOpenMountState(mountState) {
- mountState.everMounted = true;
- mountState.reconnectAttempts = 0;
- mountState.reconnectTimeoutRange = 0;
-}
-
-function _nextReconnectTimeout(maxReconnectTimeout, mountState) {
- const timeout =
- Math.floor(Math.random() * mountState.reconnectTimeoutRange) || 1;
- mountState.reconnectTimeoutRange =
- (mountState.reconnectTimeoutRange + 5) % maxReconnectTimeout;
- return timeout;
-}
-
-function LazyPromise() {
- this.promise = new Promise((resolve, reject) => {
- this.resolve = resolve;
- this.reject = reject;
- });
-}
diff --git a/src/client/packages/idom-client-react/src/server.js b/src/client/packages/idom-client-react/src/server.js
deleted file mode 100644
index 41428873e..000000000
--- a/src/client/packages/idom-client-react/src/server.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import React from "react";
-import ReactDOM from "react-dom";
-import { mountLayoutWithWebSocket } from "./mount.js";
-
-export function mountWithLayoutServer(
- element,
- serverInfo,
- maxReconnectTimeout
-) {
- const loadImportSource = (source, sourceType) =>
- import(sourceType == "NAME" ? serverInfo.path.module(source) : source);
-
- mountLayoutWithWebSocket(
- element,
- serverInfo.path.stream,
- loadImportSource,
- maxReconnectTimeout
- );
-}
-
-export function LayoutServerInfo({ host, port, path, query, secure }) {
- const wsProtocol = "ws" + (secure ? "s" : "");
- const httpProtocol = "http" + (secure ? "s" : "");
-
- const uri = host + ":" + port;
- path = new URL(path, document.baseURI).pathname;
- const url = (uri + path).split("/").slice(0, -1).join("/");
-
- const wsBaseUrl = wsProtocol + "://" + url;
- const httpBaseUrl = httpProtocol + "://" + url;
-
- if (query) {
- query = "?" + query;
- } else {
- query = "";
- }
-
- this.path = {
- stream: wsBaseUrl + "/stream" + query,
- module: (source) => httpBaseUrl + `/modules/${source}`,
- };
-}
diff --git a/src/client/packages/idom-client-react/tests/event-to-object.test.js b/src/client/packages/idom-client-react/tests/event-to-object.test.js
deleted file mode 100644
index 3dd3852fd..000000000
--- a/src/client/packages/idom-client-react/tests/event-to-object.test.js
+++ /dev/null
@@ -1,330 +0,0 @@
-import { test } from "uvu";
-import lodash from "lodash";
-import * as assert from "uvu/assert";
-import { serializeEvent } from "../src/event-to-object.js";
-import "./tooling/setup.js";
-
-function assertEqualSerializedEventData(eventData, expectedSerializedData) {
- const mockBoundingRect = {
- left: 0,
- top: 0,
- right: 0,
- bottom: 0,
- x: 0,
- y: 0,
- width: 0,
- };
-
- const mockElement = {
- tagName: null,
- getBoundingClientRect: () => mockBoundingRect,
- };
-
- const commonEventData = {
- target: mockElement,
- currentTarget: mockElement,
- relatedTarget: mockElement,
- };
-
- const commonSerializedEventData = {
- target: { boundingClientRect: mockBoundingRect },
- currentTarget: { boundingClientRect: mockBoundingRect },
- relatedTarget: { boundingClientRect: mockBoundingRect },
- };
-
- assert.equal(
- serializeEvent(lodash.merge({}, commonEventData, eventData)),
- lodash.merge({}, commonSerializedEventData, expectedSerializedData)
- );
-}
-
-const allTargetData = {
- files: [
- {
- lastModified: 0,
- name: "something",
- type: "some-type",
- size: 0,
- },
- ],
- value: "something",
- currentTime: 35,
- tagName: null, // overwritten in tests
-};
-
-[
- {
- case: "adds 'files' and 'value' attributes for INPUT if type=file",
- tagName: "INPUT",
- addTargetAttrs: { type: "file" },
- output: {
- target: {
- files: allTargetData.files,
- value: allTargetData.value,
- },
- },
- },
- ...["BUTTON", "INPUT", "OPTION", "LI", "METER", "PROGRESS", "PARAM"].map(
- (tagName) => ({
- case: `adds 'value' attribute for ${tagName} element`,
- tagName,
- output: { target: { value: allTargetData.value } },
- })
- ),
- ...["AUDIO", "VIDEO"].map((tagName) => ({
- case: `adds 'currentTime' attribute for ${tagName} element`,
- tagName,
- output: { target: { currentTime: allTargetData.currentTime } },
- })),
-].forEach((expectation) => {
- test(`serializeEvent() ${expectation.case}`, () => {
- const eventData = {
- target: {
- ...allTargetData,
- tagName: expectation.tagName,
- },
- };
- if (expectation.addTargetAttrs) {
- Object.assign(eventData.target, expectation.addTargetAttrs);
- }
- assertEqualSerializedEventData(eventData, expectation.output);
- });
-});
-
-const allEventData = {
- type: null, // set in text
- target: { tagName: null }, // avoid triggering target specific transformations
- clipboardData: "clipboardData",
- data: "data",
- altKey: "altKey",
- charCode: "charCode",
- ctrlKey: "ctrlKey",
- key: "key",
- keyCode: "keyCode",
- locale: "locale",
- location: "location",
- metaKey: "metaKey",
- repeat: "repeat",
- shiftKey: "shiftKey",
- which: "which",
- altKey: "altKey",
- button: "button",
- buttons: "buttons",
- clientX: "clientX",
- clientY: "clientY",
- ctrlKey: "ctrlKey",
- metaKey: "metaKey",
- pageX: "pageX",
- pageY: "pageY",
- screenX: "screenX",
- screenY: "screenY",
- shiftKey: "shiftKey",
- pointerId: "pointerId",
- width: "width",
- height: "height",
- pressure: "pressure",
- tiltX: "tiltX",
- tiltY: "tiltY",
- pointerType: "pointerType",
- isPrimary: "isPrimary",
- altKey: "altKey",
- ctrlKey: "ctrlKey",
- metaKey: "metaKey",
- shiftKey: "shiftKey",
- detail: "detail",
- deltaMode: "deltaMode",
- deltaX: "deltaX",
- deltaY: "deltaY",
- deltaZ: "deltaZ",
- animationName: "animationName",
- pseudoElement: "pseudoElement",
- elapsedTime: "elapsedTime",
- propertyName: "propertyName",
- pseudoElement: "pseudoElement",
- elapsedTime: "elapsedTime",
-};
-
-[
- ...["copy", "cut", "paste"].map((eventType) => ({
- eventType,
- case: "clipboard",
- output: { clipboardData: "clipboardData" },
- })),
- ...["compositionend", "compositionstart", "compositionupdate"].map(
- (eventType) => ({
- eventType,
- case: "composition",
- output: { data: "data" },
- })
- ),
- ...["keydown", "keypress", "keyup"].map((eventType) => ({
- eventType,
- case: "keyboard",
- output: {
- altKey: "altKey",
- charCode: "charCode",
- ctrlKey: "ctrlKey",
- key: "key",
- keyCode: "keyCode",
- locale: "locale",
- location: "location",
- metaKey: "metaKey",
- repeat: "repeat",
- shiftKey: "shiftKey",
- which: "which",
- },
- })),
- ...[
- "click",
- "contextmenu",
- "doubleclick",
- "drag",
- "dragend",
- "dragenter",
- "dragexit",
- "dragleave",
- "dragover",
- "dragstart",
- "drop",
- "mousedown",
- "mouseenter",
- "mouseleave",
- "mousemove",
- "mouseout",
- "mouseover",
- "mouseup",
- ].map((eventType) => ({
- eventType,
- case: "mouse",
- output: {
- altKey: "altKey",
- button: "button",
- buttons: "buttons",
- clientX: "clientX",
- clientY: "clientY",
- ctrlKey: "ctrlKey",
- metaKey: "metaKey",
- pageX: "pageX",
- pageY: "pageY",
- screenX: "screenX",
- screenY: "screenY",
- shiftKey: "shiftKey",
- },
- })),
- ...[
- "pointerdown",
- "pointermove",
- "pointerup",
- "pointercancel",
- "gotpointercapture",
- "lostpointercapture",
- "pointerenter",
- "pointerleave",
- "pointerover",
- "pointerout",
- ].map((eventType) => ({
- eventType,
- case: "pointer",
- output: {
- pointerId: "pointerId",
- width: "width",
- height: "height",
- pressure: "pressure",
- tiltX: "tiltX",
- tiltY: "tiltY",
- pointerType: "pointerType",
- altKey: "altKey",
- button: "button",
- buttons: "buttons",
- clientX: "clientX",
- clientY: "clientY",
- ctrlKey: "ctrlKey",
- metaKey: "metaKey",
- pageX: "pageX",
- pageY: "pageY",
- screenX: "screenX",
- screenY: "screenY",
- shiftKey: "shiftKey",
- isPrimary: "isPrimary",
- },
- })),
- ...["touchcancel", "touchend", "touchmove", "touchstart"].map(
- (eventType) => ({
- eventType,
- case: "touch",
- output: {
- altKey: "altKey",
- ctrlKey: "ctrlKey",
- metaKey: "metaKey",
- shiftKey: "shiftKey",
- },
- })
- ),
- {
- eventType: "scroll",
- case: "ui",
- output: {
- detail: "detail",
- },
- },
- {
- eventType: "wheel",
- case: "wheel",
- output: {
- deltaMode: "deltaMode",
- deltaX: "deltaX",
- deltaY: "deltaY",
- deltaZ: "deltaZ",
- },
- },
- ...["animationstart", "animationend", "animationiteration"].map(
- (eventType) => ({
- eventType,
- case: "animation",
- output: {
- animationName: "animationName",
- pseudoElement: "pseudoElement",
- elapsedTime: "elapsedTime",
- },
- })
- ),
- {
- eventType: "transitionend",
- case: "transition",
- output: {
- propertyName: "propertyName",
- pseudoElement: "pseudoElement",
- elapsedTime: "elapsedTime",
- },
- },
-].forEach((expectation) => {
- test(`serializeEvent() adds ${expectation.case} attributes`, () => {
- assertEqualSerializedEventData(
- { ...allEventData, type: expectation.eventType },
- expectation.output
- );
- });
-});
-
-const mockElementsToSelect = `
-
-`;
-
-test("serializeEvent() adds text of current selection", () => {
- document.body.innerHTML = mockElementsToSelect;
- const start = document.getElementById("start");
- const end = document.getElementById("end");
- window.getSelection().setBaseAndExtent(start, 0, end, 0);
- assertEqualSerializedEventData(
- { ...allEventData, type: "select" },
- {
- selectedText: "START\nMIDDLE\n",
- }
- );
-});
-
-test.run();
diff --git a/src/client/packages/idom-client-react/tests/tooling/dom.js b/src/client/packages/idom-client-react/tests/tooling/dom.js
deleted file mode 100644
index e97eac844..000000000
--- a/src/client/packages/idom-client-react/tests/tooling/dom.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import * as React from "react";
-import * as ReactTestUtils from "react-dom/test-utils.js";
-
-export function render(Tag, props = {}) {
- const container = window.document.querySelector("main");
- const component = React.h(Tag, props);
- React.render(component, container);
- return { container, component };
-}
-
-export async function fire(elem, event, details) {
- await ReactTestUtils.act(() => {
- let evt = new window.Event(event, details);
- elem.dispatchEvent(evt);
- });
-}
diff --git a/src/client/snowpack.config.js b/src/client/snowpack.config.js
deleted file mode 100644
index 750b50a33..000000000
--- a/src/client/snowpack.config.js
+++ /dev/null
@@ -1,5 +0,0 @@
-module.exports = {
- workspaceRoot: false,
- testOptions: { files: ["**/tests/**/*", "**/*.test.*"] },
- buildOptions: { out: "../idom/client" },
-};
diff --git a/src/idom/__init__.py b/src/idom/__init__.py
deleted file mode 100644
index 111057781..000000000
--- a/src/idom/__init__.py
+++ /dev/null
@@ -1,53 +0,0 @@
-from . import config, html, log, web
-from .core import hooks
-from .core.component import Component, component
-from .core.dispatcher import Stop
-from .core.events import EventHandler, event
-from .core.hooks import (
- use_callback,
- use_effect,
- use_memo,
- use_reducer,
- use_ref,
- use_state,
-)
-from .core.layout import Layout
-from .core.proto import ComponentType, VdomDict
-from .core.vdom import vdom
-from .sample import run_sample_app
-from .server.prefab import run
-from .utils import Ref, html_to_vdom
-from .widgets import hotswap, multiview
-
-
-__author__ = "idom-team"
-__version__ = "0.36.0" # DO NOT MODIFY
-
-__all__ = [
- "component",
- "Component",
- "ComponentType",
- "config",
- "event",
- "EventHandler",
- "hooks",
- "hotswap",
- "html_to_vdom",
- "html",
- "Layout",
- "log",
- "multiview",
- "Ref",
- "run_sample_app",
- "run",
- "Stop",
- "use_callback",
- "use_effect",
- "use_memo",
- "use_reducer",
- "use_ref",
- "use_state",
- "vdom",
- "VdomDict",
- "web",
-]
diff --git a/src/idom/_option.py b/src/idom/_option.py
deleted file mode 100644
index 8bb7712b8..000000000
--- a/src/idom/_option.py
+++ /dev/null
@@ -1,91 +0,0 @@
-from __future__ import annotations
-
-import os
-from logging import getLogger
-from typing import Any, Callable, Generic, TypeVar, cast
-
-
-_O = TypeVar("_O")
-logger = getLogger(__name__)
-
-
-class Option(Generic[_O]):
- """An option that can be set using an environment variable of the same name"""
-
- def __init__(
- self,
- name: str,
- default: _O,
- mutable: bool = True,
- validator: Callable[[Any], _O] = lambda x: cast(_O, x),
- ) -> None:
- self._name = name
- self._default = default
- self._mutable = mutable
- self._validator = validator
- if name in os.environ:
- self._current = validator(os.environ[name])
- logger.debug(f"{self._name}={self.current}")
-
- @property
- def name(self) -> str:
- """The name of this option (used to load environment variables)"""
- return self._name
-
- @property
- def mutable(self) -> bool:
- """Whether this option can be modified after being loaded"""
- return self._mutable
-
- @property
- def default(self) -> _O:
- """This option's default value"""
- return self._default
-
- @property
- def current(self) -> _O:
- try:
- return self._current
- except AttributeError:
- return self._default
-
- @current.setter
- def current(self, new: _O) -> None:
- self.set_current(new)
- return None
-
- def is_set(self) -> bool:
- """Whether this option has a value other than its default."""
- return hasattr(self, "_current")
-
- def set_current(self, new: Any) -> None:
- """Set the value of this option
-
- Raises a ``TypeError`` if this option is not :attr:`Option.mutable`.
- """
- if not self._mutable:
- raise TypeError(f"{self} cannot be modified after initial load")
- self._current = self._validator(new)
- logger.debug(f"{self._name}={self._current}")
-
- def set_default(self, new: _O) -> _O:
- """Set the value of this option if not :meth:`Option.is_set`
-
- Returns the current value (a la :meth:`dict.set_default`)
- """
- if not self.is_set():
- self.set_current(new)
- return self._current
-
- def reload(self) -> None:
- """Reload this option from its environment variable"""
- self.set_current(os.environ.get(self._name, self._default))
-
- def unset(self) -> None:
- """Remove the current value, the default will be used until it is set again."""
- if not self._mutable:
- raise TypeError(f"{self} cannot be modified after initial load")
- delattr(self, "_current")
-
- def __repr__(self) -> str:
- return f"Option({self._name}={self.current!r})"
diff --git a/src/idom/config.py b/src/idom/config.py
deleted file mode 100644
index c4d73ff96..000000000
--- a/src/idom/config.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""
-IDOM provides a series of configuration options that can be set using environment
-variables or, for those which allow it, a programatic interface.
-"""
-
-from pathlib import Path
-from tempfile import TemporaryDirectory
-
-from ._option import Option as _Option
-
-
-IDOM_DEBUG_MODE = _Option(
- "IDOM_DEBUG_MODE",
- default=False,
- mutable=False,
- validator=lambda x: bool(int(x)),
-)
-"""This immutable option turns on/off debug mode
-
-The string values ``1`` and ``0`` are mapped to ``True`` and ``False`` respectively.
-
-When debug is on, extra validation measures are applied that negatively impact
-performance but can be used to catch bugs during development. Additionally, the default
-log level for IDOM is set to ``DEBUG``.
-"""
-
-IDOM_CHECK_VDOM_SPEC = _Option(
- "IDOM_CHECK_VDOM_SPEC",
- default=IDOM_DEBUG_MODE.current,
- mutable=False,
- validator=lambda x: bool(int(x)),
-)
-"""This immutable option turns on/off checks which ensure VDOM is rendered to spec
-
-The string values ``1`` and ``0`` are mapped to ``True`` and ``False`` respectively.
-
-By default this check is off. When ``IDOM_DEBUG_MODE=1`` this will be turned on but can
-be manually disablled by setting ``IDOM_CHECK_VDOM_SPEC=0`` in addition.
-
-For more info on the VDOM spec, see here: :ref:`VDOM JSON Schema`
-"""
-
-# Because these web modules will be linked dynamically at runtime this can be temporary
-_DEFAULT_WEB_MODULES_DIR = TemporaryDirectory()
-
-IDOM_WED_MODULES_DIR = _Option(
- "IDOM_WED_MODULES_DIR",
- default=Path(_DEFAULT_WEB_MODULES_DIR.name),
- validator=Path,
-)
-"""The location IDOM will use to store its client application
-
-This directory **MUST** be treated as a black box. Downstream applications **MUST NOT**
-assume anything about the structure of this directory see :mod:`idom.web.module` for a
-set of publically available APIs for working with the client.
-"""
-
-IDOM_FEATURE_INDEX_AS_DEFAULT_KEY = _Option(
- "IDOM_FEATURE_INDEX_AS_DEFAULT_KEY",
- default=True,
- mutable=False,
- validator=lambda x: bool(int(x)),
-)
-"""Use the index of elements/components amongst their siblings as the default key.
-
-The flag's default value is set to true. To return to legacy behavior set
-``IDOM_FEATURE_INDEX_AS_DEFAULT_KEY=0``. In a future release, this flag will be removed
-entirely and the indices will always be the default key.
-
-For more information on changes to this feature flag see:
-https://github.com/idom-team/idom/issues/351
-"""
diff --git a/src/idom/core/_event_proxy.py b/src/idom/core/_event_proxy.py
deleted file mode 100644
index 5c47aa57f..000000000
--- a/src/idom/core/_event_proxy.py
+++ /dev/null
@@ -1,38 +0,0 @@
-from typing import Any, Dict, Sequence
-from warnings import warn
-
-
-def _wrap_in_warning_event_proxies(values: Sequence[Any]) -> Sequence[Any]:
- return [_EventProxy(x) if isinstance(x, dict) else x for x in values]
-
-
-class _EventProxy(Dict[Any, Any]):
- def __getitem__(self, key: Any) -> Any: # pragma: no cover
- try:
- return super().__getitem__(key)
- except KeyError:
- target = self.get("target")
- if isinstance(target, dict) and key in target:
- warn(
- f"The event key event[{key!r}] has been moved event['target'][{key!r}",
- DeprecationWarning,
- stacklevel=2,
- )
- return target[key]
- else:
- raise
-
- def get(self, key: Any, default: Any = None) -> Any: # pragma: no cover
- try:
- return super().__getitem__(key)
- except KeyError:
- target = self.get("target")
- if isinstance(target, dict) and key in target:
- warn(
- f"The event key event[{key!r}] has been moved event['target'][{key!r}",
- DeprecationWarning,
- stacklevel=2,
- )
- return target[key]
- else:
- return default
diff --git a/src/idom/core/_fixed_jsonpatch.py b/src/idom/core/_fixed_jsonpatch.py
deleted file mode 100644
index 702bc4dc6..000000000
--- a/src/idom/core/_fixed_jsonpatch.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# type: ignore
-
-"""A patched version of jsonpatch
-
-We need this because of: https://github.com/stefankoegl/python-json-patch/issues/138
-
-The core of this patch is in `DiffBuilder._item_removed`. The rest is just boilerplate
-that's been copied over with little to no changes.
-"""
-
-from jsonpatch import _ST_REMOVE
-from jsonpatch import DiffBuilder as _DiffBuilder
-from jsonpatch import JsonPatch as _JsonPatch
-from jsonpatch import RemoveOperation, _path_join, basestring
-from jsonpointer import JsonPointer
-
-
-def apply_patch(doc, patch, in_place=False, pointer_cls=JsonPointer):
- if isinstance(patch, basestring):
- patch = JsonPatch.from_string(patch, pointer_cls=pointer_cls)
- else:
- patch = JsonPatch(patch, pointer_cls=pointer_cls)
- return patch.apply(doc, in_place)
-
-
-def make_patch(src, dst, pointer_cls=JsonPointer):
- return JsonPatch.from_diff(src, dst, pointer_cls=pointer_cls)
-
-
-class JsonPatch(_JsonPatch):
- @classmethod
- def from_diff(
- cls,
- src,
- dst,
- optimization=True,
- dumps=None,
- pointer_cls=JsonPointer,
- ):
- json_dumper = dumps or cls.json_dumper
- builder = DiffBuilder(src, dst, json_dumper, pointer_cls=pointer_cls)
- builder._compare_values("", None, src, dst)
- ops = list(builder.execute())
- return cls(ops, pointer_cls=pointer_cls)
-
-
-class DiffBuilder(_DiffBuilder):
- def _item_removed(self, path, key, item):
- new_op = RemoveOperation(
- {
- "op": "remove",
- "path": _path_join(path, key),
- }
- )
- new_index = self.insert(new_op)
- self.store_index(item, new_index, _ST_REMOVE)
diff --git a/src/idom/core/component.py b/src/idom/core/component.py
deleted file mode 100644
index 9c154592c..000000000
--- a/src/idom/core/component.py
+++ /dev/null
@@ -1,71 +0,0 @@
-from __future__ import annotations
-
-import inspect
-from functools import wraps
-from typing import Any, Callable, Dict, Optional, Tuple, Union
-
-from .proto import ComponentType, VdomDict
-
-
-def component(
- function: Callable[..., Union[ComponentType, VdomDict]]
-) -> Callable[..., "Component"]:
- """A decorator for defining an :class:`Component`.
-
- Parameters:
- function: The function that will render a :class:`VdomDict`.
- """
- sig = inspect.signature(function)
- key_is_kwarg = "key" in sig.parameters and sig.parameters["key"].kind in (
- inspect.Parameter.KEYWORD_ONLY,
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
- )
- if key_is_kwarg:
- raise TypeError(
- f"Component render function {function} uses reserved parameter 'key'"
- )
-
- @wraps(function)
- def constructor(*args: Any, key: Optional[Any] = None, **kwargs: Any) -> Component:
- if key_is_kwarg:
- kwargs["key"] = key
- return Component(function, key, args, kwargs)
-
- return constructor
-
-
-class Component:
- """An object for rending component models."""
-
- __slots__ = "__weakref__", "_func", "_args", "_kwargs", "key"
-
- def __init__(
- self,
- function: Callable[..., Union[ComponentType, VdomDict]],
- key: Optional[Any],
- args: Tuple[Any, ...],
- kwargs: Dict[str, Any],
- ) -> None:
- self._args = args
- self._func = function
- self._kwargs = kwargs
- self.key = key
-
- def render(self) -> VdomDict:
- model = self._func(*self._args, **self._kwargs)
- if isinstance(model, ComponentType):
- model = {"tagName": "div", "children": [model]}
- return model
-
- def __repr__(self) -> str:
- sig = inspect.signature(self._func)
- try:
- args = sig.bind(*self._args, **self._kwargs).arguments
- except TypeError:
- return f"{self._func.__name__}(...)"
- else:
- items = ", ".join(f"{k}={v!r}" for k, v in args.items())
- if items:
- return f"{self._func.__name__}({id(self):02x}, {items})"
- else:
- return f"{self._func.__name__}({id(self):02x})"
diff --git a/src/idom/core/dispatcher.py b/src/idom/core/dispatcher.py
deleted file mode 100644
index 2768879ad..000000000
--- a/src/idom/core/dispatcher.py
+++ /dev/null
@@ -1,235 +0,0 @@
-from __future__ import annotations
-
-from asyncio import Future, Queue, ensure_future
-from asyncio.tasks import FIRST_COMPLETED, ensure_future, gather, wait
-from contextlib import asynccontextmanager
-from logging import getLogger
-from typing import (
- Any,
- AsyncIterator,
- Awaitable,
- Callable,
- Dict,
- List,
- NamedTuple,
- Sequence,
- Tuple,
- cast,
-)
-from weakref import WeakSet
-
-from anyio import create_task_group
-
-from idom.utils import Ref
-
-from ._fixed_jsonpatch import apply_patch, make_patch # type: ignore
-from .layout import LayoutEvent, LayoutUpdate
-from .proto import LayoutType, VdomJson
-
-
-logger = getLogger(__name__)
-
-
-SendCoroutine = Callable[["VdomJsonPatch"], Awaitable[None]]
-"""Send model patches given by a dispatcher"""
-
-RecvCoroutine = Callable[[], Awaitable[LayoutEvent]]
-"""Called by a dispatcher to return a :class:`idom.core.layout.LayoutEvent`
-
-The event will then trigger an :class:`idom.core.proto.EventHandlerType` in a layout.
-"""
-
-
-class Stop(BaseException):
- """Stop dispatching changes and events
-
- Raising this error will tell dispatchers to gracefully exit. Typically this is
- called by code running inside a layout to tell it to stop rendering.
- """
-
-
-async def dispatch_single_view(
- layout: LayoutType[LayoutUpdate, LayoutEvent],
- send: SendCoroutine,
- recv: RecvCoroutine,
-) -> None:
- """Run a dispatch loop for a single view instance"""
- with layout:
- try:
- async with create_task_group() as task_group:
- task_group.start_soon(_single_outgoing_loop, layout, send)
- task_group.start_soon(_single_incoming_loop, layout, recv)
- except Stop:
- logger.info("Stopped dispatch task")
-
-
-SharedViewDispatcher = Callable[[SendCoroutine, RecvCoroutine], Awaitable[None]]
-_SharedViewDispatcherFuture = Callable[[SendCoroutine, RecvCoroutine], "Future[None]"]
-
-
-@asynccontextmanager
-async def create_shared_view_dispatcher(
- layout: LayoutType[LayoutUpdate, LayoutEvent],
-) -> AsyncIterator[_SharedViewDispatcherFuture]:
- """Enter a dispatch context where all subsequent view instances share the same state"""
- with layout:
- (
- dispatch_shared_view,
- send_patch,
- ) = await _create_shared_view_dispatcher(layout)
-
- dispatch_tasks: List[Future[None]] = []
-
- def dispatch_shared_view_soon(
- send: SendCoroutine, recv: RecvCoroutine
- ) -> Future[None]:
- future = ensure_future(dispatch_shared_view(send, recv))
- dispatch_tasks.append(future)
- return future
-
- yield dispatch_shared_view_soon
-
- gathered_dispatch_tasks = gather(*dispatch_tasks, return_exceptions=True)
-
- while True:
- (
- update_future,
- dispatchers_completed_future,
- ) = await _wait_until_first_complete(
- layout.render(),
- gathered_dispatch_tasks,
- )
-
- if dispatchers_completed_future.done():
- update_future.cancel()
- break
- else:
- patch = VdomJsonPatch.create_from(update_future.result())
-
- send_patch(patch)
-
-
-def ensure_shared_view_dispatcher_future(
- layout: LayoutType[LayoutUpdate, LayoutEvent],
-) -> Tuple[Future[None], SharedViewDispatcher]:
- """Ensure the future of a dispatcher made by :func:`create_shared_view_dispatcher`
-
- This returns a future that can be awaited to block until all dispatch tasks have
- completed as well as the dispatcher coroutine itself which is used to start dispatch
- tasks.
-
- This is required in situations where usage of the async context manager from
- :func:`create_shared_view_dispatcher` is not possible. Typically this happens when
- integrating IDOM with other frameworks, servers, or applications.
- """
- dispatcher_future: Future[SharedViewDispatcher] = Future()
-
- async def dispatch_shared_view_forever() -> None:
- with layout:
- (
- dispatch_shared_view,
- send_patch,
- ) = await _create_shared_view_dispatcher(layout)
-
- dispatcher_future.set_result(dispatch_shared_view)
-
- while True:
- send_patch(await render_json_patch(layout))
-
- async def dispatch(send: SendCoroutine, recv: RecvCoroutine) -> None:
- await (await dispatcher_future)(send, recv)
-
- return ensure_future(dispatch_shared_view_forever()), dispatch
-
-
-async def render_json_patch(layout: LayoutType[LayoutUpdate, Any]) -> VdomJsonPatch:
- """Render a class:`VdomJsonPatch` from a layout"""
- return VdomJsonPatch.create_from(await layout.render())
-
-
-class VdomJsonPatch(NamedTuple):
- """An object describing an update to a :class:`Layout` in the form of a JSON patch"""
-
- path: str
- """The path where changes should be applied"""
-
- changes: List[Dict[str, Any]]
- """A list of JSON patches to apply at the given path"""
-
- def apply_to(self, model: VdomJson) -> VdomJson:
- """Return the model resulting from the changes in this update"""
- return cast(
- VdomJson,
- apply_patch(
- model, [{**c, "path": self.path + c["path"]} for c in self.changes]
- ),
- )
-
- @classmethod
- def create_from(cls, update: LayoutUpdate) -> VdomJsonPatch:
- """Return a patch given an layout update"""
- return cls(update.path, make_patch(update.old or {}, update.new).patch)
-
-
-async def _create_shared_view_dispatcher(
- layout: LayoutType[LayoutUpdate, LayoutEvent],
-) -> Tuple[SharedViewDispatcher, Callable[[VdomJsonPatch], None]]:
- update = await layout.render()
- model_state = Ref(update.new)
-
- # We push updates to queues instead of pushing directly to send() callbacks in
- # order to isolate send_patch() from any errors send() callbacks might raise.
- all_patch_queues: WeakSet[Queue[VdomJsonPatch]] = WeakSet()
-
- async def dispatch_shared_view(send: SendCoroutine, recv: RecvCoroutine) -> None:
- patch_queue: Queue[VdomJsonPatch] = Queue()
- try:
- async with create_task_group() as inner_task_group:
- all_patch_queues.add(patch_queue)
- effective_update = LayoutUpdate("", None, model_state.current)
- await send(VdomJsonPatch.create_from(effective_update))
- inner_task_group.start_soon(_single_incoming_loop, layout, recv)
- inner_task_group.start_soon(_shared_outgoing_loop, send, patch_queue)
- except Stop:
- logger.info("Stopped dispatch task")
- finally:
- all_patch_queues.remove(patch_queue)
- return None
-
- def send_patch(patch: VdomJsonPatch) -> None:
- model_state.current = patch.apply_to(model_state.current)
- for queue in all_patch_queues:
- queue.put_nowait(patch)
-
- return dispatch_shared_view, send_patch
-
-
-async def _single_outgoing_loop(
- layout: LayoutType[LayoutUpdate, LayoutEvent], send: SendCoroutine
-) -> None:
- while True:
- await send(await render_json_patch(layout))
-
-
-async def _single_incoming_loop(
- layout: LayoutType[LayoutUpdate, LayoutEvent], recv: RecvCoroutine
-) -> None:
- while True:
- # We need to fire and forget here so that we avoid waiting on the completion
- # of this event handler before receiving and running the next one.
- ensure_future(layout.deliver(await recv()))
-
-
-async def _shared_outgoing_loop(
- send: SendCoroutine, queue: Queue[VdomJsonPatch]
-) -> None:
- while True:
- await send(await queue.get())
-
-
-async def _wait_until_first_complete(
- *tasks: Awaitable[Any],
-) -> Sequence[Future[Any]]:
- futures = [ensure_future(t) for t in tasks]
- await wait(futures, return_when=FIRST_COMPLETED)
- return futures
diff --git a/src/idom/core/hooks.py b/src/idom/core/hooks.py
deleted file mode 100644
index 0aeb2a0d5..000000000
--- a/src/idom/core/hooks.py
+++ /dev/null
@@ -1,598 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-from logging import getLogger
-from threading import get_ident as get_thread_id
-from types import FunctionType
-from typing import (
- TYPE_CHECKING,
- Any,
- Awaitable,
- Callable,
- Dict,
- Generic,
- List,
- NewType,
- Optional,
- Sequence,
- Tuple,
- TypeVar,
- Union,
- cast,
- overload,
-)
-
-from typing_extensions import Protocol
-
-from idom.utils import Ref
-
-
-if not TYPE_CHECKING:
- # make flake8 think that this variable exists
- ellipsis = type(...)
-
-
-__all__ = [
- "use_state",
- "use_effect",
- "use_reducer",
- "use_callback",
- "use_ref",
- "use_memo",
-]
-
-logger = getLogger(__name__)
-
-_StateType = TypeVar("_StateType")
-
-
-@overload
-def use_state(
- initial_value: Callable[[], _StateType],
-) -> Tuple[
- _StateType,
- Callable[[_StateType | Callable[[_StateType], _StateType]], None],
-]:
- ...
-
-
-@overload
-def use_state(
- initial_value: _StateType,
-) -> Tuple[
- _StateType,
- Callable[[_StateType | Callable[[_StateType], _StateType]], None],
-]:
- ...
-
-
-def use_state(
- initial_value: _StateType | Callable[[], _StateType],
-) -> Tuple[
- _StateType,
- Callable[[_StateType | Callable[[_StateType], _StateType]], None],
-]:
- """See the full :ref:`Use State` docs for details
-
- Parameters:
- initial_value:
- Defines the initial value of the state. A callable (accepting no arguments)
- can be used as a constructor function to avoid re-creating the initial value
- on each render.
-
- Returns:
- A tuple containing the current state and a function to update it.
- """
- current_state = _use_const(lambda: _CurrentState(initial_value))
- return current_state.value, current_state.dispatch
-
-
-class _CurrentState(Generic[_StateType]):
-
- __slots__ = "value", "dispatch"
-
- def __init__(
- self,
- initial_value: Union[_StateType, Callable[[], _StateType]],
- ) -> None:
- if callable(initial_value):
- self.value = initial_value()
- else:
- self.value = initial_value
-
- hook = current_hook()
-
- def dispatch(
- new: Union[_StateType, Callable[[_StateType], _StateType]]
- ) -> None:
- if callable(new):
- next_value = new(self.value)
- else:
- next_value = new
- if next_value is not self.value:
- self.value = next_value
- hook.schedule_render()
-
- self.dispatch = dispatch
-
-
-_EffectCleanFunc = Callable[[], None]
-_SyncEffectFunc = Callable[[], Optional[_EffectCleanFunc]]
-_AsyncEffectFunc = Callable[[], Awaitable[Optional[_EffectCleanFunc]]]
-_EffectApplyFunc = Union[_SyncEffectFunc, _AsyncEffectFunc]
-
-
-@overload
-def use_effect(
- function: None = None,
- dependencies: Sequence[Any] | ellipsis | None = ...,
-) -> Callable[[_EffectApplyFunc], None]:
- ...
-
-
-@overload
-def use_effect(
- function: _EffectApplyFunc,
- dependencies: Sequence[Any] | ellipsis | None = ...,
-) -> None:
- ...
-
-
-def use_effect(
- function: Optional[_EffectApplyFunc] = None,
- dependencies: Sequence[Any] | ellipsis | None = ...,
-) -> Optional[Callable[[_EffectApplyFunc], None]]:
- """See the full :ref:`Use Effect` docs for details
-
- Parameters:
- function:
- Applies the effect and can return a clean-up function
- dependencies:
- Dependencies for the effect. The effect will only trigger if the identity
- of any value in the given sequence changes (i.e. their :func:`id` is
- different). By default these are inferred based on local variables that are
- referenced by the given function.
-
- Returns:
- If not function is provided, a decorator. Otherwise ``None``.
- """
- hook = current_hook()
-
- dependencies = _try_to_infer_closure_values(function, dependencies)
- memoize = use_memo(dependencies=dependencies)
- last_clean_callback: Ref[Optional[_EffectCleanFunc]] = use_ref(None)
-
- def add_effect(function: _EffectApplyFunc) -> None:
-
- if not asyncio.iscoroutinefunction(function):
- sync_function = cast(_SyncEffectFunc, function)
- else:
-
- async_function = cast(_AsyncEffectFunc, function)
-
- def sync_function() -> Optional[_EffectCleanFunc]:
- future = asyncio.ensure_future(async_function())
-
- def clean_future() -> None:
- if not future.cancel():
- clean = future.result()
- if clean is not None:
- clean()
-
- return clean_future
-
- def effect() -> None:
- if last_clean_callback.current is not None:
- last_clean_callback.current()
-
- clean = last_clean_callback.current = sync_function()
- if clean is not None:
- hook.add_effect(WILL_UNMOUNT_EFFECT, clean)
-
- return None
-
- return memoize(lambda: hook.add_effect(DID_RENDER_EFFECT, effect))
-
- if function is not None:
- add_effect(function)
- return None
- else:
- return add_effect
-
-
-_ActionType = TypeVar("_ActionType")
-
-
-def use_reducer(
- reducer: Callable[[_StateType, _ActionType], _StateType],
- initial_value: _StateType,
-) -> Tuple[_StateType, Callable[[_ActionType], None]]:
- """See the full :ref:`Use Reducer` docs for details
-
- Parameters:
- reducer:
- A function which applies an action to the current state in order to
- produce the next state.
- initial_value:
- The initial state value (same as for :func:`use_state`)
-
- Returns:
- A tuple containing the current state and a function to change it with an action
- """
- state, set_state = use_state(initial_value)
- return state, _use_const(lambda: _create_dispatcher(reducer, set_state))
-
-
-def _create_dispatcher(
- reducer: Callable[[_StateType, _ActionType], _StateType],
- set_state: Callable[[Callable[[_StateType], _StateType]], None],
-) -> Callable[[_ActionType], None]:
- def dispatch(action: _ActionType) -> None:
- set_state(lambda last_state: reducer(last_state, action))
-
- return dispatch
-
-
-_CallbackFunc = TypeVar("_CallbackFunc", bound=Callable[..., Any])
-
-
-@overload
-def use_callback(
- function: None = None,
- dependencies: Sequence[Any] | ellipsis | None = ...,
-) -> Callable[[_CallbackFunc], _CallbackFunc]:
- ...
-
-
-@overload
-def use_callback(
- function: _CallbackFunc,
- dependencies: Sequence[Any] | ellipsis | None = ...,
-) -> _CallbackFunc:
- ...
-
-
-def use_callback(
- function: Optional[_CallbackFunc] = None,
- dependencies: Sequence[Any] | ellipsis | None = ...,
-) -> Union[_CallbackFunc, Callable[[_CallbackFunc], _CallbackFunc]]:
- """See the full :ref:`Use Callback` docs for details
-
- Parameters:
- function:
- The function whose identity will be preserved
- dependencies:
- Dependencies of the callback. The identity the ``function`` will be udpated
- if the identity of any value in the given sequence changes (i.e. their
- :func:`id` is different). By default these are inferred based on local
- variables that are referenced by the given function.
-
- Returns:
- The current function
- """
- dependencies = _try_to_infer_closure_values(function, dependencies)
- memoize = use_memo(dependencies=dependencies)
-
- def setup(function: _CallbackFunc) -> _CallbackFunc:
- return memoize(lambda: function)
-
- if function is not None:
- return setup(function)
- else:
- return setup
-
-
-class _LambdaCaller(Protocol):
- """MyPy doesn't know how to deal with TypeVars only used in function return"""
-
- def __call__(self, func: Callable[[], _StateType]) -> _StateType:
- ...
-
-
-@overload
-def use_memo(
- function: None = None,
- dependencies: Sequence[Any] | ellipsis | None = ...,
-) -> _LambdaCaller:
- ...
-
-
-@overload
-def use_memo(
- function: Callable[[], _StateType],
- dependencies: Sequence[Any] | ellipsis | None = ...,
-) -> _StateType:
- ...
-
-
-def use_memo(
- function: Optional[Callable[[], _StateType]] = None,
- dependencies: Sequence[Any] | ellipsis | None = ...,
-) -> Union[_StateType, Callable[[Callable[[], _StateType]], _StateType]]:
- """See the full :ref:`Use Memo` docs for details
-
- Parameters:
- function:
- The function to be memoized.
- dependencies:
- Dependencies for the memoized function. The memo will only be recomputed if
- the identity of any value in the given sequence changes (i.e. their
- :func:`id` is different). By default these are inferred based on local
- variables that are referenced by the given function.
-
- Returns:
- The current state
- """
- dependencies = _try_to_infer_closure_values(function, dependencies)
-
- memo: _Memo[_StateType] = _use_const(_Memo)
-
- if memo.empty():
- # we need to initialize on the first run
- changed = True
- memo.deps = () if dependencies is None else dependencies
- elif dependencies is None:
- changed = True
- memo.deps = ()
- elif (
- len(memo.deps) != len(dependencies)
- # if deps are same length check identity for each item
- or any(current is not new for current, new in zip(memo.deps, dependencies))
- ):
- memo.deps = dependencies
- changed = True
- else:
- changed = False
-
- setup: Callable[[Callable[[], _StateType]], _StateType]
-
- if changed:
-
- def setup(function: Callable[[], _StateType]) -> _StateType:
- current_value = memo.value = function()
- return current_value
-
- else:
-
- def setup(function: Callable[[], _StateType]) -> _StateType:
- return memo.value
-
- if function is not None:
- return setup(function)
- else:
- return setup
-
-
-class _Memo(Generic[_StateType]):
- """Simple object for storing memoization data"""
-
- __slots__ = "value", "deps"
-
- value: _StateType
- deps: Sequence[Any]
-
- def empty(self) -> bool:
- try:
- self.value
- except AttributeError:
- return True
- else:
- return False
-
-
-def use_ref(initial_value: _StateType) -> Ref[_StateType]:
- """See the full :ref:`Use State` docs for details
-
- Parameters:
- initial_value: The value initially assigned to the reference.
-
- Returns:
- A :class:`Ref` object.
- """
- return _use_const(lambda: Ref(initial_value))
-
-
-def _use_const(function: Callable[[], _StateType]) -> _StateType:
- return current_hook().use_state(function)
-
-
-def _try_to_infer_closure_values(
- func: Callable[..., Any] | None,
- values: Sequence[Any] | ellipsis | None,
-) -> Sequence[Any] | None:
- if values is ...:
- if isinstance(func, FunctionType):
- return (
- [cell.cell_contents for cell in func.__closure__]
- if func.__closure__
- else []
- )
- else:
- return None
- else:
- return cast("Sequence[Any] | None", values)
-
-
-_current_life_cycle_hook: Dict[int, "LifeCycleHook"] = {}
-
-
-def current_hook() -> "LifeCycleHook":
- """Get the current :class:`LifeCycleHook`"""
- try:
- return _current_life_cycle_hook[get_thread_id()]
- except KeyError as error:
- msg = "No life cycle hook is active. Are you rendering in a layout?"
- raise RuntimeError(msg) from error
-
-
-EffectType = NewType("EffectType", str)
-"""Used in :meth:`LifeCycleHook.add_effect` to indicate what effect should be saved"""
-
-DID_RENDER_EFFECT = EffectType("DID_RENDER")
-"""An effect that will be triggered after each render"""
-
-WILL_UNMOUNT_EFFECT = EffectType("WILL_UNMOUNT")
-"""An effect that will be triggered just before the component is unmounted"""
-
-
-class LifeCycleHook:
- """Defines the life cycle of a layout component.
-
- Components can request access to their own life cycle events and state through hooks
- while :class:`~idom.core.proto.LayoutType` objects drive drive the life cycle
- forward by triggering events and rendering view changes.
-
- Example:
-
- If removed from the complexities of a layout, a very simplified full life cycle
- for a single component with no child components would look a bit like this:
-
- .. testcode::
-
- from idom.core.hooks import LifeCycleHook, DID_RENDER_EFFECT
-
-
- # this function will come from a layout implementation
- schedule_render = lambda: ...
-
- # --- start life cycle ---
-
- hook = hooks.LifeCycle(schedule_render)
-
- # --- start render cycle ---
-
- hook.component_will_render()
-
- hook.set_current()
-
- try:
- # render the component
- ...
-
- # the component may access the current hook
- assert hooks.current_hook() is hook
-
- # and save state or add effects
- current_hook().use_state(lambda: ...)
- current_hook().use_effect(DID_RENDER_EFFECT, lambda: ...)
- finally:
- hook.unset_current()
-
- # This should only be called after any child components yielded by
- # component_instance.render() have also been rendered because effects
- # must run after the full set of changes have been resolved.
- hook.component_did_render()
-
- # Typically an event occurs and a new render is scheduled, thus begining
- # the render cycle anew.
- hook.schedule_render()
-
-
- # --- end render cycle ---
-
- hook.component_will_unmount()
- del hook
-
- # --- end render cycle ---
- """
-
- __slots__ = (
- "_schedule_render_callback",
- "_schedule_render_later",
- "_current_state_index",
- "_state",
- "_rendered_atleast_once",
- "_is_rendering",
- "_event_effects",
- "__weakref__",
- )
-
- def __init__(
- self,
- schedule_render: Callable[[], None],
- ) -> None:
- self._schedule_render_callback = schedule_render
- self._schedule_render_later = False
- self._is_rendering = False
- self._rendered_atleast_once = False
- self._current_state_index = 0
- self._state: Tuple[Any, ...] = ()
- self._event_effects: Dict[EffectType, List[Callable[[], None]]] = {
- DID_RENDER_EFFECT: [],
- WILL_UNMOUNT_EFFECT: [],
- }
-
- def schedule_render(self) -> None:
- if self._is_rendering:
- self._schedule_render_later = True
- else:
- self._schedule_render()
- return None
-
- def use_state(self, function: Callable[[], _StateType]) -> _StateType:
- if not self._rendered_atleast_once:
- # since we're not intialized yet we're just appending state
- result = function()
- self._state += (result,)
- else:
- # once finalized we iterate over each succesively used piece of state
- result = self._state[self._current_state_index]
- self._current_state_index += 1
- return result
-
- def add_effect(self, effect_type: EffectType, function: Callable[[], None]) -> None:
- """Trigger a function on the occurance of the given effect type"""
- self._event_effects[effect_type].append(function)
-
- def component_will_render(self) -> None:
- """The component is about to render"""
- self._is_rendering = True
- self._event_effects[WILL_UNMOUNT_EFFECT].clear()
-
- def component_did_render(self) -> None:
- """The component completed a render"""
- did_render_effects = self._event_effects[DID_RENDER_EFFECT]
- for effect in did_render_effects:
- try:
- effect()
- except Exception:
- logger.exception(f"Post-render effect {effect} failed")
- did_render_effects.clear()
-
- self._is_rendering = False
- if self._schedule_render_later:
- self._schedule_render()
- self._rendered_atleast_once = True
- self._current_state_index = 0
-
- def component_will_unmount(self) -> None:
- """The component is about to be removed from the layout"""
- will_unmount_effects = self._event_effects[WILL_UNMOUNT_EFFECT]
- for effect in will_unmount_effects:
- try:
- effect()
- except Exception:
- logger.exception(f"Pre-unmount effect {effect} failed")
- will_unmount_effects.clear()
-
- def set_current(self) -> None:
- """Set this hook as the active hook in this thread
-
- This method is called by a layout before entering the render method
- of this hook's associated component.
- """
- _current_life_cycle_hook[get_thread_id()] = self
-
- def unset_current(self) -> None:
- """Unset this hook as the active hook in this thread"""
- # this assertion should never fail - primarilly useful for debug
- assert _current_life_cycle_hook[get_thread_id()] is self
- del _current_life_cycle_hook[get_thread_id()]
-
- def _schedule_render(self) -> None:
- try:
- self._schedule_render_callback()
- except Exception:
- logger.exception(
- f"Failed to schedule render via {self._schedule_render_callback}"
- )
diff --git a/src/idom/core/proto.py b/src/idom/core/proto.py
deleted file mode 100644
index e12120533..000000000
--- a/src/idom/core/proto.py
+++ /dev/null
@@ -1,170 +0,0 @@
-from __future__ import annotations
-
-from types import TracebackType
-from typing import (
- Any,
- Callable,
- Dict,
- Iterable,
- List,
- Mapping,
- Optional,
- Sequence,
- Type,
- TypeVar,
- Union,
-)
-
-from typing_extensions import Protocol, TypedDict, runtime_checkable
-
-
-ComponentConstructor = Callable[..., "ComponentType"]
-"""Simple function returning a new component"""
-
-
-Key = Union[str, int]
-
-
-@runtime_checkable
-class ComponentType(Protocol):
- """The expected interface for all component-like objects"""
-
- key: Key | None
- """An identifier which is unique amongst a component's immediate siblings"""
-
- def render(self) -> VdomDict:
- """Render the component's :class:`VdomDict`."""
-
-
-_Self = TypeVar("_Self")
-_Render = TypeVar("_Render", covariant=True)
-_Event = TypeVar("_Event", contravariant=True)
-
-
-@runtime_checkable
-class LayoutType(Protocol[_Render, _Event]):
- """Renders and delivers, updates to views and events to handlers, respectively"""
-
- async def render(self) -> _Render:
- """Render an update to a view"""
-
- async def deliver(self, event: _Event) -> None:
- """Relay an event to its respective handler"""
-
- def __enter__(self: _Self) -> _Self:
- """Prepare the layout for its first render"""
-
- def __exit__(
- self, exc_type: Type[Exception], exc_value: Exception, traceback: TracebackType
- ) -> Optional[bool]:
- """Clean up the view after its final render"""
-
-
-VdomAttributes = Mapping[str, Any]
-"""Describes the attributes of a :class:`VdomDict`"""
-
-VdomChild = Union[ComponentType, "VdomDict", str]
-"""A single child element of a :class:`VdomDict`"""
-
-VdomChildren = Sequence[VdomChild]
-"""Describes a series of :class:`VdomChild` elements"""
-
-VdomAttributesAndChildren = Union[
- Mapping[str, Any], # this describes both VdomDict and VdomAttributes
- Iterable[VdomChild],
-]
-"""Useful for the ``*attributes_and_children`` parameter in :func:`idom.core.vdom.vdom`"""
-
-
-class _VdomDictOptional(TypedDict, total=False):
- key: Key | None
- children: Sequence[
- # recursive types are not allowed yet:
- # https://github.com/python/mypy/issues/731
- Union[ComponentType, Dict[str, Any], str]
- ]
- attributes: VdomAttributes
- eventHandlers: EventHandlerDict # noqa
- importSource: ImportSourceDict # noqa
-
-
-class _VdomDictRequired(TypedDict, total=True):
- tagName: str # noqa
-
-
-class VdomDict(_VdomDictRequired, _VdomDictOptional):
- """A :ref:`VDOM` dictionary"""
-
-
-class ImportSourceDict(TypedDict):
- source: str
- fallback: Any
- sourceType: str # noqa
- unmountBeforeUpdate: bool # noqa
-
-
-class _OptionalVdomJson(TypedDict, total=False):
- key: Key
- error: str
- children: List[Any]
- attributes: Dict[str, Any]
- eventHandlers: Dict[str, _JsonEventTarget] # noqa
- importSource: _JsonImportSource # noqa
-
-
-class _RequiredVdomJson(TypedDict, total=True):
- tagName: str # noqa
-
-
-class VdomJson(_RequiredVdomJson, _OptionalVdomJson):
- """A JSON serializable form of :class:`VdomDict` matching the :data:`VDOM_JSON_SCHEMA`"""
-
-
-class _JsonEventTarget(TypedDict):
- target: str
- preventDefault: bool # noqa
- stopPropagation: bool # noqa
-
-
-class _JsonImportSource(TypedDict):
- source: str
- fallback: Any
-
-
-EventHandlerMapping = Mapping[str, "EventHandlerType"]
-"""A generic mapping between event names to their handlers"""
-
-EventHandlerDict = Dict[str, "EventHandlerType"]
-"""A dict mapping between event names to their handlers"""
-
-
-class EventHandlerFunc(Protocol):
- """A coroutine which can handle event data"""
-
- async def __call__(self, data: Sequence[Any]) -> None:
- ...
-
-
-@runtime_checkable
-class EventHandlerType(Protocol):
- """Defines a handler for some event"""
-
- prevent_default: bool
- """Whether to block the event from propagating further up the DOM"""
-
- stop_propagation: bool
- """Stops the default action associate with the event from taking place."""
-
- function: EventHandlerFunc
- """A coroutine which can respond to an event and its data"""
-
- target: Optional[str]
- """Typically left as ``None`` except when a static target is useful.
-
- When testing, it may be useful to specify a static target ID so events can be
- triggered programatically.
-
- .. note::
-
- When ``None``, it is left to a :class:`LayoutType` to auto generate a unique ID.
- """
diff --git a/src/idom/log.py b/src/idom/log.py
deleted file mode 100644
index 856ee9ab2..000000000
--- a/src/idom/log.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import logging
-import sys
-from logging.config import dictConfig
-
-from .config import IDOM_DEBUG_MODE
-
-
-dictConfig(
- {
- "version": 1,
- "disable_existing_loggers": False,
- "loggers": {
- "idom": {
- "level": "DEBUG" if IDOM_DEBUG_MODE.current else "INFO",
- "handlers": ["console"],
- },
- },
- "handlers": {
- "console": {
- "class": "logging.StreamHandler",
- "formatter": "generic",
- "stream": sys.stdout,
- }
- },
- "formatters": {
- "generic": {
- "format": "%(asctime)s | %(levelname)s | %(message)s",
- "datefmt": r"%Y-%m-%dT%H:%M:%S%z",
- "class": "logging.Formatter",
- }
- },
- }
-)
-
-
-ROOT_LOGGER = logging.getLogger("idom")
-"""IDOM's root logger instance"""
-
-
-if IDOM_DEBUG_MODE.current:
- ROOT_LOGGER.debug("IDOM is in debug mode")
diff --git a/src/idom/sample.py b/src/idom/sample.py
deleted file mode 100644
index 63ffdd243..000000000
--- a/src/idom/sample.py
+++ /dev/null
@@ -1,60 +0,0 @@
-from __future__ import annotations
-
-import webbrowser
-from typing import Any
-
-from idom.server.proto import ServerType
-
-from . import html
-from .core.component import component
-from .core.proto import VdomDict
-from .server.utils import find_available_port, find_builtin_server_type
-
-
-@component
-def App() -> VdomDict:
- return html.div(
- {"style": {"padding": "15px"}},
- html.h1("Sample Application"),
- html.p(
- "This is a basic application made with IDOM. Click ",
- html.a(
- {"href": "https://pypi.org/project/idom/", "target": "_blank"},
- "here",
- ),
- " to learn more.",
- ),
- )
-
-
-def run_sample_app(
- host: str = "127.0.0.1",
- port: int | None = None,
- open_browser: bool = False,
- run_in_thread: bool | None = None,
-) -> ServerType[Any]:
- """Run a sample application.
-
- Args:
- host: host where the server should run
- port: the port on the host to serve from
- open_browser: whether to open a browser window after starting the server
- """
- port = port or find_available_port(host)
- server_type = find_builtin_server_type("PerClientStateServer")
- server = server_type(App)
-
- run_in_thread = open_browser or run_in_thread
-
- if not run_in_thread: # pragma: no cover
- server.run(host=host, port=port)
- return server
-
- thread = server.run_in_thread(host=host, port=port)
- server.wait_until_started(5)
-
- if open_browser: # pragma: no cover
- webbrowser.open(f"http://{host}:{port}")
- thread.join()
-
- return server
diff --git a/src/idom/server/__init__.py b/src/idom/server/__init__.py
deleted file mode 100644
index 0dfd40ace..000000000
--- a/src/idom/server/__init__.py
+++ /dev/null
@@ -1,8 +0,0 @@
-from .prefab import hotswap_server, multiview_server, run
-
-
-__all__ = [
- "hotswap_server",
- "multiview_server",
- "run",
-]
diff --git a/src/idom/server/fastapi.py b/src/idom/server/fastapi.py
deleted file mode 100644
index f36078a48..000000000
--- a/src/idom/server/fastapi.py
+++ /dev/null
@@ -1,54 +0,0 @@
-from typing import Optional
-
-from fastapi import FastAPI
-
-from idom.core.proto import ComponentConstructor
-
-from .starlette import (
- Config,
- StarletteServer,
- _setup_common_routes,
- _setup_config_and_app,
- _setup_shared_view_dispatcher_route,
- _setup_single_view_dispatcher_route,
-)
-
-
-def PerClientStateServer(
- constructor: ComponentConstructor,
- config: Optional[Config] = None,
- app: Optional[FastAPI] = None,
-) -> StarletteServer:
- """Return a :class:`StarletteServer` where each client has its own state.
-
- Implements the :class:`~idom.server.proto.ServerFactory` protocol
-
- Parameters:
- constructor: A component constructor
- config: Options for configuring server behavior
- app: An application instance (otherwise a default instance is created)
- """
- config, app = _setup_config_and_app(config, app, FastAPI)
- _setup_common_routes(config, app)
- _setup_single_view_dispatcher_route(config["url_prefix"], app, constructor)
- return StarletteServer(app)
-
-
-def SharedClientStateServer(
- constructor: ComponentConstructor,
- config: Optional[Config] = None,
- app: Optional[FastAPI] = None,
-) -> StarletteServer:
- """Return a :class:`StarletteServer` where each client shares state.
-
- Implements the :class:`~idom.server.proto.ServerFactory` protocol
-
- Parameters:
- constructor: A component constructor
- config: Options for configuring server behavior
- app: An application instance (otherwise a default instance is created)
- """
- config, app = _setup_config_and_app(config, app, FastAPI)
- _setup_common_routes(config, app)
- _setup_shared_view_dispatcher_route(config["url_prefix"], app, constructor)
- return StarletteServer(app)
diff --git a/src/idom/server/flask.py b/src/idom/server/flask.py
deleted file mode 100644
index 895735c2a..000000000
--- a/src/idom/server/flask.py
+++ /dev/null
@@ -1,295 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-import json
-import logging
-from asyncio import Queue as AsyncQueue
-from queue import Queue as ThreadQueue
-from threading import Event as ThreadEvent
-from threading import Thread
-from typing import Any, Callable, Dict, NamedTuple, Optional, Tuple, Union, cast
-from urllib.parse import parse_qs as parse_query_string
-
-from flask import Blueprint, Flask, redirect, request, send_from_directory, url_for
-from flask_cors import CORS
-from flask_sockets import Sockets
-from gevent import pywsgi
-from geventwebsocket.handler import WebSocketHandler
-from geventwebsocket.websocket import WebSocket
-from typing_extensions import TypedDict
-
-import idom
-from idom.config import IDOM_DEBUG_MODE, IDOM_WED_MODULES_DIR
-from idom.core.dispatcher import dispatch_single_view
-from idom.core.layout import LayoutEvent, LayoutUpdate
-from idom.core.proto import ComponentConstructor, ComponentType
-
-from .utils import CLIENT_BUILD_DIR, threaded, wait_on_event
-
-
-logger = logging.getLogger(__name__)
-
-
-class Config(TypedDict, total=False):
- """Render server config for :class:`FlaskRenderServer`"""
-
- cors: Union[bool, Dict[str, Any]]
- """Enable or configure Cross Origin Resource Sharing (CORS)
-
- For more information see docs for ``flask_cors.CORS``
- """
-
- import_name: str
- """The module where the application instance was created
-
- For more info see :class:`flask.Flask`.
- """
-
- redirect_root_to_index: bool
- """Whether to redirect the root URL (with prefix) to ``index.html``"""
-
- serve_static_files: bool
- """Whether or not to serve static files (i.e. web modules)"""
-
- url_prefix: str
- """The URL prefix where IDOM resources will be served from"""
-
-
-def PerClientStateServer(
- constructor: ComponentConstructor,
- config: Optional[Config] = None,
- app: Optional[Flask] = None,
-) -> FlaskServer:
- """Return a :class:`FlaskServer` where each client has its own state.
-
- Implements the :class:`~idom.server.proto.ServerFactory` protocol
-
- Parameters:
- constructor: A component constructor
- config: Options for configuring server behavior
- app: An application instance (otherwise a default instance is created)
- """
- config, app = _setup_config_and_app(config, app)
- blueprint = Blueprint("idom", __name__, url_prefix=config["url_prefix"])
- _setup_common_routes(blueprint, config)
- _setup_single_view_dispatcher_route(app, config, constructor)
- app.register_blueprint(blueprint)
- return FlaskServer(app)
-
-
-class FlaskServer:
- """A thin wrapper for running a Flask application
-
- See :class:`idom.server.proto.Server` for more info
- """
-
- _wsgi_server: pywsgi.WSGIServer
-
- def __init__(self, app: Flask) -> None:
- self.app = app
- self._did_start = ThreadEvent()
-
- @app.before_first_request
- def server_did_start() -> None:
- self._did_start.set()
-
- def run(self, host: str, port: int, *args: Any, **kwargs: Any) -> None:
- if IDOM_DEBUG_MODE.current:
- logging.basicConfig(level=logging.DEBUG) # pragma: no cover
- logger.info(f"Running at http://{host}:{port}")
- self._wsgi_server = _StartCallbackWSGIServer(
- self._did_start.set,
- (host, port),
- self.app,
- *args,
- handler_class=WebSocketHandler,
- **kwargs,
- )
- self._wsgi_server.serve_forever()
-
- run_in_thread = threaded(run)
-
- def wait_until_started(self, timeout: Optional[float] = 3.0) -> None:
- wait_on_event(f"start {self.app}", self._did_start, timeout)
-
- def stop(self, timeout: Optional[float] = 3.0) -> None:
- try:
- server = self._wsgi_server
- except AttributeError: # pragma: no cover
- raise RuntimeError(
- f"Application is not running or was not started by {self}"
- )
- else:
- server.stop(timeout)
-
-
-def _setup_config_and_app(
- config: Optional[Config], app: Optional[Flask]
-) -> Tuple[Config, Flask]:
- return (
- {
- "url_prefix": "",
- "cors": False,
- "serve_static_files": True,
- "redirect_root_to_index": True,
- **(config or {}), # type: ignore
- },
- app or Flask(__name__),
- )
-
-
-def _setup_common_routes(blueprint: Blueprint, config: Config) -> None:
- cors_config = config["cors"]
- if cors_config: # pragma: no cover
- cors_params = cors_config if isinstance(cors_config, dict) else {}
- CORS(blueprint, **cors_params)
-
- if config["serve_static_files"]:
-
- @blueprint.route("/client/")
- def send_client_dir(path: str) -> Any:
- return send_from_directory(str(CLIENT_BUILD_DIR), path)
-
- @blueprint.route("/modules/")
- def send_modules_dir(path: str) -> Any:
- return send_from_directory(str(IDOM_WED_MODULES_DIR.current), path)
-
- if config["redirect_root_to_index"]:
-
- @blueprint.route("/")
- def redirect_to_index() -> Any:
- return redirect(
- url_for(
- "idom.send_client_dir",
- path="index.html",
- **request.args,
- )
- )
-
-
-def _setup_single_view_dispatcher_route(
- app: Flask, config: Config, constructor: ComponentConstructor
-) -> None:
- sockets = Sockets(app)
-
- @sockets.route(_join_url_paths(config["url_prefix"], "/stream")) # type: ignore
- def model_stream(ws: WebSocket) -> None:
- def send(value: Any) -> None:
- ws.send(json.dumps(value))
-
- def recv() -> Optional[LayoutEvent]:
- event = ws.receive()
- if event is not None:
- return LayoutEvent(**json.loads(event))
- else:
- return None
-
- dispatch_single_view_in_thread(constructor(**_get_query_params(ws)), send, recv)
-
-
-def _get_query_params(ws: WebSocket) -> Dict[str, Any]:
- return {
- k: v if len(v) > 1 else v[0]
- for k, v in parse_query_string(ws.environ["QUERY_STRING"]).items()
- }
-
-
-def dispatch_single_view_in_thread(
- component: ComponentType,
- send: Callable[[Any], None],
- recv: Callable[[], Optional[LayoutEvent]],
-) -> None:
- dispatch_thread_info_created = ThreadEvent()
- dispatch_thread_info_ref: idom.Ref[Optional[_DispatcherThreadInfo]] = idom.Ref(None)
-
- def run_dispatcher() -> None:
- loop = asyncio.new_event_loop()
- asyncio.set_event_loop(loop)
-
- thread_send_queue: "ThreadQueue[LayoutUpdate]" = ThreadQueue()
- async_recv_queue: "AsyncQueue[LayoutEvent]" = AsyncQueue()
-
- async def send_coro(value: Any) -> None:
- thread_send_queue.put(value)
-
- async def recv_coro() -> Any:
- return await async_recv_queue.get()
-
- async def main() -> None:
- await dispatch_single_view(idom.Layout(component), send_coro, recv_coro)
-
- main_future = asyncio.ensure_future(main())
-
- dispatch_thread_info_ref.current = _DispatcherThreadInfo(
- dispatch_loop=loop,
- dispatch_future=main_future,
- thread_send_queue=thread_send_queue,
- async_recv_queue=async_recv_queue,
- )
- dispatch_thread_info_created.set()
-
- loop.run_until_complete(main_future)
-
- Thread(target=run_dispatcher, daemon=True).start()
-
- dispatch_thread_info_created.wait()
- dispatch_thread_info = cast(_DispatcherThreadInfo, dispatch_thread_info_ref.current)
- assert dispatch_thread_info is not None
-
- stop = ThreadEvent()
-
- def run_send() -> None:
- while not stop.is_set():
- send(dispatch_thread_info.thread_send_queue.get())
-
- Thread(target=run_send, daemon=True).start()
-
- try:
- while True:
- value = recv()
- if value is None:
- stop.set()
- break
- # BUG: https://github.com/nedbat/coveragepy/issues/1012
- # Coverage isn't able to support concurrency coverage for both threading and gevent
- dispatch_thread_info.dispatch_loop.call_soon_threadsafe( # pragma: no cover
- dispatch_thread_info.async_recv_queue.put_nowait, value
- )
- finally:
- dispatch_thread_info.dispatch_loop.call_soon_threadsafe(
- dispatch_thread_info.dispatch_future.cancel
- )
-
- return None
-
-
-class _DispatcherThreadInfo(NamedTuple):
- dispatch_loop: asyncio.AbstractEventLoop
- dispatch_future: "asyncio.Future[Any]"
- thread_send_queue: "ThreadQueue[LayoutUpdate]"
- async_recv_queue: "AsyncQueue[LayoutEvent]"
-
-
-class _StartCallbackWSGIServer(pywsgi.WSGIServer): # type: ignore
- def __init__(
- self, before_first_request: Callable[[], None], *args: Any, **kwargs: Any
- ) -> None:
- self._before_first_request_callback = before_first_request
- super().__init__(*args, **kwargs)
-
- def update_environ(self) -> None:
- """
- Called before the first request is handled to fill in WSGI environment values.
-
- This includes getting the correct server name and port.
- """
- super().update_environ()
- # BUG: https://github.com/nedbat/coveragepy/issues/1012
- # Coverage isn't able to support concurrency coverage for both threading and gevent
- self._before_first_request_callback() # pragma: no cover
-
-
-def _join_url_paths(*args: str) -> str:
- # urllib.parse.urljoin performs more logic than is needed. Thus we need a util func
- # to join paths as if they were POSIX paths.
- return "/".join(map(lambda x: str(x).rstrip("/"), filter(None, args)))
diff --git a/src/idom/server/prefab.py b/src/idom/server/prefab.py
deleted file mode 100644
index 8d7c73325..000000000
--- a/src/idom/server/prefab.py
+++ /dev/null
@@ -1,151 +0,0 @@
-import logging
-from typing import Any, Dict, Optional, Tuple, TypeVar
-
-from idom.core.proto import ComponentConstructor
-from idom.widgets import MountFunc, MultiViewMount, hotswap, multiview
-
-from .proto import ServerFactory, ServerType
-from .utils import find_available_port, find_builtin_server_type
-
-
-logger = logging.getLogger(__name__)
-
-_App = TypeVar("_App")
-_Config = TypeVar("_Config")
-
-
-def run(
- component: ComponentConstructor,
- server_type: Optional[ServerFactory[_App, _Config]] = None,
- host: str = "127.0.0.1",
- port: Optional[int] = None,
- server_config: Optional[Any] = None,
- run_kwargs: Optional[Dict[str, Any]] = None,
- app: Optional[Any] = None,
- daemon: bool = False,
-) -> ServerType[_App]:
- """A utility for quickly running a render server with minimal boilerplate
-
- Parameters:
- component:
- The root of the view.
- server_type:
- What server to run. Defaults to a builtin implementation if available.
- host:
- The host string.
- port:
- The port number. Defaults to a dynamically discovered available port.
- server_config:
- Options passed to configure the server.
- run_kwargs:
- Keyword arguments passed to the :meth:`~idom.server.proto.Server.run`
- or :meth:`~idom.server.proto.Server.run_in_thread` methods of the server
- depending on whether ``daemon`` is set or not.
- app:
- Register the server to an existing application and run that.
- daemon:
- Whether the server should be run in a daemon thread.
-
- Returns:
- The server instance. This isn't really useful unless the server is spawned
- as a daemon. Otherwise this function blocks until the server has stopped.
- """
- if server_type is None:
- server_type = find_builtin_server_type("PerClientStateServer")
- if port is None: # pragma: no cover
- port = find_available_port(host)
-
- server = server_type(component, server_config, app)
- logger.info(f"Using {type(server).__name__}")
-
- run_server = server.run if not daemon else server.run_in_thread
- run_server(host, port, **(run_kwargs or {}))
- server.wait_until_started()
-
- return server
-
-
-def multiview_server(
- server_type: Optional[ServerFactory[_App, _Config]] = None,
- host: str = "127.0.0.1",
- port: Optional[int] = None,
- server_config: Optional[_Config] = None,
- run_kwargs: Optional[Dict[str, Any]] = None,
- app: Optional[Any] = None,
-) -> Tuple[MultiViewMount, ServerType[_App]]:
- """Set up a server where views can be dynamically added.
-
- In other words this allows the user to work with IDOM in an imperative manner. Under
- the hood this uses the :func:`idom.widgets.multiview` function to add the views on
- the fly.
-
- Parameters:
- server: The server type to start up as a daemon
- host: The server hostname
- port: The server port number
- server_config: Value passed to :meth:`~idom.server.proto.ServerFactory`
- run_kwargs: Keyword args passed to :meth:`~idom.server.proto.Server.run_in_thread`
- app: Optionally provide a prexisting application to register to
-
- Returns:
- The server instance and a function for adding views. See
- :func:`idom.widgets.multiview` for details.
- """
- mount, component = multiview()
-
- server = run(
- component,
- server_type,
- host,
- port,
- server_config=server_config,
- run_kwargs=run_kwargs,
- daemon=True,
- app=app,
- )
-
- return mount, server
-
-
-def hotswap_server(
- server_type: Optional[ServerFactory[_App, _Config]] = None,
- host: str = "127.0.0.1",
- port: Optional[int] = None,
- server_config: Optional[_Config] = None,
- run_kwargs: Optional[Dict[str, Any]] = None,
- app: Optional[Any] = None,
- sync_views: bool = False,
-) -> Tuple[MountFunc, ServerType[_App]]:
- """Set up a server where views can be dynamically swapped out.
-
- In other words this allows the user to work with IDOM in an imperative manner. Under
- the hood this uses the :func:`idom.widgets.hotswap` function to swap the views on
- the fly.
-
- Parameters:
- server: The server type to start up as a daemon
- host: The server hostname
- port: The server port number
- server_config: Value passed to :meth:`~idom.server.proto.ServerFactory`
- run_kwargs: Keyword args passed to :meth:`~idom.server.proto.Server.run_in_thread`
- app: Optionally provide a prexisting application to register to
- sync_views: Whether to update all displays with newly mounted components
-
- Returns:
- The server instance and a function for swapping views. See
- :func:`idom.widgets.hotswap` for details.
- """
- mount, component = hotswap(update_on_change=sync_views)
-
- server = run(
- component,
- server_type,
- host,
- port,
- server_config=server_config,
- run_kwargs=run_kwargs,
- daemon=True,
- app=app,
- )
-
- return mount, server
diff --git a/src/idom/server/proto.py b/src/idom/server/proto.py
deleted file mode 100644
index d0db29eee..000000000
--- a/src/idom/server/proto.py
+++ /dev/null
@@ -1,43 +0,0 @@
-from __future__ import annotations
-
-from threading import Thread
-from typing import Optional, TypeVar
-
-from typing_extensions import Protocol
-
-from idom.core.proto import ComponentConstructor
-
-
-_App = TypeVar("_App")
-_Config = TypeVar("_Config", contravariant=True)
-
-
-class ServerFactory(Protocol[_App, _Config]):
- """Setup a :class:`Server`"""
-
- def __call__(
- self,
- constructor: ComponentConstructor,
- config: Optional[_Config] = None,
- app: Optional[_App] = None,
- ) -> ServerType[_App]:
- ...
-
-
-class ServerType(Protocol[_App]):
- """A thin wrapper around a web server that provides a common operational interface"""
-
- app: _App
- """The server's underlying application"""
-
- def run(self, host: str, port: int) -> None:
- """Start running the server"""
-
- def run_in_thread(self, host: str, port: int) -> Thread:
- """Run the server in a thread"""
-
- def wait_until_started(self, timeout: Optional[float] = None) -> None:
- """Block until the server is able to receive requests"""
-
- def stop(self, timeout: Optional[float] = None) -> None:
- """Stop the running server"""
diff --git a/src/idom/server/sanic.py b/src/idom/server/sanic.py
deleted file mode 100644
index aba81e63e..000000000
--- a/src/idom/server/sanic.py
+++ /dev/null
@@ -1,256 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-import json
-import logging
-from asyncio import Future
-from asyncio.events import AbstractEventLoop
-from threading import Event
-from typing import Any, Dict, Optional, Tuple, Union
-
-from mypy_extensions import TypedDict
-from sanic import Blueprint, Sanic, request, response
-from sanic_cors import CORS
-from websockets import WebSocketCommonProtocol
-
-from idom.config import IDOM_WED_MODULES_DIR
-from idom.core.dispatcher import (
- RecvCoroutine,
- SendCoroutine,
- SharedViewDispatcher,
- VdomJsonPatch,
- dispatch_single_view,
- ensure_shared_view_dispatcher_future,
-)
-from idom.core.layout import Layout, LayoutEvent
-from idom.core.proto import ComponentConstructor
-
-from .utils import CLIENT_BUILD_DIR, threaded, wait_on_event
-
-
-logger = logging.getLogger(__name__)
-
-_SERVER_COUNT = 0
-
-
-class Config(TypedDict, total=False):
- """Config for :class:`SanicRenderServer`"""
-
- cors: Union[bool, Dict[str, Any]]
- """Enable or configure Cross Origin Resource Sharing (CORS)
-
- For more information see docs for ``sanic_cors.CORS``
- """
-
- redirect_root_to_index: bool
- """Whether to redirect the root URL (with prefix) to ``index.html``"""
-
- serve_static_files: bool
- """Whether or not to serve static files (i.e. web modules)"""
-
- url_prefix: str
- """The URL prefix where IDOM resources will be served from"""
-
-
-def PerClientStateServer(
- constructor: ComponentConstructor,
- config: Optional[Config] = None,
- app: Optional[Sanic] = None,
-) -> SanicServer:
- """Return a :class:`SanicServer` where each client has its own state.
-
- Implements the :class:`~idom.server.proto.ServerFactory` protocol
-
- Parameters:
- constructor: A component constructor
- config: Options for configuring server behavior
- app: An application instance (otherwise a default instance is created)
- """
- config, app = _setup_config_and_app(config, app)
- blueprint = Blueprint(f"idom_dispatcher_{id(app)}", url_prefix=config["url_prefix"])
- _setup_common_routes(blueprint, config)
- _setup_single_view_dispatcher_route(blueprint, constructor)
- app.blueprint(blueprint)
- return SanicServer(app)
-
-
-def SharedClientStateServer(
- constructor: ComponentConstructor,
- config: Optional[Config] = None,
- app: Optional[Sanic] = None,
-) -> SanicServer:
- """Return a :class:`SanicServer` where each client shares state.
-
- Implements the :class:`~idom.server.proto.ServerFactory` protocol
-
- Parameters:
- constructor: A component constructor
- config: Options for configuring server behavior
- app: An application instance (otherwise a default instance is created)
- """
- config, app = _setup_config_and_app(config, app)
- blueprint = Blueprint(f"idom_dispatcher_{id(app)}", url_prefix=config["url_prefix"])
- _setup_common_routes(blueprint, config)
- _setup_shared_view_dispatcher_route(app, blueprint, constructor)
- app.blueprint(blueprint)
- return SanicServer(app)
-
-
-class SanicServer:
- """A thin wrapper for running a Sanic application
-
- See :class:`idom.server.proto.Server` for more info
- """
-
- _loop: AbstractEventLoop
-
- def __init__(self, app: Sanic) -> None:
- self.app = app
- self._did_start = Event()
- self._did_stop = Event()
- app.register_listener(self._server_did_start, "after_server_start")
- app.register_listener(self._server_did_stop, "after_server_stop")
-
- def run(self, host: str, port: int, *args: Any, **kwargs: Any) -> None:
- self.app.run(host, port, *args, **kwargs) # pragma: no cover
-
- @threaded
- def run_in_thread(self, host: str, port: int, *args: Any, **kwargs: Any) -> None:
- loop = asyncio.get_event_loop()
-
- # what follows was copied from:
- # https://github.com/sanic-org/sanic/blob/7028eae083b0da72d09111b9892ddcc00bce7df4/examples/run_async_advanced.py
-
- serv_coro = self.app.create_server(
- host, port, *args, **kwargs, return_asyncio_server=True
- )
- serv_task = asyncio.ensure_future(serv_coro, loop=loop)
- server = loop.run_until_complete(serv_task)
- server.after_start()
- try:
- loop.run_forever()
- except KeyboardInterrupt: # pragma: no cover
- loop.stop()
- finally:
- server.before_stop()
-
- # Wait for server to close
- close_task = server.close()
- loop.run_until_complete(close_task)
-
- # Complete all tasks on the loop
- for connection in server.connections:
- connection.close_if_idle()
- server.after_stop()
-
- def wait_until_started(self, timeout: Optional[float] = 3.0) -> None:
- wait_on_event(f"start {self.app}", self._did_start, timeout)
-
- def stop(self, timeout: Optional[float] = 3.0) -> None:
- self._loop.call_soon_threadsafe(self.app.stop)
- wait_on_event(f"stop {self.app}", self._did_stop, timeout)
-
- async def _server_did_start(self, app: Sanic, loop: AbstractEventLoop) -> None:
- self._loop = loop
- self._did_start.set()
-
- async def _server_did_stop(self, app: Sanic, loop: AbstractEventLoop) -> None:
- self._did_stop.set()
-
-
-def _setup_config_and_app(
- config: Optional[Config],
- app: Optional[Sanic],
-) -> Tuple[Config, Sanic]:
- if app is None:
- global _SERVER_COUNT
- _SERVER_COUNT += 1
- app = Sanic(f"{__name__}[{_SERVER_COUNT}]")
- return (
- {
- "cors": False,
- "url_prefix": "",
- "serve_static_files": True,
- "redirect_root_to_index": True,
- **(config or {}), # type: ignore
- },
- app,
- )
-
-
-def _setup_common_routes(blueprint: Blueprint, config: Config) -> None:
- cors_config = config["cors"]
- if cors_config: # pragma: no cover
- cors_params = cors_config if isinstance(cors_config, dict) else {}
- CORS(blueprint, **cors_params)
-
- if config["serve_static_files"]:
- blueprint.static("/client", str(CLIENT_BUILD_DIR))
- blueprint.static("/modules", str(IDOM_WED_MODULES_DIR.current))
-
- if config["redirect_root_to_index"]:
-
- @blueprint.route("/") # type: ignore
- def redirect_to_index(
- request: request.Request,
- ) -> response.HTTPResponse:
- return response.redirect(
- f"{blueprint.url_prefix}/client/index.html?{request.query_string}"
- )
-
-
-def _setup_single_view_dispatcher_route(
- blueprint: Blueprint, constructor: ComponentConstructor
-) -> None:
- @blueprint.websocket("/stream") # type: ignore
- async def model_stream(
- request: request.Request, socket: WebSocketCommonProtocol
- ) -> None:
- send, recv = _make_send_recv_callbacks(socket)
- component_params = {k: request.args.get(k) for k in request.args}
- await dispatch_single_view(Layout(constructor(**component_params)), send, recv)
-
-
-def _setup_shared_view_dispatcher_route(
- app: Sanic, blueprint: Blueprint, constructor: ComponentConstructor
-) -> None:
- dispatcher_future: Future[None]
- dispatch_coroutine: SharedViewDispatcher
-
- async def activate_dispatcher(app: Sanic, loop: AbstractEventLoop) -> None:
- nonlocal dispatcher_future
- nonlocal dispatch_coroutine
- dispatcher_future, dispatch_coroutine = ensure_shared_view_dispatcher_future(
- Layout(constructor())
- )
-
- async def deactivate_dispatcher(app: Sanic, loop: AbstractEventLoop) -> None:
- logger.debug("Stopping dispatcher - server is shutting down")
- dispatcher_future.cancel()
- await asyncio.wait([dispatcher_future])
-
- app.register_listener(activate_dispatcher, "before_server_start")
- app.register_listener(deactivate_dispatcher, "before_server_stop")
-
- @blueprint.websocket("/stream") # type: ignore
- async def model_stream(
- request: request.Request, socket: WebSocketCommonProtocol
- ) -> None:
- if request.args:
- raise ValueError(
- "SharedClientState server does not support per-client view parameters"
- )
- send, recv = _make_send_recv_callbacks(socket)
- await dispatch_coroutine(send, recv)
-
-
-def _make_send_recv_callbacks(
- socket: WebSocketCommonProtocol,
-) -> Tuple[SendCoroutine, RecvCoroutine]:
- async def sock_send(value: VdomJsonPatch) -> None:
- await socket.send(json.dumps(value))
-
- async def sock_recv() -> LayoutEvent:
- return LayoutEvent(**json.loads(await socket.recv()))
-
- return sock_send, sock_recv
diff --git a/src/idom/server/starlette.py b/src/idom/server/starlette.py
deleted file mode 100644
index 5b751e140..000000000
--- a/src/idom/server/starlette.py
+++ /dev/null
@@ -1,291 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-import json
-import logging
-import sys
-from asyncio import Future
-from threading import Event, Thread, current_thread
-from typing import Any, Dict, Optional, Tuple, TypeVar, Union
-
-from mypy_extensions import TypedDict
-from starlette.applications import Starlette
-from starlette.middleware.cors import CORSMiddleware
-from starlette.requests import Request
-from starlette.responses import RedirectResponse
-from starlette.staticfiles import StaticFiles
-from starlette.websockets import WebSocket, WebSocketDisconnect
-from uvicorn.config import Config as UvicornConfig
-from uvicorn.server import Server as UvicornServer
-from uvicorn.supervisors.multiprocess import Multiprocess
-from uvicorn.supervisors.statreload import StatReload as ChangeReload
-
-from idom.config import IDOM_DEBUG_MODE, IDOM_WED_MODULES_DIR
-from idom.core.dispatcher import (
- RecvCoroutine,
- SendCoroutine,
- SharedViewDispatcher,
- VdomJsonPatch,
- dispatch_single_view,
- ensure_shared_view_dispatcher_future,
-)
-from idom.core.layout import Layout, LayoutEvent
-from idom.core.proto import ComponentConstructor
-
-from .utils import CLIENT_BUILD_DIR, poll, threaded
-
-
-logger = logging.getLogger(__name__)
-
-_StarletteType = TypeVar("_StarletteType", bound=Starlette)
-
-
-class Config(TypedDict, total=False):
- """Config for :class:`StarletteRenderServer`"""
-
- cors: Union[bool, Dict[str, Any]]
- """Enable or configure Cross Origin Resource Sharing (CORS)
-
- For more information see docs for ``starlette.middleware.cors.CORSMiddleware``
- """
-
- redirect_root_to_index: bool
- """Whether to redirect the root URL (with prefix) to ``index.html``"""
-
- serve_static_files: bool
- """Whether or not to serve static files (i.e. web modules)"""
-
- url_prefix: str
- """The URL prefix where IDOM resources will be served from"""
-
-
-def PerClientStateServer(
- constructor: ComponentConstructor,
- config: Optional[Config] = None,
- app: Optional[Starlette] = None,
-) -> StarletteServer:
- """Return a :class:`StarletteServer` where each client has its own state.
-
- Implements the :class:`~idom.server.proto.ServerFactory` protocol
-
- Parameters:
- constructor: A component constructor
- config: Options for configuring server behavior
- app: An application instance (otherwise a default instance is created)
- """
- config, app = _setup_config_and_app(config, app, Starlette)
- _setup_common_routes(config, app)
- _setup_single_view_dispatcher_route(config["url_prefix"], app, constructor)
- return StarletteServer(app)
-
-
-def SharedClientStateServer(
- constructor: ComponentConstructor,
- config: Optional[Config] = None,
- app: Optional[Starlette] = None,
-) -> StarletteServer:
- """Return a :class:`StarletteServer` where each client shares state.
-
- Implements the :class:`~idom.server.proto.ServerFactory` protocol
-
- Parameters:
- constructor: A component constructor
- config: Options for configuring server behavior
- app: An application instance (otherwise a default instance is created)
- """
- config, app = _setup_config_and_app(config, app, Starlette)
- _setup_common_routes(config, app)
- _setup_shared_view_dispatcher_route(config["url_prefix"], app, constructor)
- return StarletteServer(app)
-
-
-class StarletteServer:
- """A thin wrapper for running a Starlette application
-
- See :class:`idom.server.proto.Server` for more info
- """
-
- _server: UvicornServer
- _current_thread: Thread
-
- def __init__(self, app: Starlette) -> None:
- self.app = app
- self._did_stop = Event()
- app.on_event("shutdown")(self._server_did_stop)
-
- def run(self, host: str, port: int, *args: Any, **kwargs: Any) -> None:
- self._current_thread = current_thread()
-
- self._server = server = UvicornServer(
- UvicornConfig(
- self.app, host=host, port=port, loop="asyncio", *args, **kwargs
- )
- )
-
- # The following was copied from the uvicorn source with minimal modification. We
- # shouldn't need to do this, but unfortunately there's no easy way to gain access to
- # the server instance so you can stop it.
- # BUG: https://github.com/encode/uvicorn/issues/742
- config = server.config
-
- if (config.reload or config.workers > 1) and not isinstance(
- server.config.app, str
- ): # pragma: no cover
- logger = logging.getLogger("uvicorn.error")
- logger.warning(
- "You must pass the application as an import string to enable 'reload' or "
- "'workers'."
- )
- sys.exit(1)
-
- if config.should_reload: # pragma: no cover
- sock = config.bind_socket()
- supervisor = ChangeReload(config, target=server.run, sockets=[sock])
- supervisor.run()
- elif config.workers > 1: # pragma: no cover
- sock = config.bind_socket()
- supervisor = Multiprocess(config, target=server.run, sockets=[sock])
- supervisor.run()
- else:
- import asyncio
-
- asyncio.set_event_loop(asyncio.new_event_loop())
- server.run()
-
- run_in_thread = threaded(run)
-
- def wait_until_started(self, timeout: Optional[float] = 3.0) -> None:
- poll(
- f"start {self.app}",
- 0.01,
- timeout,
- lambda: hasattr(self, "_server") and self._server.started,
- )
-
- def stop(self, timeout: Optional[float] = 3.0) -> None:
- self._server.should_exit = True
- self._did_stop.wait(timeout)
-
- async def _server_did_stop(self) -> None:
- self._did_stop.set()
-
-
-def _setup_config_and_app(
- config: Optional[Config],
- app: Optional[_StarletteType],
- app_type: type[_StarletteType],
-) -> Tuple[Config, _StarletteType]:
- return (
- {
- "cors": False,
- "url_prefix": "",
- "serve_static_files": True,
- "redirect_root_to_index": True,
- **(config or {}), # type: ignore
- },
- app or app_type(debug=IDOM_DEBUG_MODE.current),
- )
-
-
-def _setup_common_routes(config: Config, app: Starlette) -> None:
- cors_config = config["cors"]
- if cors_config: # pragma: no cover
- cors_params = (
- cors_config if isinstance(cors_config, dict) else {"allow_origins": ["*"]}
- )
- app.add_middleware(CORSMiddleware, **cors_params)
-
- # This really should be added to the APIRouter, but there's a bug in Starlette
- # BUG: https://github.com/tiangolo/fastapi/issues/1469
- url_prefix = config["url_prefix"]
- if config["serve_static_files"]:
- app.mount(
- f"{url_prefix}/client",
- StaticFiles(
- directory=str(CLIENT_BUILD_DIR),
- html=True,
- check_dir=True,
- ),
- name="idom_client_files",
- )
- app.mount(
- f"{url_prefix}/modules",
- StaticFiles(
- directory=str(IDOM_WED_MODULES_DIR.current),
- html=True,
- check_dir=False,
- ),
- name="idom_web_module_files",
- )
-
- if config["redirect_root_to_index"]:
-
- @app.route(f"{url_prefix}/")
- def redirect_to_index(request: Request) -> RedirectResponse:
- return RedirectResponse(
- f"{url_prefix}/client/index.html?{request.query_params}"
- )
-
-
-def _setup_single_view_dispatcher_route(
- url_prefix: str, app: Starlette, constructor: ComponentConstructor
-) -> None:
- @app.websocket_route(f"{url_prefix}/stream")
- async def model_stream(socket: WebSocket) -> None:
- await socket.accept()
- send, recv = _make_send_recv_callbacks(socket)
- try:
- await dispatch_single_view(
- Layout(constructor(**dict(socket.query_params))), send, recv
- )
- except WebSocketDisconnect as error:
- logger.info(f"WebSocket disconnect: {error.code}")
-
-
-def _setup_shared_view_dispatcher_route(
- url_prefix: str, app: Starlette, constructor: ComponentConstructor
-) -> None:
- dispatcher_future: Future[None]
- dispatch_coroutine: SharedViewDispatcher
-
- @app.on_event("startup")
- async def activate_dispatcher() -> None:
- nonlocal dispatcher_future
- nonlocal dispatch_coroutine
- dispatcher_future, dispatch_coroutine = ensure_shared_view_dispatcher_future(
- Layout(constructor())
- )
-
- @app.on_event("shutdown")
- async def deactivate_dispatcher() -> None:
- logger.debug("Stopping dispatcher - server is shutting down")
- dispatcher_future.cancel()
- await asyncio.wait([dispatcher_future])
-
- @app.websocket_route(f"{url_prefix}/stream")
- async def model_stream(socket: WebSocket) -> None:
- await socket.accept()
-
- if socket.query_params:
- raise ValueError(
- "SharedClientState server does not support per-client view parameters"
- )
-
- send, recv = _make_send_recv_callbacks(socket)
-
- try:
- await dispatch_coroutine(send, recv)
- except WebSocketDisconnect as error:
- logger.info(f"WebSocket disconnect: {error.code}")
-
-
-def _make_send_recv_callbacks(
- socket: WebSocket,
-) -> Tuple[SendCoroutine, RecvCoroutine]:
- async def sock_send(value: VdomJsonPatch) -> None:
- await socket.send_text(json.dumps(value))
-
- async def sock_recv() -> LayoutEvent:
- return LayoutEvent(**json.loads(await socket.receive_text()))
-
- return sock_send, sock_recv
diff --git a/src/idom/server/tornado.py b/src/idom/server/tornado.py
deleted file mode 100644
index b8cb21f1c..000000000
--- a/src/idom/server/tornado.py
+++ /dev/null
@@ -1,201 +0,0 @@
-from __future__ import annotations
-
-import asyncio
-import json
-from asyncio import Queue as AsyncQueue
-from asyncio.futures import Future
-from threading import Event as ThreadEvent
-from typing import Any, List, Optional, Tuple, Type, Union
-from urllib.parse import urljoin
-
-from tornado.platform.asyncio import AsyncIOMainLoop
-from tornado.web import Application, RedirectHandler, RequestHandler, StaticFileHandler
-from tornado.websocket import WebSocketHandler
-from typing_extensions import TypedDict
-
-from idom.config import IDOM_WED_MODULES_DIR
-from idom.core.dispatcher import VdomJsonPatch, dispatch_single_view
-from idom.core.layout import Layout, LayoutEvent
-from idom.core.proto import ComponentConstructor
-
-from .utils import CLIENT_BUILD_DIR, threaded, wait_on_event
-
-
-_RouteHandlerSpecs = List[Tuple[str, Type[RequestHandler], Any]]
-
-
-class Config(TypedDict, total=False):
- """Render server config for :class:`TornadoRenderServer` subclasses"""
-
- redirect_root_to_index: bool
- """Whether to redirect the root URL (with prefix) to ``index.html``"""
-
- serve_static_files: bool
- """Whether or not to serve static files (i.e. web modules)"""
-
- url_prefix: str
- """The URL prefix where IDOM resources will be served from"""
-
-
-def PerClientStateServer(
- constructor: ComponentConstructor,
- config: Optional[Config] = None,
- app: Optional[Application] = None,
-) -> TornadoServer:
- """Return a :class:`TornadoServer` where each client has its own state.
-
- Implements the :class:`~idom.server.proto.ServerFactory` protocol
-
- Parameters:
- constructor: A component constructor
- config: Options for configuring server behavior
- app: An application instance (otherwise a default instance is created)
- """
- config, app = _setup_config_and_app(config, app)
- _add_handler(
- app,
- config,
- _setup_common_routes(config) + _setup_single_view_dispatcher_route(constructor),
- )
- return TornadoServer(app)
-
-
-class TornadoServer:
- """A thin wrapper for running a Tornado application
-
- See :class:`idom.server.proto.Server` for more info
- """
-
- _loop: asyncio.AbstractEventLoop
-
- def __init__(self, app: Application) -> None:
- self.app = app
- self._did_start = ThreadEvent()
-
- def run(self, host: str, port: int, *args: Any, **kwargs: Any) -> None:
- self._loop = asyncio.get_event_loop()
- AsyncIOMainLoop().install()
- self.app.listen(port, host, *args, **kwargs)
- self._did_start.set()
- asyncio.get_event_loop().run_forever()
-
- @threaded
- def run_in_thread(self, host: str, port: int, *args: Any, **kwargs: Any) -> None:
- self.run(host, port, *args, **kwargs)
-
- def wait_until_started(self, timeout: Optional[float] = 3.0) -> None:
- self._did_start.wait(timeout)
-
- def stop(self, timeout: Optional[float] = 3.0) -> None:
- try:
- loop = self._loop
- except AttributeError: # pragma: no cover
- raise RuntimeError(
- f"Application is not running or was not started by {self}"
- )
- else:
- did_stop = ThreadEvent()
-
- def stop() -> None:
- loop.stop()
- did_stop.set()
-
- loop.call_soon_threadsafe(stop)
-
- wait_on_event(f"stop {self.app}", did_stop, timeout)
-
-
-def _setup_config_and_app(
- config: Optional[Config], app: Optional[Application]
-) -> Tuple[Config, Application]:
- return (
- {
- "url_prefix": "",
- "serve_static_files": True,
- "redirect_root_to_index": True,
- **(config or {}), # type: ignore
- },
- app or Application(),
- )
-
-
-def _setup_common_routes(config: Config) -> _RouteHandlerSpecs:
- handlers: _RouteHandlerSpecs = []
- if config["serve_static_files"]:
- handlers.append(
- (
- r"/client/(.*)",
- StaticFileHandler,
- {"path": str(CLIENT_BUILD_DIR)},
- )
- )
- handlers.append(
- (
- r"/modules/(.*)",
- StaticFileHandler,
- {"path": str(IDOM_WED_MODULES_DIR.current)},
- )
- )
- if config["redirect_root_to_index"]:
- handlers.append(("/", RedirectHandler, {"url": "./client/index.html"}))
- return handlers
-
-
-def _add_handler(
- app: Application, config: Config, handlers: _RouteHandlerSpecs
-) -> None:
- prefixed_handlers: List[Any] = [
- (urljoin(config["url_prefix"], route_pattern),) + tuple(handler_info)
- for route_pattern, *handler_info in handlers
- ]
- app.add_handlers(r".*", prefixed_handlers)
-
-
-def _setup_single_view_dispatcher_route(
- constructor: ComponentConstructor,
-) -> _RouteHandlerSpecs:
- return [
- (
- "/stream",
- PerClientStateModelStreamHandler,
- {"component_constructor": constructor},
- )
- ]
-
-
-class PerClientStateModelStreamHandler(WebSocketHandler):
- """A web-socket handler that serves up a new model stream to each new client"""
-
- _dispatch_future: Future[None]
- _message_queue: AsyncQueue[str]
-
- def initialize(self, component_constructor: ComponentConstructor) -> None:
- self._component_constructor = component_constructor
-
- async def open(self, *args: str, **kwargs: str) -> None:
- message_queue: "AsyncQueue[str]" = AsyncQueue()
- query_params = {k: v[0].decode() for k, v in self.request.arguments.items()}
-
- async def send(value: VdomJsonPatch) -> None:
- await self.write_message(json.dumps(value))
-
- async def recv() -> LayoutEvent:
- return LayoutEvent(**json.loads(await message_queue.get()))
-
- self._message_queue = message_queue
- self._dispatch_future = asyncio.ensure_future(
- dispatch_single_view(
- Layout(self._component_constructor(**query_params)),
- send,
- recv,
- )
- )
-
- async def on_message(self, message: Union[str, bytes]) -> None:
- await self._message_queue.put(
- message if isinstance(message, str) else message.decode()
- )
-
- def on_close(self) -> None:
- if not self._dispatch_future.done():
- self._dispatch_future.cancel()
diff --git a/src/idom/server/utils.py b/src/idom/server/utils.py
deleted file mode 100644
index 1aa775e93..000000000
--- a/src/idom/server/utils.py
+++ /dev/null
@@ -1,123 +0,0 @@
-import asyncio
-import socket
-import time
-from contextlib import closing
-from functools import wraps
-from importlib import import_module
-from pathlib import Path
-from threading import Event, Thread
-from typing import Any, Callable, List, Optional
-
-from typing_extensions import ParamSpec
-
-import idom
-
-from .proto import ServerFactory
-
-
-CLIENT_BUILD_DIR = Path(idom.__file__).parent / "client"
-
-_SUPPORTED_PACKAGES = [
- "sanic",
- "fastapi",
- "flask",
- "tornado",
- "starlette",
-]
-
-
-_FuncParams = ParamSpec("_FuncParams")
-
-
-def threaded(function: Callable[_FuncParams, None]) -> Callable[_FuncParams, Thread]:
- @wraps(function)
- def wrapper(*args: Any, **kwargs: Any) -> Thread:
- def target() -> None:
- asyncio.set_event_loop(asyncio.new_event_loop())
- function(*args, **kwargs)
-
- thread = Thread(target=target, daemon=True)
- thread.start()
-
- return thread
-
- return wrapper
-
-
-def wait_on_event(description: str, event: Event, timeout: Optional[float]) -> None:
- if not event.wait(timeout):
- raise TimeoutError(f"Did not {description} within {timeout} seconds")
-
-
-def poll(
- description: str,
- frequency: float,
- timeout: Optional[float],
- function: Callable[[], bool],
-) -> None:
- if timeout is not None:
- expiry = time.time() + timeout
- while not function():
- if time.time() > expiry:
- raise TimeoutError(f"Did not {description} within {timeout} seconds")
- time.sleep(frequency)
- else:
- while not function():
- time.sleep(frequency)
-
-
-def find_builtin_server_type(type_name: str) -> ServerFactory[Any, Any]:
- """Find first installed server implementation
-
- Raises:
- :class:`RuntimeError` if one cannot be found
- """
- installed_builtins: List[str] = []
- for name in _SUPPORTED_PACKAGES:
- try:
- import_module(name)
- except ImportError: # pragma: no cover
- continue
- else:
- builtin_module = import_module(f"idom.server.{name}")
- installed_builtins.append(builtin_module.__name__)
- try:
- return getattr(builtin_module, type_name) # type: ignore
- except AttributeError: # pragma: no cover
- pass
- else: # pragma: no cover
- if not installed_builtins:
- raise RuntimeError(
- f"Found none of the following builtin server implementations {_SUPPORTED_PACKAGES}"
- )
- else:
- raise ImportError(
- f"No server type {type_name!r} found in installed implementations {installed_builtins}"
- )
-
-
-def find_available_port(
- host: str,
- port_min: int = 8000,
- port_max: int = 9000,
- allow_reuse_waiting_ports: bool = True,
-) -> int:
- """Get a port that's available for the given host and port range"""
- for port in range(port_min, port_max):
- with closing(socket.socket()) as sock:
- try:
- if allow_reuse_waiting_ports:
- # As per this answer: https://stackoverflow.com/a/19247688/3159288
- # setting can be somewhat unreliable because we allow the use of
- # ports that are stuck in TIME_WAIT. However, not setting the option
- # means we're overly cautious and almost always use a different addr
- # even if it could have actually been used.
- sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- sock.bind((host, port))
- except OSError:
- pass
- else:
- return port
- raise RuntimeError(
- f"Host {host!r} has no available port in range {port_max}-{port_max}"
- )
diff --git a/src/idom/testing.py b/src/idom/testing.py
deleted file mode 100644
index e310e4fe8..000000000
--- a/src/idom/testing.py
+++ /dev/null
@@ -1,437 +0,0 @@
-from __future__ import annotations
-
-import logging
-import re
-import shutil
-from contextlib import contextmanager
-from functools import wraps
-from traceback import format_exception
-from types import TracebackType
-from typing import (
- Any,
- Callable,
- Dict,
- Generic,
- Iterator,
- List,
- NoReturn,
- Optional,
- Tuple,
- Type,
- TypeVar,
- Union,
-)
-from urllib.parse import urlencode, urlunparse
-from uuid import uuid4
-from weakref import ref
-
-from selenium.webdriver import Chrome
-from selenium.webdriver.remote.webdriver import WebDriver
-
-from idom.config import IDOM_WED_MODULES_DIR
-from idom.core.events import EventHandler, to_event_handler_function
-from idom.core.hooks import LifeCycleHook, current_hook
-from idom.server.prefab import hotswap_server
-from idom.server.proto import ServerFactory, ServerType
-from idom.server.utils import find_available_port
-
-from .log import ROOT_LOGGER
-
-
-__all__ = [
- "find_available_port",
- "create_simple_selenium_web_driver",
- "ServerMountPoint",
-]
-
-
-def create_simple_selenium_web_driver(
- driver_type: Type[WebDriver] = Chrome,
- driver_options: Optional[Any] = None,
- implicit_wait_timeout: float = 10.0,
- page_load_timeout: float = 5.0,
- window_size: Tuple[int, int] = (1080, 800),
-) -> WebDriver:
- driver = driver_type(options=driver_options)
-
- driver.set_window_size(*window_size)
- driver.set_page_load_timeout(page_load_timeout)
- driver.implicitly_wait(implicit_wait_timeout)
-
- return driver
-
-
-_Self = TypeVar("_Self", bound="ServerMountPoint[Any, Any]")
-_Mount = TypeVar("_Mount")
-_Server = TypeVar("_Server", bound=ServerType[Any])
-_App = TypeVar("_App")
-_Config = TypeVar("_Config")
-
-
-class ServerMountPoint(Generic[_Mount, _Server]):
- """A context manager for imperatively mounting views to a render server when testing"""
-
- mount: _Mount
- server: _Server
-
- _log_handler: "_LogRecordCaptor"
-
- def __init__(
- self,
- server_type: Optional[ServerFactory[_App, _Config]] = None,
- host: str = "127.0.0.1",
- port: Optional[int] = None,
- server_config: Optional[_Config] = None,
- run_kwargs: Optional[Dict[str, Any]] = None,
- mount_and_server_constructor: "Callable[..., Tuple[_Mount, _Server]]" = hotswap_server, # type: ignore
- app: Optional[_App] = None,
- **other_options: Any,
- ) -> None:
- self.host = host
- self.port = port or find_available_port(host, allow_reuse_waiting_ports=False)
- self._mount_and_server_constructor: "Callable[[], Tuple[_Mount, _Server]]" = (
- lambda: mount_and_server_constructor(
- server_type,
- self.host,
- self.port,
- server_config,
- run_kwargs,
- app,
- **other_options,
- )
- )
-
- @property
- def log_records(self) -> List[logging.LogRecord]:
- """A list of captured log records"""
- return self._log_handler.records
-
- def url(self, path: str = "", query: Optional[Any] = None) -> str:
- """Return a URL string pointing to the host and point of the server
-
- Args:
- path: the path to a resource on the server
- query: a dictionary or list of query parameters
- """
- return urlunparse(
- [
- "http",
- f"{self.host}:{self.port}",
- path,
- "",
- urlencode(query or ()),
- "",
- ]
- )
-
- def list_logged_exceptions(
- self,
- pattern: str = "",
- types: Union[Type[Any], Tuple[Type[Any], ...]] = Exception,
- log_level: int = logging.ERROR,
- del_log_records: bool = True,
- ) -> List[BaseException]:
- """Return a list of logged exception matching the given criteria
-
- Args:
- log_level: The level of log to check
- exclude_exc_types: Any exception types to ignore
- del_log_records: Whether to delete the log records for yielded exceptions
- """
- found: List[BaseException] = []
- compiled_pattern = re.compile(pattern)
- for index, record in enumerate(self.log_records):
- if record.levelno >= log_level and record.exc_info:
- error = record.exc_info[1]
- if (
- error is not None
- and isinstance(error, types)
- and compiled_pattern.search(str(error))
- ):
- if del_log_records:
- del self.log_records[index - len(found)]
- found.append(error)
- return found
-
- def __enter__(self: _Self) -> _Self:
- self._log_handler = _LogRecordCaptor()
- logging.getLogger().addHandler(self._log_handler)
- self.mount, self.server = self._mount_and_server_constructor()
- return self
-
- def __exit__(
- self,
- exc_type: Optional[Type[BaseException]],
- exc_value: Optional[BaseException],
- traceback: Optional[TracebackType],
- ) -> None:
- self.server.stop()
- logging.getLogger().removeHandler(self._log_handler)
- del self.mount, self.server
- logged_errors = self.list_logged_exceptions(del_log_records=False)
- if logged_errors: # pragma: no cover
- raise logged_errors[0]
- return None
-
-
-class LogAssertionError(AssertionError):
- """An assertion error raised in relation to log messages."""
-
-
-@contextmanager
-def assert_idom_logged(
- match_message: str = "",
- error_type: type[Exception] | None = None,
- match_error: str = "",
- clear_matched_records: bool = False,
-) -> Iterator[None]:
- """Assert that IDOM produced a log matching the described message or error.
-
- Args:
- match_message: Must match a logged message.
- error_type: Checks the type of logged exceptions.
- match_error: Must match an error message.
- clear_matched_records: Whether to remove logged records that match.
- """
- message_pattern = re.compile(match_message)
- error_pattern = re.compile(match_error)
-
- try:
- with capture_idom_logs(yield_existing=clear_matched_records) as log_records:
- yield None
- except Exception:
- raise
- else:
- found = False
- for record in list(log_records):
- if (
- # record message matches
- message_pattern.findall(record.getMessage())
- # error type matches
- and (
- error_type is None
- or (
- record.exc_info is not None
- and record.exc_info[0] is not None
- and issubclass(record.exc_info[0], error_type)
- )
- )
- # error message pattern matches
- and (
- not match_error
- or (
- record.exc_info is not None
- and error_pattern.findall(
- "".join(format_exception(*record.exc_info))
- )
- )
- )
- ):
- found = True
- if clear_matched_records:
- log_records.remove(record)
-
- if not found: # pragma: no cover
- _raise_log_message_error(
- "Could not find a log record matching the given",
- match_message,
- error_type,
- match_error,
- )
-
-
-@contextmanager
-def assert_idom_did_not_log(
- match_message: str = "",
- error_type: type[Exception] | None = None,
- match_error: str = "",
- clear_matched_records: bool = False,
-) -> Iterator[None]:
- """Assert the inverse of :func:`assert_idom_logged`"""
- try:
- with assert_idom_logged(
- match_message, error_type, match_error, clear_matched_records
- ):
- yield None
- except LogAssertionError:
- pass
- else:
- _raise_log_message_error(
- "Did find a log record matching the given",
- match_message,
- error_type,
- match_error,
- )
-
-
-def _raise_log_message_error(
- prefix: str,
- match_message: str = "",
- error_type: type[Exception] | None = None,
- match_error: str = "",
-) -> NoReturn:
- conditions = []
- if match_message:
- conditions.append(f"log message pattern {match_message!r}")
- if error_type:
- conditions.append(f"exception type {error_type}")
- if match_error:
- conditions.append(f"error message pattern {match_error!r}")
- raise LogAssertionError(prefix + " " + " and ".join(conditions))
-
-
-@contextmanager
-def capture_idom_logs(
- yield_existing: bool = False,
-) -> Iterator[list[logging.LogRecord]]:
- """Capture logs from IDOM
-
- Parameters:
- yield_existing:
- If already inside an existing capture context yield the same list of logs.
- This is useful if you need to mutate the list of logs to affect behavior in
- the outer context.
- """
- if yield_existing:
- for handler in reversed(ROOT_LOGGER.handlers):
- if isinstance(handler, _LogRecordCaptor):
- yield handler.records
- return None
-
- handler = _LogRecordCaptor()
- original_level = ROOT_LOGGER.level
-
- ROOT_LOGGER.setLevel(logging.DEBUG)
- ROOT_LOGGER.addHandler(handler)
- try:
- yield handler.records
- finally:
- ROOT_LOGGER.removeHandler(handler)
- ROOT_LOGGER.setLevel(original_level)
-
-
-class _LogRecordCaptor(logging.NullHandler):
- def __init__(self) -> None:
- self.records: List[logging.LogRecord] = []
- super().__init__()
-
- def handle(self, record: logging.LogRecord) -> bool:
- self.records.append(record)
- return True
-
-
-class HookCatcher:
- """Utility for capturing a LifeCycleHook from a component
-
- Example:
- .. code-block::
-
- hooks = HookCatcher(index_by_kwarg="key")
-
- @idom.component
- @hooks.capture
- def MyComponent(key):
- ...
-
- ... # render the component
-
- # grab the last render of where MyComponent(key='some_key')
- hooks.index["some_key"]
- # or grab the hook from the component's last render
- hooks.latest
-
- After the first render of ``MyComponent`` the ``HookCatcher`` will have
- captured the component's ``LifeCycleHook``.
- """
-
- latest: LifeCycleHook
-
- def __init__(self, index_by_kwarg: Optional[str] = None):
- self.index_by_kwarg = index_by_kwarg
- self.index: Dict[Any, LifeCycleHook] = {}
-
- def capture(self, render_function: Callable[..., Any]) -> Callable[..., Any]:
- """Decorator for capturing a ``LifeCycleHook`` on each render of a component"""
-
- # The render function holds a reference to `self` and, via the `LifeCycleHook`,
- # the component. Some tests check whether components are garbage collected, thus
- # we must use a `ref` here to ensure these checks pass once the catcher itself
- # has been collected.
- self_ref = ref(self)
-
- @wraps(render_function)
- def wrapper(*args: Any, **kwargs: Any) -> Any:
- self = self_ref()
- assert self is not None, "Hook catcher has been garbage collected"
-
- hook = current_hook()
- if self.index_by_kwarg is not None:
- self.index[kwargs[self.index_by_kwarg]] = hook
- self.latest = hook
- return render_function(*args, **kwargs)
-
- return wrapper
-
-
-class StaticEventHandler:
- """Utility for capturing the target of one event handler
-
- Example:
- .. code-block::
-
- static_handler = StaticEventHandler()
-
- @idom.component
- def MyComponent():
- state, set_state = idom.hooks.use_state(0)
- handler = static_handler.use(lambda event: set_state(state + 1))
- return idom.html.button({"onClick": handler}, "Click me!")
-
- # gives the target ID for onClick where from the last render of MyComponent
- static_handlers.target
-
- If you need to capture event handlers from different instances of a component
- the you should create multiple ``StaticEventHandler`` instances.
-
- .. code-block::
-
- static_handlers_by_key = {
- "first": StaticEventHandler(),
- "second": StaticEventHandler(),
- }
-
- @idom.component
- def Parent():
- return idom.html.div(Child(key="first"), Child(key="second"))
-
- @idom.component
- def Child(key):
- state, set_state = idom.hooks.use_state(0)
- handler = static_handlers_by_key[key].use(lambda event: set_state(state + 1))
- return idom.html.button({"onClick": handler}, "Click me!")
-
- # grab the individual targets for each instance above
- first_target = static_handlers_by_key["first"].target
- second_target = static_handlers_by_key["second"].target
- """
-
- def __init__(self) -> None:
- self.target = uuid4().hex
-
- def use(
- self,
- function: Callable[..., Any],
- stop_propagation: bool = False,
- prevent_default: bool = False,
- ) -> EventHandler:
- return EventHandler(
- to_event_handler_function(function),
- stop_propagation,
- prevent_default,
- self.target,
- )
-
-
-def clear_idom_web_modules_dir() -> None:
- for path in IDOM_WED_MODULES_DIR.current.iterdir():
- shutil.rmtree(path) if path.is_dir() else path.unlink()
diff --git a/src/idom/utils.py b/src/idom/utils.py
deleted file mode 100644
index e8f9cfd01..000000000
--- a/src/idom/utils.py
+++ /dev/null
@@ -1,142 +0,0 @@
-from html.parser import HTMLParser as _HTMLParser
-from typing import Any, Callable, Dict, Generic, List, Optional, Tuple, TypeVar
-
-
-_RefValue = TypeVar("_RefValue")
-_UNDEFINED: Any = object()
-
-
-class Ref(Generic[_RefValue]):
- """Hold a reference to a value
-
- This is used in imperative code to mutate the state of this object in order to
- incur side effects. Generally refs should be avoided if possible, but sometimes
- they are required.
-
- Notes:
- You can compare the contents for two ``Ref`` objects using the ``==`` operator.
- """
-
- __slots__ = "current"
-
- def __init__(self, initial_value: _RefValue = _UNDEFINED) -> None:
- if initial_value is not _UNDEFINED:
- self.current = initial_value
- """The present value"""
-
- def set_current(self, new: _RefValue) -> _RefValue:
- """Set the current value and return what is now the old value
-
- This is nice to use in ``lambda`` functions.
- """
- old = self.current
- self.current = new
- return old
-
- def __eq__(self, other: Any) -> bool:
- try:
- return isinstance(other, Ref) and (other.current == self.current)
- except AttributeError:
- # attribute error occurs for uninitialized refs
- return False
-
- def __repr__(self) -> str:
- try:
- current = repr(self.current)
- except AttributeError:
- # attribute error occurs for uninitialized refs
- current = ""
- return f"{type(self).__name__}({current})"
-
-
-_ModelTransform = Callable[[Dict[str, Any]], Any]
-
-
-def html_to_vdom(source: str, *transforms: _ModelTransform) -> Dict[str, Any]:
- """Transform HTML into a DOM model
-
- Parameters:
- source:
- The raw HTML as a string
- transforms:
- Functions of the form ``transform(old) -> new`` where ``old`` is a VDOM
- dictionary which will be replaced by ``new``. For example, you could use a
- transform function to add highlighting to a ``
`` block.
- """
- parser = HtmlParser()
- parser.feed(source)
- root = parser.model()
- to_visit = [root]
- while to_visit:
- node = to_visit.pop(0)
- if isinstance(node, dict) and "children" in node:
- transformed = []
- for child in node["children"]:
- if isinstance(child, dict):
- for t in transforms:
- child = t(child)
- if child is not None:
- transformed.append(child)
- to_visit.append(child)
- node["children"] = transformed
- if "attributes" in node and not node["attributes"]:
- del node["attributes"]
- if "children" in node and not node["children"]:
- del node["children"]
- return root
-
-
-class HtmlParser(_HTMLParser):
- """HTML to VDOM parser
-
- Example:
-
- .. code-block::
-
- parser = HtmlParser()
-
- parser.feed(an_html_string)
- parser.feed(another_html_string)
- ...
-
- vdom = parser.model()
- """
-
- def model(self) -> Dict[str, Any]:
- """Get the current state of parsed VDOM model"""
- return self._node_stack[0]
-
- def feed(self, data: str) -> None:
- """Feed in HTML that will update the :meth:`HtmlParser.model`"""
- self._node_stack.append(self._make_vdom("div", {}))
- super().feed(data)
-
- def reset(self) -> None:
- """Reset the state of the parser"""
- self._node_stack: List[Dict[str, Any]] = []
- super().reset()
-
- def handle_starttag(self, tag: str, attrs: List[Tuple[str, Optional[str]]]) -> None:
- new = self._make_vdom(tag, dict(attrs))
- current = self._node_stack[-1]
- current["children"].append(new)
- self._node_stack.append(new)
-
- def handle_endtag(self, tag: str) -> None:
- del self._node_stack[-1]
-
- def handle_data(self, data: str) -> None:
- self._node_stack[-1]["children"].append(data)
-
- @staticmethod
- def _make_vdom(tag: str, attrs: Dict[str, Any]) -> Dict[str, Any]:
- if "style" in attrs:
- style = attrs["style"]
- if isinstance(style, str):
- style_dict = {}
- for k, v in (part.split(":", 1) for part in style.split(";") if part):
- title_case_key = k.title().replace("-", "")
- camel_case_key = title_case_key[:1].lower() + title_case_key[1:]
- style_dict[camel_case_key] = v
- attrs["style"] = style_dict
- return {"tagName": tag, "attributes": attrs, "children": []}
diff --git a/src/idom/web/templates/react.default.js b/src/idom/web/templates/react.default.js
deleted file mode 100644
index d5d8b5ac9..000000000
--- a/src/idom/web/templates/react.default.js
+++ /dev/null
@@ -1,48 +0,0 @@
-export { default } from "$CDN/$PACKAGE";
-export * from "$CDN/$PACKAGE";
-
-import * as React from "$CDN/react";
-import * as ReactDOM from "$CDN/react-dom";
-
-export function bind(node, config) {
- return {
- create: (component, props, children) =>
- React.createElement(component, wrapEventHandlers(props), ...children),
- render: (element) => ReactDOM.render(element, node),
- unmount: () => ReactDOM.unmountComponentAtNode(node),
- };
-}
-
-function wrapEventHandlers(props) {
- const newProps = Object.assign({}, props);
- for (const [key, value] of Object.entries(props)) {
- if (typeof value === "function") {
- newProps[key] = makeJsonSafeEventHandler(value);
- }
- }
- return newProps;
-}
-
-function makeJsonSafeEventHandler(oldHandler) {
- // Since we can't really know what the event handlers get passed we have to check if
- // they are JSON serializable or not. We can allow normal synthetic events to pass
- // through since the original handler already knows how to serialize those for us.
- return function safeEventHandler() {
- oldHandler(
- ...Array.from(arguments).filter((value) => {
- if (typeof value === "object" && value.nativeEvent) {
- // this is probably a standard React synthetic event
- return true;
- } else {
- try {
- JSON.stringify(value);
- } catch (err) {
- console.error("Failed to serialize some event data");
- return false;
- }
- return true;
- }
- })
- );
- };
-}
diff --git a/src/idom/widgets.py b/src/idom/widgets.py
deleted file mode 100644
index 2564ac945..000000000
--- a/src/idom/widgets.py
+++ /dev/null
@@ -1,280 +0,0 @@
-from __future__ import annotations
-
-from base64 import b64encode
-from typing import (
- Any,
- Callable,
- Dict,
- List,
- Optional,
- Sequence,
- Set,
- Tuple,
- TypeVar,
- Union,
-)
-
-from typing_extensions import Protocol
-
-import idom
-
-from . import html
-from .core import hooks
-from .core.component import component
-from .core.proto import ComponentConstructor, VdomDict
-from .utils import Ref
-
-
-def image(
- format: str,
- value: Union[str, bytes] = "",
- attributes: Optional[Dict[str, Any]] = None,
-) -> VdomDict:
- """Utility for constructing an image from a string or bytes
-
- The source value will automatically be encoded to base64
- """
- if format == "svg":
- format = "svg+xml"
-
- if isinstance(value, str):
- bytes_value = value.encode()
- else:
- bytes_value = value
-
- base64_value = b64encode(bytes_value).decode()
- src = f"data:image/{format};base64,{base64_value}"
-
- return {"tagName": "img", "attributes": {"src": src, **(attributes or {})}}
-
-
-_Value = TypeVar("_Value")
-
-
-def use_linked_inputs(
- attributes: Sequence[Dict[str, Any]],
- on_change: Callable[[_Value], None] = lambda value: None,
- cast: _CastFunc[_Value] = lambda value: value,
- initial_value: str = "",
- ignore_empty: bool = True,
-) -> List[VdomDict]:
- """Return a list of linked inputs equal to the number of given attributes.
-
- Parameters:
- attributes:
- That attributes of each returned input element. If the number of generated
- inputs is variable, you may need to assign each one a
- :ref:`key ` by including a ``"key"`` in each
- attribute dictionary.
- on_change:
- A callback which is triggered when any input is changed. This callback need
- not update the 'value' field in the attributes of the inputs since that is
- handled automatically.
- cast:
- Cast the 'value' of changed inputs that is passed to ``on_change``.
- initial_value:
- Initialize the 'value' field of the inputs.
- ignore_empty:
- Do not trigger ``on_change`` if the 'value' is an empty string.
- """
- value, set_value = idom.hooks.use_state(initial_value)
-
- def sync_inputs(event: Dict[str, Any]) -> None:
- new_value = event["value"]
- set_value(new_value)
- if not new_value and ignore_empty:
- return None
- on_change(cast(new_value))
-
- inputs: list[VdomDict] = []
- for attrs in attributes:
- # we're going to mutate this so copy it
- attrs = attrs.copy()
-
- key = attrs.pop("key", None)
- attrs.update({"onChange": sync_inputs, "value": value})
-
- inputs.append(html.input(attrs, key=key))
-
- return inputs
-
-
-_CastTo = TypeVar("_CastTo", covariant=True)
-
-
-class _CastFunc(Protocol[_CastTo]):
- def __call__(self, value: str) -> _CastTo:
- ...
-
-
-MountFunc = Callable[[ComponentConstructor], None]
-
-
-def hotswap(update_on_change: bool = False) -> Tuple[MountFunc, ComponentConstructor]:
- """Swap out components from a layout on the fly.
-
- Since you can't change the component functions used to create a layout
- in an imperative manner, you can use ``hotswap`` to do this so
- long as you set things up ahead of time.
-
- Parameters:
- update_on_change: Whether or not all views of the layout should be udpated on a swap.
-
- Example:
- .. code-block:: python
-
- import idom
-
- show, root = idom.hotswap()
- PerClientStateServer(root).run_in_thread("localhost", 8765)
-
- @idom.component
- def DivOne(self):
- return {"tagName": "div", "children": [1]}
-
- show(DivOne)
-
- # displaying the output now will show DivOne
-
- @idom.component
- def DivTwo(self):
- return {"tagName": "div", "children": [2]}
-
- show(DivTwo)
-
- # displaying the output now will show DivTwo
- """
- constructor_ref: Ref[Callable[[], Any]] = Ref(lambda: {"tagName": "div"})
-
- if update_on_change:
- set_constructor_callbacks: Set[Callable[[Callable[[], Any]], None]] = set()
-
- @component
- def HotSwap() -> Any:
- # new displays will adopt the latest constructor and arguments
- constructor, set_constructor = _use_callable(constructor_ref.current)
-
- def add_callback() -> Callable[[], None]:
- set_constructor_callbacks.add(set_constructor)
- return lambda: set_constructor_callbacks.remove(set_constructor)
-
- hooks.use_effect(add_callback)
-
- return constructor()
-
- def swap(constructor: Callable[[], Any]) -> None:
- constructor_ref.current = constructor
-
- for set_constructor in set_constructor_callbacks:
- set_constructor(constructor)
-
- return None
-
- else:
-
- @component
- def HotSwap() -> Any:
- return constructor_ref.current()
-
- def swap(constructor: Callable[[], Any]) -> None:
- constructor_ref.current = constructor
- return None
-
- return swap, HotSwap
-
-
-_Func = Callable[..., Any]
-
-
-def _use_callable(initial_func: _Func) -> Tuple[_Func, Callable[[_Func], None]]:
- state = hooks.use_state(lambda: initial_func)
- return state[0], lambda new: state[1](lambda old: new)
-
-
-def multiview() -> Tuple[MultiViewMount, ComponentConstructor]:
- """Dynamically add components to a layout on the fly
-
- Since you can't change the component functions used to create a layout
- in an imperative manner, you can use ``multiview`` to do this so
- long as you set things up ahead of time.
-
- Examples:
-
- .. code-block::
-
- import idom
-
- mount, multiview = idom.widgets.multiview()
-
- @idom.component
- def Hello():
- return idom.html.h1(["hello"])
-
- # auto static view ID
- mount.add("hello", Hello)
- # use the view ID to create the associate component instance
- hello_component_instance = multiview("hello")
-
- @idom.component
- def World():
- return idom.html.h1(["world"])
-
- generated_view_id = mount.add(None, World)
- world_component_instance = multiview(generated_view_id)
-
- Displaying ``root`` with the parameter ``view_id=hello_world_view_id`` will show
- the message 'hello world'. Usually though this is achieved by connecting to the
- socket serving up the VDOM with a query parameter for view ID. This allow many
- views to be added and then displayed dynamically in, for example, a Jupyter
- Notebook where one might want multiple active views which can all be interacted
- with at the same time.
-
- See :func:`idom.server.prefab.multiview_server` for a reference usage.
- """
- views: Dict[str, ComponentConstructor] = {}
-
- @component
- def MultiView(view_id: str) -> Any:
- try:
- return views[view_id]()
- except KeyError:
- raise ValueError(f"Unknown view {view_id!r}")
-
- return MultiViewMount(views), MultiView
-
-
-class MultiViewMount:
- """Mount point for :func:`multiview`"""
-
- __slots__ = "_next_auto_id", "_views"
-
- def __init__(self, views: Dict[str, ComponentConstructor]):
- self._next_auto_id = 0
- self._views = views
-
- def add(self, view_id: Optional[str], constructor: ComponentConstructor) -> str:
- """Add a component constructor
-
- Parameters:
- view_id:
- The view ID the constructor will be associated with. If ``None`` then
- a view ID will be automatically generated.
- constructor:
- The component constructor to be mounted. It must accept no arguments.
-
- Returns:
- The view ID that was assocaited with the component - most useful for
- auto-generated view IDs
- """
- if view_id is None:
- self._next_auto_id += 1
- view_id = str(self._next_auto_id)
- self._views[view_id] = constructor
- return view_id
-
- def remove(self, view_id: str) -> None:
- """Remove a mounted component constructor given its view ID"""
- del self._views[view_id]
-
- def __repr__(self) -> str:
- return f"{type(self).__name__}({self._views})"
diff --git a/src/js/.eslintrc.json b/src/js/.eslintrc.json
new file mode 100644
index 000000000..8536da62b
--- /dev/null
+++ b/src/js/.eslintrc.json
@@ -0,0 +1,31 @@
+{
+ "env": {
+ "browser": true,
+ "node": true,
+ "es2021": true
+ },
+ "extends": [
+ "eslint:recommended",
+ "plugin:react/recommended",
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "overrides": [],
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "plugins": ["react", "@typescript-eslint"],
+ "rules": {
+ "@typescript-eslint/ban-ts-comment": "off",
+ "@typescript-eslint/no-explicit-any": "off",
+ "@typescript-eslint/no-non-null-assertion": "off",
+ "@typescript-eslint/no-empty-function": "off",
+ "react/prop-types": "off"
+ },
+ "settings": {
+ "react": {
+ "version": "detect"
+ }
+ }
+}
diff --git a/src/js/.gitignore b/src/js/.gitignore
new file mode 100644
index 000000000..fedd7ea26
--- /dev/null
+++ b/src/js/.gitignore
@@ -0,0 +1,3 @@
+tsconfig.tsbuildinfo
+packages/**/package-lock.json
+dist
diff --git a/src/js/README.md b/src/js/README.md
new file mode 100644
index 000000000..e99df49c0
--- /dev/null
+++ b/src/js/README.md
@@ -0,0 +1,3 @@
+# ReactPy Client
+
+An ES6 Javascript client for ReactPy
diff --git a/src/js/app/.eslintrc.json b/src/js/app/.eslintrc.json
new file mode 100644
index 000000000..442025c3d
--- /dev/null
+++ b/src/js/app/.eslintrc.json
@@ -0,0 +1,19 @@
+{
+ "env": {
+ "browser": true,
+ "es2021": true
+ },
+ "extends": [
+ "eslint:recommended",
+ "plugin:react/recommended",
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "overrides": [],
+ "parser": "@typescript-eslint/parser",
+ "parserOptions": {
+ "ecmaVersion": "latest",
+ "sourceType": "module"
+ },
+ "plugins": ["react", "@typescript-eslint"],
+ "rules": {}
+}
diff --git a/src/js/app/index.html b/src/js/app/index.html
new file mode 100644
index 000000000..e94280368
--- /dev/null
+++ b/src/js/app/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+ {__head__}
+
+
+
+
+
diff --git a/src/js/app/package-lock.json b/src/js/app/package-lock.json
new file mode 100644
index 000000000..adc398279
--- /dev/null
+++ b/src/js/app/package-lock.json
@@ -0,0 +1,1193 @@
+{
+ "name": "ui",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "license": "MIT",
+ "dependencies": {
+ "@reactpy/client": "^0.2.0",
+ "preact": "^10.7.0"
+ },
+ "devDependencies": {
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "typescript": "^4.9.5",
+ "vite": "^3.2.8"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
+ "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
+ "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@reactpy/client": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
+ "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
+ "dependencies": {
+ "event-to-object": "^0.1.2",
+ "json-pointer": "^0.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16 <18",
+ "react-dom": ">=16 <18"
+ }
+ },
+ "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==",
+ "dev": true
+ },
+ "node_modules/@types/react": {
+ "version": "17.0.57",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.57.tgz",
+ "integrity": "sha512-e4msYpu5QDxzNrXDHunU/VPyv2M1XemGG/p7kfCjUiPtlLDCWLGQfgAMng6YyisWYxZ09mYdQlmMnyS0NfZdEg==",
+ "dev": true,
+ "dependencies": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "17.0.19",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz",
+ "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "^17"
+ }
+ },
+ "node_modules/@types/scheduler": {
+ "version": "0.16.3",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
+ "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
+ "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==",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
+ "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.15.18",
+ "@esbuild/linux-loong64": "0.15.18",
+ "esbuild-android-64": "0.15.18",
+ "esbuild-android-arm64": "0.15.18",
+ "esbuild-darwin-64": "0.15.18",
+ "esbuild-darwin-arm64": "0.15.18",
+ "esbuild-freebsd-64": "0.15.18",
+ "esbuild-freebsd-arm64": "0.15.18",
+ "esbuild-linux-32": "0.15.18",
+ "esbuild-linux-64": "0.15.18",
+ "esbuild-linux-arm": "0.15.18",
+ "esbuild-linux-arm64": "0.15.18",
+ "esbuild-linux-mips64le": "0.15.18",
+ "esbuild-linux-ppc64le": "0.15.18",
+ "esbuild-linux-riscv64": "0.15.18",
+ "esbuild-linux-s390x": "0.15.18",
+ "esbuild-netbsd-64": "0.15.18",
+ "esbuild-openbsd-64": "0.15.18",
+ "esbuild-sunos-64": "0.15.18",
+ "esbuild-windows-32": "0.15.18",
+ "esbuild-windows-64": "0.15.18",
+ "esbuild-windows-arm64": "0.15.18"
+ }
+ },
+ "node_modules/esbuild-android-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
+ "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-android-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
+ "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
+ "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
+ "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
+ "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
+ "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
+ "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
+ "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
+ "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
+ "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-mips64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
+ "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-ppc64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
+ "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-riscv64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
+ "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-s390x": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
+ "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-netbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
+ "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-openbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
+ "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-sunos-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
+ "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
+ "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
+ "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
+ "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/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==",
+ "dependencies": {
+ "json-pointer": "^0.6.2"
+ }
+ },
+ "node_modules/foreach": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
+ "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg=="
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz",
+ "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/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
+ },
+ "node_modules/json-pointer": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz",
+ "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==",
+ "dependencies": {
+ "foreach": "^2.0.4"
+ }
+ },
+ "node_modules/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,
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/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,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/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==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/postcss": {
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/preact": {
+ "version": "10.13.2",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.2.tgz",
+ "integrity": "sha512-q44QFLhOhty2Bd0Y46fnYW0gD/cbVM9dUVtNTDKPcdXSMA7jfY+Jpd6rk3GB0lcQss0z5s/6CmVP0Z/hV+g6pw==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/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,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/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,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "scheduler": "^0.20.2"
+ },
+ "peerDependencies": {
+ "react": "17.0.2"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.2",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+ "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.11.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "2.79.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
+ "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
+ "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/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==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "3.2.8",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.8.tgz",
+ "integrity": "sha512-EtQU16PLIJpAZol2cTLttNP1mX6L0SyI0pgQB1VOoWeQnMSvtiwovV3D6NcjN8CZQWWyESD2v5NGnpz5RvgOZA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.15.9",
+ "postcss": "^8.4.18",
+ "resolve": "^1.22.1",
+ "rollup": "^2.79.1"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ }
+ },
+ "dependencies": {
+ "@esbuild/android-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
+ "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-loong64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
+ "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@reactpy/client": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
+ "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
+ "requires": {
+ "event-to-object": "^0.1.2",
+ "json-pointer": "^0.6.2"
+ }
+ },
+ "@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.57",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.57.tgz",
+ "integrity": "sha512-e4msYpu5QDxzNrXDHunU/VPyv2M1XemGG/p7kfCjUiPtlLDCWLGQfgAMng6YyisWYxZ09mYdQlmMnyS0NfZdEg==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@types/react-dom": {
+ "version": "17.0.19",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz",
+ "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==",
+ "dev": true,
+ "requires": {
+ "@types/react": "^17"
+ }
+ },
+ "@types/scheduler": {
+ "version": "0.16.3",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
+ "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==",
+ "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
+ },
+ "esbuild": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
+ "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
+ "dev": true,
+ "requires": {
+ "@esbuild/android-arm": "0.15.18",
+ "@esbuild/linux-loong64": "0.15.18",
+ "esbuild-android-64": "0.15.18",
+ "esbuild-android-arm64": "0.15.18",
+ "esbuild-darwin-64": "0.15.18",
+ "esbuild-darwin-arm64": "0.15.18",
+ "esbuild-freebsd-64": "0.15.18",
+ "esbuild-freebsd-arm64": "0.15.18",
+ "esbuild-linux-32": "0.15.18",
+ "esbuild-linux-64": "0.15.18",
+ "esbuild-linux-arm": "0.15.18",
+ "esbuild-linux-arm64": "0.15.18",
+ "esbuild-linux-mips64le": "0.15.18",
+ "esbuild-linux-ppc64le": "0.15.18",
+ "esbuild-linux-riscv64": "0.15.18",
+ "esbuild-linux-s390x": "0.15.18",
+ "esbuild-netbsd-64": "0.15.18",
+ "esbuild-openbsd-64": "0.15.18",
+ "esbuild-sunos-64": "0.15.18",
+ "esbuild-windows-32": "0.15.18",
+ "esbuild-windows-64": "0.15.18",
+ "esbuild-windows-arm64": "0.15.18"
+ }
+ },
+ "esbuild-android-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
+ "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-android-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
+ "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-darwin-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
+ "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-darwin-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
+ "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-freebsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
+ "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-freebsd-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
+ "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
+ "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
+ "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
+ "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
+ "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-mips64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
+ "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-ppc64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
+ "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-riscv64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
+ "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-s390x": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
+ "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-netbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
+ "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-openbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
+ "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-sunos-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
+ "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
+ "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
+ "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
+ "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
+ "dev": true,
+ "optional": true
+ },
+ "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=="
+ },
+ "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==",
+ "dev": true
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "is-core-module": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.0.tgz",
+ "integrity": "sha512-RECHCBCd/viahWmwj6enj19sKbHfJrddi/6cBDsNTKbNq0f7VeaUkBo60BqzvPqo/W54ChS62Z5qyun7cfOMqQ==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "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"
+ }
+ },
+ "nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true
+ },
+ "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
+ },
+ "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==",
+ "dev": true
+ },
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "postcss": {
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+ "dev": true,
+ "requires": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "preact": {
+ "version": "10.13.2",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.13.2.tgz",
+ "integrity": "sha512-q44QFLhOhty2Bd0Y46fnYW0gD/cbVM9dUVtNTDKPcdXSMA7jfY+Jpd6rk3GB0lcQss0z5s/6CmVP0Z/hV+g6pw=="
+ },
+ "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.2",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+ "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.11.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "rollup": {
+ "version": "2.79.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
+ "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
+ "dev": 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"
+ }
+ },
+ "source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true
+ },
+ "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==",
+ "dev": true
+ },
+ "typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true
+ },
+ "vite": {
+ "version": "3.2.8",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.8.tgz",
+ "integrity": "sha512-EtQU16PLIJpAZol2cTLttNP1mX6L0SyI0pgQB1VOoWeQnMSvtiwovV3D6NcjN8CZQWWyESD2v5NGnpz5RvgOZA==",
+ "dev": true,
+ "requires": {
+ "esbuild": "^0.15.9",
+ "fsevents": "~2.3.2",
+ "postcss": "^8.4.18",
+ "resolve": "^1.22.1",
+ "rollup": "^2.79.1"
+ }
+ }
+ }
+}
diff --git a/src/js/app/package.json b/src/js/app/package.json
new file mode 100644
index 000000000..55a42fd66
--- /dev/null
+++ b/src/js/app/package.json
@@ -0,0 +1,28 @@
+{
+ "author": "Ryan Morshead",
+ "license": "MIT",
+ "main": "src/dist/index.js",
+ "types": "src/dist/index.d.ts",
+ "description": "A client application for ReactPy implemented in React",
+ "dependencies": {
+ "@reactpy/client": "^0.2.0",
+ "preact": "^10.7.0"
+ },
+ "devDependencies": {
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "typescript": "^4.9.5",
+ "vite": "^3.2.8"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/reactive-python/reactpy"
+ },
+ "scripts": {
+ "build": "vite build",
+ "format": "prettier --write . && eslint --fix .",
+ "test": "npm run check:tests",
+ "check:tests": "echo 'no tests'",
+ "check:types": "tsc --noEmit"
+ }
+}
diff --git a/src/js/app/public/assets/reactpy-logo.ico b/src/js/app/public/assets/reactpy-logo.ico
new file mode 100644
index 000000000..62be5f5ba
Binary files /dev/null and b/src/js/app/public/assets/reactpy-logo.ico differ
diff --git a/src/js/app/src/index.ts b/src/js/app/src/index.ts
new file mode 100644
index 000000000..1f47853aa
--- /dev/null
+++ b/src/js/app/src/index.ts
@@ -0,0 +1,12 @@
+import { mount, SimpleReactPyClient } from "@reactpy/client";
+
+export function app(element: HTMLElement) {
+ const client = new SimpleReactPyClient({
+ serverLocation: {
+ url: document.location.origin,
+ route: document.location.pathname,
+ query: document.location.search,
+ },
+ });
+ mount(element, client);
+}
diff --git a/src/js/app/tsconfig.json b/src/js/app/tsconfig.json
new file mode 100644
index 000000000..c736ab13d
--- /dev/null
+++ b/src/js/app/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../tsconfig.package.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "composite": true
+ },
+ "include": ["src"],
+ "references": [
+ {
+ "path": "../packages/@reactpy/client"
+ }
+ ]
+}
diff --git a/src/js/app/vite.config.js b/src/js/app/vite.config.js
new file mode 100644
index 000000000..c97fb6dac
--- /dev/null
+++ b/src/js/app/vite.config.js
@@ -0,0 +1,12 @@
+import { defineConfig } from "vite";
+
+export default defineConfig({
+ build: { emptyOutDir: true },
+ resolve: {
+ alias: {
+ react: "preact/compat",
+ "react-dom": "preact/compat",
+ },
+ },
+ base: "/_reactpy/",
+});
diff --git a/src/js/package-lock.json b/src/js/package-lock.json
new file mode 100644
index 000000000..91b7f302c
--- /dev/null
+++ b/src/js/package-lock.json
@@ -0,0 +1,6003 @@
+{
+ "name": "js",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "license": "MIT",
+ "workspaces": [
+ "packages/event-to-object",
+ "packages/@reactpy/client",
+ "app"
+ ],
+ "devDependencies": {
+ "@typescript-eslint/eslint-plugin": "^5.58.0",
+ "@typescript-eslint/parser": "^5.58.0",
+ "eslint": "^8.38.0",
+ "eslint-plugin-react": "^7.32.2",
+ "prettier": "^3.0.0-alpha.6"
+ }
+ },
+ "app": {
+ "license": "MIT",
+ "dependencies": {
+ "@reactpy/client": "^0.2.0",
+ "preact": "^10.7.0"
+ },
+ "devDependencies": {
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "typescript": "^4.9.5",
+ "vite": "^3.2.8"
+ }
+ },
+ "app/node_modules/@reactpy/client": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
+ "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
+ "dependencies": {
+ "event-to-object": "^0.1.2",
+ "json-pointer": "^0.6.2"
+ },
+ "peerDependencies": {
+ "react": ">=16 <18",
+ "react-dom": ">=16 <18"
+ }
+ },
+ "app/node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "apps/ui": {
+ "extraneous": true,
+ "license": "MIT",
+ "dependencies": {
+ "@reactpy/client": "^0.2.0",
+ "preact": "^10.7.0"
+ },
+ "devDependencies": {
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "prettier": "^3.0.0-alpha.6",
+ "typescript": "^4.9.5",
+ "vite": "^3.1.8"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
+ "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
+ "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz",
+ "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz",
+ "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.5.1",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz",
+ "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.8",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
+ "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@reactpy/client": {
+ "resolved": "packages/@reactpy/client",
+ "link": true
+ },
+ "node_modules/@types/json-pointer": {
+ "version": "1.0.31",
+ "resolved": "https://registry.npmjs.org/@types/json-pointer/-/json-pointer-1.0.31.tgz",
+ "integrity": "sha512-hTPul7Um6LqsHXHQpdkXTU7Oysjsf+9k4Yfmg6JhSKG/jj9QuQGyMUdj6trPH6WHiIdxw7nYSROgOxeFmCVK2w==",
+ "dev": true
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
+ "dev": true
+ },
+ "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==",
+ "dev": true
+ },
+ "node_modules/@types/react": {
+ "version": "17.0.53",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz",
+ "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==",
+ "dev": true,
+ "dependencies": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "17.0.19",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz",
+ "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/react": "^17"
+ }
+ },
+ "node_modules/@types/scheduler": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
+ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
+ "dev": true
+ },
+ "node_modules/@types/semver": {
+ "version": "7.3.13",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
+ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz",
+ "integrity": "sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.4.0",
+ "@typescript-eslint/scope-manager": "5.58.0",
+ "@typescript-eslint/type-utils": "5.58.0",
+ "@typescript-eslint/utils": "5.58.0",
+ "debug": "^4.3.4",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "natural-compare-lite": "^1.4.0",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^5.0.0",
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.58.0.tgz",
+ "integrity": "sha512-ixaM3gRtlfrKzP8N6lRhBbjTow1t6ztfBvQNGuRM8qH1bjFFXIJ35XY+FC0RRBKn3C6cT+7VW1y8tNm7DwPHDQ==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "5.58.0",
+ "@typescript-eslint/types": "5.58.0",
+ "@typescript-eslint/typescript-estree": "5.58.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.58.0.tgz",
+ "integrity": "sha512-b+w8ypN5CFvrXWQb9Ow9T4/6LC2MikNf1viLkYTiTbkQl46CnR69w7lajz1icW0TBsYmlpg+mRzFJ4LEJ8X9NA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.58.0",
+ "@typescript-eslint/visitor-keys": "5.58.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.58.0.tgz",
+ "integrity": "sha512-FF5vP/SKAFJ+LmR9PENql7fQVVgGDOS+dq3j+cKl9iW/9VuZC/8CFmzIP0DLKXfWKpRHawJiG70rVH+xZZbp8w==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "5.58.0",
+ "@typescript-eslint/utils": "5.58.0",
+ "debug": "^4.3.4",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.58.0.tgz",
+ "integrity": "sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.58.0.tgz",
+ "integrity": "sha512-cRACvGTodA+UxnYM2uwA2KCwRL7VAzo45syNysqlMyNyjw0Z35Icc9ihPJZjIYuA5bXJYiJ2YGUB59BqlOZT1Q==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.58.0",
+ "@typescript-eslint/visitor-keys": "5.58.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.58.0.tgz",
+ "integrity": "sha512-gAmLOTFXMXOC+zP1fsqm3VceKSBQJNzV385Ok3+yzlavNHZoedajjS4UyS21gabJYcobuigQPs/z71A9MdJFqQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@types/json-schema": "^7.0.9",
+ "@types/semver": "^7.3.12",
+ "@typescript-eslint/scope-manager": "5.58.0",
+ "@typescript-eslint/types": "5.58.0",
+ "@typescript-eslint/typescript-estree": "5.58.0",
+ "eslint-scope": "^5.1.1",
+ "semver": "^7.3.7"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/utils/node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.58.0.tgz",
+ "integrity": "sha512-/fBraTlPj0jwdyTwLyrRTxv/3lnU2H96pNTVM6z3esTWLtA5MZ9ghSMJ7Rb+TtUAdtEw9EyJzJ0EydIMKxQ9gA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.58.0",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.8.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/app": {
+ "resolved": "app",
+ "link": true
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
+ "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "is-array-buffer": "^3.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
+ "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
+ "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz",
+ "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0",
+ "get-intrinsic": "^1.1.3"
+ }
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/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
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true
+ },
+ "node_modules/csstype": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
+ "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "dev": true,
+ "dependencies": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/diff": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
+ "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+ "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
+ "dev": true,
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.0",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.0",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
+ "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3",
+ "has": "^1.0.3",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
+ "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
+ "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.15.18",
+ "@esbuild/linux-loong64": "0.15.18",
+ "esbuild-android-64": "0.15.18",
+ "esbuild-android-arm64": "0.15.18",
+ "esbuild-darwin-64": "0.15.18",
+ "esbuild-darwin-arm64": "0.15.18",
+ "esbuild-freebsd-64": "0.15.18",
+ "esbuild-freebsd-arm64": "0.15.18",
+ "esbuild-linux-32": "0.15.18",
+ "esbuild-linux-64": "0.15.18",
+ "esbuild-linux-arm": "0.15.18",
+ "esbuild-linux-arm64": "0.15.18",
+ "esbuild-linux-mips64le": "0.15.18",
+ "esbuild-linux-ppc64le": "0.15.18",
+ "esbuild-linux-riscv64": "0.15.18",
+ "esbuild-linux-s390x": "0.15.18",
+ "esbuild-netbsd-64": "0.15.18",
+ "esbuild-openbsd-64": "0.15.18",
+ "esbuild-sunos-64": "0.15.18",
+ "esbuild-windows-32": "0.15.18",
+ "esbuild-windows-64": "0.15.18",
+ "esbuild-windows-arm64": "0.15.18"
+ }
+ },
+ "node_modules/esbuild-android-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
+ "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-android-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
+ "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
+ "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
+ "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
+ "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
+ "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
+ "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
+ "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
+ "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
+ "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-mips64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
+ "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-ppc64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
+ "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-riscv64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
+ "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-s390x": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
+ "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-netbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
+ "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-openbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
+ "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-sunos-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
+ "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
+ "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
+ "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
+ "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz",
+ "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.4.0",
+ "@eslint/eslintrc": "^2.0.2",
+ "@eslint/js": "8.38.0",
+ "@humanwhocodes/config-array": "^0.11.8",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.4.0",
+ "espree": "^9.5.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-sdsl": "^4.1.4",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.32.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz",
+ "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flatmap": "^1.3.1",
+ "array.prototype.tosorted": "^1.1.1",
+ "doctrine": "^2.1.0",
+ "estraverse": "^5.3.0",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.6",
+ "object.fromentries": "^2.0.6",
+ "object.hasown": "^1.1.2",
+ "object.values": "^1.1.6",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.4",
+ "semver": "^6.3.0",
+ "string.prototype.matchall": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/resolve": {
+ "version": "2.0.0-next.4",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
+ "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
+ "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.5.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz",
+ "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.8.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+ "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/event-to-object": {
+ "resolved": "packages/event-to-object",
+ "link": true
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.2.12",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+ "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+ "dev": true
+ },
+ "node_modules/for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dev": true,
+ "dependencies": {
+ "is-callable": "^1.1.3"
+ }
+ },
+ "node_modules/foreach": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz",
+ "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg=="
+ },
+ "node_modules/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
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "dev": true
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+ "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.0",
+ "functions-have-names": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+ "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
+ "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.20.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
+ "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+ "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
+ "node_modules/happy-dom": {
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.9.0.tgz",
+ "integrity": "sha512-JZwJuGdR7ko8L61136YzmrLv7LgTh5b8XaEM3P709mLjyQuXJ3zHTDXvUtBBahRjGlcYW0zGjIiEWizoTUGKfA==",
+ "dev": true,
+ "dependencies": {
+ "css.escape": "^1.5.1",
+ "he": "^1.2.0",
+ "iconv-lite": "^0.6.3",
+ "node-fetch": "^2.x.x",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0"
+ }
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
+ "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/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,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "dev": true,
+ "dependencies": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
+ "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.0",
+ "is-typed-array": "^1.1.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+ "dev": true,
+ "dependencies": {
+ "has-bigints": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "dev": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "dev": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "dev": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
+ "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/js-sdsl": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz",
+ "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==",
+ "dev": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-pointer": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz",
+ "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==",
+ "dependencies": {
+ "foreach": "^2.0.4"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
+ "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==",
+ "dev": true,
+ "dependencies": {
+ "array-includes": "^3.1.5",
+ "object.assign": "^4.1.3"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/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==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/natural-compare-lite": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
+ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
+ "dev": true
+ },
+ "node_modules/node-fetch": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
+ "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
+ "dev": true,
+ "dependencies": {
+ "whatwg-url": "^5.0.0"
+ },
+ "engines": {
+ "node": "4.x || >=6.0.0"
+ },
+ "peerDependencies": {
+ "encoding": "^0.1.0"
+ },
+ "peerDependenciesMeta": {
+ "encoding": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/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==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz",
+ "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
+ "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.hasown": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz",
+ "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==",
+ "dev": true,
+ "dependencies": {
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
+ "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/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,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/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==",
+ "dev": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/preact": {
+ "version": "10.15.1",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz",
+ "integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "3.0.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz",
+ "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==",
+ "dev": true,
+ "bin": {
+ "prettier": "bin/prettier.cjs"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/prettier/prettier?sponsor=1"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dev": true,
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/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,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/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,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1",
+ "scheduler": "^0.20.2"
+ },
+ "peerDependencies": {
+ "react": "17.0.2"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "dev": true
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+ "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "functions-have-names": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.2",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+ "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.11.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "2.79.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
+ "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
+ "dev": true,
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/sade": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "dev": true,
+ "dependencies": {
+ "mri": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
+ "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "is-regex": "^1.1.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "node_modules/scheduler": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz",
+ "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz",
+ "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
+ "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "regexp.prototype.flags": "^1.4.3",
+ "side-channel": "^1.0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
+ "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/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==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "node_modules/tsm": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tsm/-/tsm-2.3.0.tgz",
+ "integrity": "sha512-++0HFnmmR+gMpDtKTnW3XJ4yv9kVGi20n+NfyQWB9qwJvTaIWY9kBmzek2YUQK5APTQ/1DTrXmm4QtFPmW9Rzw==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.15.16"
+ },
+ "bin": {
+ "tsm": "bin.js"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/tsutils": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^1.8.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
+ "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "is-typed-array": "^1.1.9"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
+ "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
+ "dev": true,
+ "peer": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=12.20"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/uvu": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
+ "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==",
+ "dev": true,
+ "dependencies": {
+ "dequal": "^2.0.0",
+ "diff": "^5.0.0",
+ "kleur": "^4.0.3",
+ "sade": "^1.7.3"
+ },
+ "bin": {
+ "uvu": "bin.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/vite": {
+ "version": "3.2.8",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.8.tgz",
+ "integrity": "sha512-EtQU16PLIJpAZol2cTLttNP1mX6L0SyI0pgQB1VOoWeQnMSvtiwovV3D6NcjN8CZQWWyESD2v5NGnpz5RvgOZA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.15.9",
+ "postcss": "^8.4.18",
+ "resolve": "^1.22.1",
+ "rollup": "^2.79.1"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "dev": true,
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "dependencies": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ }
+ },
+ "node_modules/whatwg-url/node_modules/webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+ "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "dev": true,
+ "dependencies": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
+ "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
+ "dev": true,
+ "dependencies": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0",
+ "is-typed-array": "^1.1.10"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/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
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "packages/@reactpy/client": {
+ "version": "0.3.1",
+ "license": "MIT",
+ "dependencies": {
+ "event-to-object": "^0.1.2",
+ "json-pointer": "^0.6.2"
+ },
+ "devDependencies": {
+ "@types/json-pointer": "^1.0.31",
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "typescript": "^4.9.5"
+ },
+ "peerDependencies": {
+ "react": ">=16 <18",
+ "react-dom": ">=16 <18"
+ }
+ },
+ "packages/@reactpy/client/node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "packages/app": {
+ "name": "@reactpy/app",
+ "extraneous": true,
+ "license": "MIT",
+ "dependencies": {
+ "@reactpy/client": "^0.1.0",
+ "preact": "^10.7.0"
+ },
+ "devDependencies": {
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "prettier": "^3.0.0-alpha.6",
+ "typescript": "^4.9.5",
+ "vite": "^3.1.8"
+ }
+ },
+ "packages/client": {
+ "name": "@reactpy/client",
+ "version": "0.2.0",
+ "extraneous": true,
+ "license": "MIT",
+ "dependencies": {
+ "event-to-object": "^0.1.0",
+ "json-pointer": "^0.6.2"
+ },
+ "devDependencies": {
+ "@types/json-pointer": "^1.0.31",
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "prettier": "^3.0.0-alpha.6",
+ "typescript": "^4.9.5"
+ },
+ "peerDependencies": {
+ "react": ">=16 <18",
+ "react-dom": ">=16 <18"
+ }
+ },
+ "packages/event-to-object": {
+ "version": "0.1.2",
+ "license": "MIT",
+ "dependencies": {
+ "json-pointer": "^0.6.2"
+ },
+ "devDependencies": {
+ "happy-dom": "^8.9.0",
+ "lodash": "^4.17.21",
+ "tsm": "^2.0.0",
+ "typescript": "^4.9.5",
+ "uvu": "^0.5.1"
+ }
+ },
+ "packages/event-to-object/node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "packages/event-to-object/packages/event-to-object": {
+ "extraneous": true
+ },
+ "ui": {
+ "extraneous": true,
+ "license": "MIT",
+ "dependencies": {
+ "@reactpy/client": "^0.2.0",
+ "preact": "^10.7.0"
+ },
+ "devDependencies": {
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "typescript": "^4.9.5",
+ "vite": "^3.1.8"
+ }
+ }
+ },
+ "dependencies": {
+ "@esbuild/android-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
+ "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
+ "dev": true,
+ "optional": true
+ },
+ "@esbuild/linux-loong64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
+ "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
+ "dev": true,
+ "optional": true
+ },
+ "@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^3.3.0"
+ }
+ },
+ "@eslint-community/regexpp": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz",
+ "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==",
+ "dev": true
+ },
+ "@eslint/eslintrc": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz",
+ "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.5.1",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ }
+ },
+ "@eslint/js": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz",
+ "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==",
+ "dev": true
+ },
+ "@humanwhocodes/config-array": {
+ "version": "0.11.8",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
+ "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==",
+ "dev": true,
+ "requires": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ }
+ },
+ "@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true
+ },
+ "@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
+ "dev": true
+ },
+ "@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ }
+ },
+ "@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true
+ },
+ "@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ }
+ },
+ "@reactpy/client": {
+ "version": "file:packages/@reactpy/client",
+ "requires": {
+ "@types/json-pointer": "^1.0.31",
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "event-to-object": "^0.1.2",
+ "json-pointer": "^0.6.2",
+ "typescript": "^4.9.5"
+ },
+ "dependencies": {
+ "typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true
+ }
+ }
+ },
+ "@types/json-pointer": {
+ "version": "1.0.31",
+ "resolved": "https://registry.npmjs.org/@types/json-pointer/-/json-pointer-1.0.31.tgz",
+ "integrity": "sha512-hTPul7Um6LqsHXHQpdkXTU7Oysjsf+9k4Yfmg6JhSKG/jj9QuQGyMUdj6trPH6WHiIdxw7nYSROgOxeFmCVK2w==",
+ "dev": true
+ },
+ "@types/json-schema": {
+ "version": "7.0.11",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
+ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==",
+ "dev": true
+ },
+ "@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.53",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.53.tgz",
+ "integrity": "sha512-1yIpQR2zdYu1Z/dc1OxC+MA6GR240u3gcnP4l6mvj/PJiVaqHsQPmWttsvHsfnhfPbU2FuGmo0wSITPygjBmsw==",
+ "dev": true,
+ "requires": {
+ "@types/prop-types": "*",
+ "@types/scheduler": "*",
+ "csstype": "^3.0.2"
+ }
+ },
+ "@types/react-dom": {
+ "version": "17.0.19",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.19.tgz",
+ "integrity": "sha512-PiYG40pnQRdPHnlf7tZnp0aQ6q9tspYr72vD61saO6zFCybLfMqwUCN0va1/P+86DXn18ZWeW30Bk7xlC5eEAQ==",
+ "dev": true,
+ "requires": {
+ "@types/react": "^17"
+ }
+ },
+ "@types/scheduler": {
+ "version": "0.16.2",
+ "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
+ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
+ "dev": true
+ },
+ "@types/semver": {
+ "version": "7.3.13",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
+ "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
+ "dev": true
+ },
+ "@typescript-eslint/eslint-plugin": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.58.0.tgz",
+ "integrity": "sha512-vxHvLhH0qgBd3/tW6/VccptSfc8FxPQIkmNTVLWcCOVqSBvqpnKkBTYrhcGlXfSnd78azwe+PsjYFj0X34/njA==",
+ "dev": true,
+ "requires": {
+ "@eslint-community/regexpp": "^4.4.0",
+ "@typescript-eslint/scope-manager": "5.58.0",
+ "@typescript-eslint/type-utils": "5.58.0",
+ "@typescript-eslint/utils": "5.58.0",
+ "debug": "^4.3.4",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "natural-compare-lite": "^1.4.0",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ }
+ },
+ "@typescript-eslint/parser": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.58.0.tgz",
+ "integrity": "sha512-ixaM3gRtlfrKzP8N6lRhBbjTow1t6ztfBvQNGuRM8qH1bjFFXIJ35XY+FC0RRBKn3C6cT+7VW1y8tNm7DwPHDQ==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/scope-manager": "5.58.0",
+ "@typescript-eslint/types": "5.58.0",
+ "@typescript-eslint/typescript-estree": "5.58.0",
+ "debug": "^4.3.4"
+ }
+ },
+ "@typescript-eslint/scope-manager": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.58.0.tgz",
+ "integrity": "sha512-b+w8ypN5CFvrXWQb9Ow9T4/6LC2MikNf1viLkYTiTbkQl46CnR69w7lajz1icW0TBsYmlpg+mRzFJ4LEJ8X9NA==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.58.0",
+ "@typescript-eslint/visitor-keys": "5.58.0"
+ }
+ },
+ "@typescript-eslint/type-utils": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.58.0.tgz",
+ "integrity": "sha512-FF5vP/SKAFJ+LmR9PENql7fQVVgGDOS+dq3j+cKl9iW/9VuZC/8CFmzIP0DLKXfWKpRHawJiG70rVH+xZZbp8w==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/typescript-estree": "5.58.0",
+ "@typescript-eslint/utils": "5.58.0",
+ "debug": "^4.3.4",
+ "tsutils": "^3.21.0"
+ }
+ },
+ "@typescript-eslint/types": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.58.0.tgz",
+ "integrity": "sha512-JYV4eITHPzVQMnHZcYJXl2ZloC7thuUHrcUmxtzvItyKPvQ50kb9QXBkgNAt90OYMqwaodQh2kHutWZl1fc+1g==",
+ "dev": true
+ },
+ "@typescript-eslint/typescript-estree": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.58.0.tgz",
+ "integrity": "sha512-cRACvGTodA+UxnYM2uwA2KCwRL7VAzo45syNysqlMyNyjw0Z35Icc9ihPJZjIYuA5bXJYiJ2YGUB59BqlOZT1Q==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.58.0",
+ "@typescript-eslint/visitor-keys": "5.58.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ }
+ },
+ "@typescript-eslint/utils": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.58.0.tgz",
+ "integrity": "sha512-gAmLOTFXMXOC+zP1fsqm3VceKSBQJNzV385Ok3+yzlavNHZoedajjS4UyS21gabJYcobuigQPs/z71A9MdJFqQ==",
+ "dev": true,
+ "requires": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@types/json-schema": "^7.0.9",
+ "@types/semver": "^7.3.12",
+ "@typescript-eslint/scope-manager": "5.58.0",
+ "@typescript-eslint/types": "5.58.0",
+ "@typescript-eslint/typescript-estree": "5.58.0",
+ "eslint-scope": "^5.1.1",
+ "semver": "^7.3.7"
+ },
+ "dependencies": {
+ "eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true
+ }
+ }
+ },
+ "@typescript-eslint/visitor-keys": {
+ "version": "5.58.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.58.0.tgz",
+ "integrity": "sha512-/fBraTlPj0jwdyTwLyrRTxv/3lnU2H96pNTVM6z3esTWLtA5MZ9ghSMJ7Rb+TtUAdtEw9EyJzJ0EydIMKxQ9gA==",
+ "dev": true,
+ "requires": {
+ "@typescript-eslint/types": "5.58.0",
+ "eslint-visitor-keys": "^3.3.0"
+ }
+ },
+ "acorn": {
+ "version": "8.8.2",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
+ "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==",
+ "dev": true
+ },
+ "acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "requires": {}
+ },
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "app": {
+ "version": "file:app",
+ "requires": {
+ "@reactpy/client": "^0.2.0",
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "preact": "^10.7.0",
+ "typescript": "^4.9.5",
+ "vite": "^3.2.8"
+ },
+ "dependencies": {
+ "@reactpy/client": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/@reactpy/client/-/client-0.2.1.tgz",
+ "integrity": "sha512-9sgGH+pJ2BpLT+QSVe7FQLS2VQ9acHgPlO8X3qiTumGw43O0X82sm8pzya8H8dAew463SeGza/pZc0mpUBHmqA==",
+ "requires": {
+ "event-to-object": "^0.1.2",
+ "json-pointer": "^0.6.2"
+ }
+ },
+ "typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true
+ }
+ }
+ },
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "array-buffer-byte-length": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz",
+ "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "is-array-buffer": "^3.0.1"
+ }
+ },
+ "array-includes": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
+ "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "is-string": "^1.0.7"
+ }
+ },
+ "array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true
+ },
+ "array.prototype.flatmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
+ "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
+ }
+ },
+ "array.prototype.tosorted": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.1.tgz",
+ "integrity": "sha512-pZYPXPRl2PqWcsUs6LOMn+1f1532nEoPTYowBtqLwAW+W8vSVhkIGnmOX1t/UQjD6YGI0vcD2B1U7ZFGQH9jnQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0",
+ "get-intrinsic": "^1.1.3"
+ }
+ },
+ "available-typed-arrays": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
+ "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==",
+ "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": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true
+ },
+ "csstype": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
+ "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==",
+ "dev": true
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "define-properties": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz",
+ "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==",
+ "dev": true,
+ "requires": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true
+ },
+ "diff": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz",
+ "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==",
+ "dev": true
+ },
+ "dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "requires": {
+ "path-type": "^4.0.0"
+ }
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "es-abstract": {
+ "version": "1.21.2",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz",
+ "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==",
+ "dev": true,
+ "requires": {
+ "array-buffer-byte-length": "^1.0.0",
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "es-set-tostringtag": "^2.0.1",
+ "es-to-primitive": "^1.2.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.2.0",
+ "get-symbol-description": "^1.0.0",
+ "globalthis": "^1.0.3",
+ "gopd": "^1.0.1",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.5",
+ "is-array-buffer": "^3.0.2",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-typed-array": "^1.1.10",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.3",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trim": "^1.2.7",
+ "string.prototype.trimend": "^1.0.6",
+ "string.prototype.trimstart": "^1.0.6",
+ "typed-array-length": "^1.0.4",
+ "unbox-primitive": "^1.0.2",
+ "which-typed-array": "^1.1.9"
+ }
+ },
+ "es-set-tostringtag": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz",
+ "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==",
+ "dev": true,
+ "requires": {
+ "get-intrinsic": "^1.1.3",
+ "has": "^1.0.3",
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "es-shim-unscopables": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
+ "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "esbuild": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
+ "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
+ "dev": true,
+ "requires": {
+ "@esbuild/android-arm": "0.15.18",
+ "@esbuild/linux-loong64": "0.15.18",
+ "esbuild-android-64": "0.15.18",
+ "esbuild-android-arm64": "0.15.18",
+ "esbuild-darwin-64": "0.15.18",
+ "esbuild-darwin-arm64": "0.15.18",
+ "esbuild-freebsd-64": "0.15.18",
+ "esbuild-freebsd-arm64": "0.15.18",
+ "esbuild-linux-32": "0.15.18",
+ "esbuild-linux-64": "0.15.18",
+ "esbuild-linux-arm": "0.15.18",
+ "esbuild-linux-arm64": "0.15.18",
+ "esbuild-linux-mips64le": "0.15.18",
+ "esbuild-linux-ppc64le": "0.15.18",
+ "esbuild-linux-riscv64": "0.15.18",
+ "esbuild-linux-s390x": "0.15.18",
+ "esbuild-netbsd-64": "0.15.18",
+ "esbuild-openbsd-64": "0.15.18",
+ "esbuild-sunos-64": "0.15.18",
+ "esbuild-windows-32": "0.15.18",
+ "esbuild-windows-64": "0.15.18",
+ "esbuild-windows-arm64": "0.15.18"
+ }
+ },
+ "esbuild-android-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
+ "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-android-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
+ "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-darwin-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
+ "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-darwin-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
+ "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-freebsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
+ "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-freebsd-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
+ "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
+ "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
+ "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
+ "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
+ "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-mips64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
+ "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-ppc64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
+ "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-riscv64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
+ "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-linux-s390x": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
+ "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-netbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
+ "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-openbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
+ "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-sunos-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
+ "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
+ "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
+ "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
+ "dev": true,
+ "optional": true
+ },
+ "esbuild-windows-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
+ "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
+ "dev": true,
+ "optional": true
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true
+ },
+ "eslint": {
+ "version": "8.38.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz",
+ "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==",
+ "dev": true,
+ "requires": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.4.0",
+ "@eslint/eslintrc": "^2.0.2",
+ "@eslint/js": "8.38.0",
+ "@humanwhocodes/config-array": "^0.11.8",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.4.0",
+ "espree": "^9.5.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-sdsl": "^4.1.4",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0"
+ }
+ },
+ "eslint-plugin-react": {
+ "version": "7.32.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.32.2.tgz",
+ "integrity": "sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flatmap": "^1.3.1",
+ "array.prototype.tosorted": "^1.1.1",
+ "doctrine": "^2.1.0",
+ "estraverse": "^5.3.0",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.6",
+ "object.fromentries": "^2.0.6",
+ "object.hasown": "^1.1.2",
+ "object.values": "^1.1.6",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.4",
+ "semver": "^6.3.0",
+ "string.prototype.matchall": "^4.0.8"
+ },
+ "dependencies": {
+ "doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "resolve": {
+ "version": "2.0.0-next.4",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
+ "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "dev": true
+ }
+ }
+ },
+ "eslint-scope": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "dev": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz",
+ "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==",
+ "dev": true
+ },
+ "espree": {
+ "version": "9.5.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz",
+ "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==",
+ "dev": true,
+ "requires": {
+ "acorn": "^8.8.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.0"
+ }
+ },
+ "esquery": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+ "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.1.0"
+ }
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "requires": {
+ "estraverse": "^5.2.0"
+ }
+ },
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true
+ },
+ "esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true
+ },
+ "event-to-object": {
+ "version": "file:packages/event-to-object",
+ "requires": {
+ "happy-dom": "^8.9.0",
+ "json-pointer": "^0.6.2",
+ "lodash": "^4.17.21",
+ "tsm": "^2.0.0",
+ "typescript": "^4.9.5",
+ "uvu": "^0.5.1"
+ },
+ "dependencies": {
+ "typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "dev": true
+ }
+ }
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "fast-glob": {
+ "version": "3.2.12",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz",
+ "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==",
+ "dev": true,
+ "requires": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ }
+ }
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "fastq": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
+ "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "dev": true,
+ "requires": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "requires": {
+ "flat-cache": "^3.0.4"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "requires": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dev": true,
+ "requires": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ }
+ },
+ "flatted": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
+ "dev": true
+ },
+ "for-each": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
+ "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==",
+ "dev": true,
+ "requires": {
+ "is-callable": "^1.1.3"
+ }
+ },
+ "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==",
+ "dev": true
+ },
+ "function.prototype.name": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+ "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.0",
+ "functions-have-names": "^1.2.2"
+ }
+ },
+ "functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true
+ },
+ "get-intrinsic": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz",
+ "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.3"
+ }
+ },
+ "get-symbol-description": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
+ "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ }
+ },
+ "glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.3"
+ }
+ },
+ "globals": {
+ "version": "13.20.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz",
+ "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==",
+ "dev": true,
+ "requires": {
+ "type-fest": "^0.20.2"
+ }
+ },
+ "globalthis": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz",
+ "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.3"
+ }
+ },
+ "globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "requires": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ }
+ },
+ "gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dev": true,
+ "requires": {
+ "get-intrinsic": "^1.1.3"
+ }
+ },
+ "grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
+ "dev": true
+ },
+ "happy-dom": {
+ "version": "8.9.0",
+ "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-8.9.0.tgz",
+ "integrity": "sha512-JZwJuGdR7ko8L61136YzmrLv7LgTh5b8XaEM3P709mLjyQuXJ3zHTDXvUtBBahRjGlcYW0zGjIiEWizoTUGKfA==",
+ "dev": true,
+ "requires": {
+ "css.escape": "^1.5.1",
+ "he": "^1.2.0",
+ "iconv-lite": "^0.6.3",
+ "node-fetch": "^2.x.x",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^2.0.0",
+ "whatwg-mimetype": "^3.0.0"
+ }
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "dev": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "dev": true,
+ "requires": {
+ "get-intrinsic": "^1.1.1"
+ }
+ },
+ "has-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
+ "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
+ "dev": true
+ },
+ "has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "dev": true
+ },
+ "has-tostringtag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
+ "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.2"
+ }
+ },
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true
+ },
+ "iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "requires": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ }
+ },
+ "ignore": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
+ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==",
+ "dev": true
+ },
+ "import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true
+ },
+ "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
+ },
+ "internal-slot": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz",
+ "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==",
+ "dev": true,
+ "requires": {
+ "get-intrinsic": "^1.2.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "is-array-buffer": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
+ "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.2.0",
+ "is-typed-array": "^1.1.10"
+ }
+ },
+ "is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+ "dev": true,
+ "requires": {
+ "has-bigints": "^1.0.1"
+ }
+ },
+ "is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true
+ },
+ "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==",
+ "dev": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "dev": true,
+ "requires": {
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true
+ },
+ "is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-negative-zero": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+ "dev": true
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "dev": true,
+ "requires": {
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "dev": true,
+ "requires": {
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "dev": true,
+ "requires": {
+ "has-symbols": "^1.0.2"
+ }
+ },
+ "is-typed-array": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz",
+ "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==",
+ "dev": true,
+ "requires": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "js-sdsl": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz",
+ "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==",
+ "dev": true
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "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"
+ }
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "jsx-ast-utils": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
+ "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==",
+ "dev": true,
+ "requires": {
+ "array-includes": "^3.1.5",
+ "object.assign": "^4.1.3"
+ }
+ },
+ "kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "dev": true
+ },
+ "levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "requires": {
+ "p-locate": "^5.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "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==",
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true
+ },
+ "micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "requires": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ }
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "mri": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz",
+ "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "dev": true
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "natural-compare-lite": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
+ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
+ "dev": true
+ },
+ "node-fetch": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz",
+ "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==",
+ "dev": true,
+ "requires": {
+ "whatwg-url": "^5.0.0"
+ }
+ },
+ "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=="
+ },
+ "object-inspect": {
+ "version": "1.12.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
+ "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==",
+ "dev": true
+ },
+ "object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true
+ },
+ "object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "object.entries": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz",
+ "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "object.fromentries": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
+ "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "object.hasown": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz",
+ "integrity": "sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==",
+ "dev": true,
+ "requires": {
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "object.values": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
+ "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "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"
+ }
+ },
+ "optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dev": true,
+ "requires": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ }
+ },
+ "p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "requires": {
+ "yocto-queue": "^0.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "requires": {
+ "p-limit": "^3.0.2"
+ }
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true
+ },
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true
+ },
+ "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==",
+ "dev": true
+ },
+ "path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true
+ },
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ },
+ "picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true
+ },
+ "postcss": {
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+ "dev": true,
+ "requires": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "preact": {
+ "version": "10.15.1",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.15.1.tgz",
+ "integrity": "sha512-qs2ansoQEwzNiV5eAcRT1p1EC/dmEzaATVDJNiB3g2sRDWdA7b7MurXdJjB2+/WQktGWZwxvDrnuRFbWuIr64g=="
+ },
+ "prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true
+ },
+ "prettier": {
+ "version": "3.0.0-alpha.6",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0-alpha.6.tgz",
+ "integrity": "sha512-AdbQSZ6Oo+iy9Ekzmsgno05P1uX2vqPkjOMJqRfP8hTe+m6iDw4Nt7bPFpWZ/HYCU+3f0P5U0o2ghxQwwkLH7A==",
+ "dev": true
+ },
+ "prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dev": true,
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "punycode": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz",
+ "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==",
+ "dev": true
+ },
+ "queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "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"
+ }
+ },
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "dev": true
+ },
+ "regexp.prototype.flags": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+ "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "functions-have-names": "^1.2.2"
+ }
+ },
+ "resolve": {
+ "version": "1.22.2",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz",
+ "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==",
+ "dev": true,
+ "requires": {
+ "is-core-module": "^2.11.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true
+ },
+ "reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "rollup": {
+ "version": "2.79.1",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
+ "integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
+ "dev": true,
+ "requires": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "requires": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "sade": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz",
+ "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==",
+ "dev": true,
+ "requires": {
+ "mri": "^1.1.0"
+ }
+ },
+ "safe-regex-test": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
+ "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "is-regex": "^1.1.4"
+ }
+ },
+ "safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true
+ },
+ "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"
+ }
+ },
+ "semver": {
+ "version": "7.4.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.4.0.tgz",
+ "integrity": "sha512-RgOxM8Mw+7Zus0+zcLEUn8+JfoLpj/huFTItQy2hsM4khuC1HYRDp0cU482Ewn/Fcy6bCjufD8vAj7voC66KQw==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true
+ },
+ "side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ }
+ },
+ "slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true
+ },
+ "source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "dev": true
+ },
+ "string.prototype.matchall": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
+ "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "regexp.prototype.flags": "^1.4.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "string.prototype.trim": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz",
+ "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ },
+ "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==",
+ "dev": true
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "tr46": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+ "dev": true
+ },
+ "tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "tsm": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tsm/-/tsm-2.3.0.tgz",
+ "integrity": "sha512-++0HFnmmR+gMpDtKTnW3XJ4yv9kVGi20n+NfyQWB9qwJvTaIWY9kBmzek2YUQK5APTQ/1DTrXmm4QtFPmW9Rzw==",
+ "dev": true,
+ "requires": {
+ "esbuild": "^0.15.16"
+ }
+ },
+ "tsutils": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+ "dev": true,
+ "requires": {
+ "tslib": "^1.8.1"
+ }
+ },
+ "type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "requires": {
+ "prelude-ls": "^1.2.1"
+ }
+ },
+ "type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true
+ },
+ "typed-array-length": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz",
+ "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "is-typed-array": "^1.1.9"
+ }
+ },
+ "typescript": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
+ "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
+ "dev": true,
+ "peer": true
+ },
+ "unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "dev": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ }
+ },
+ "uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "uvu": {
+ "version": "0.5.6",
+ "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz",
+ "integrity": "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==",
+ "dev": true,
+ "requires": {
+ "dequal": "^2.0.0",
+ "diff": "^5.0.0",
+ "kleur": "^4.0.3",
+ "sade": "^1.7.3"
+ }
+ },
+ "vite": {
+ "version": "3.2.8",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.8.tgz",
+ "integrity": "sha512-EtQU16PLIJpAZol2cTLttNP1mX6L0SyI0pgQB1VOoWeQnMSvtiwovV3D6NcjN8CZQWWyESD2v5NGnpz5RvgOZA==",
+ "dev": true,
+ "requires": {
+ "esbuild": "^0.15.9",
+ "fsevents": "~2.3.2",
+ "postcss": "^8.4.18",
+ "resolve": "^1.22.1",
+ "rollup": "^2.79.1"
+ }
+ },
+ "webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true
+ },
+ "whatwg-encoding": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz",
+ "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==",
+ "dev": true,
+ "requires": {
+ "iconv-lite": "0.6.3"
+ }
+ },
+ "whatwg-mimetype": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz",
+ "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==",
+ "dev": true
+ },
+ "whatwg-url": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+ "dev": true,
+ "requires": {
+ "tr46": "~0.0.3",
+ "webidl-conversions": "^3.0.0"
+ },
+ "dependencies": {
+ "webidl-conversions": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+ "dev": true
+ }
+ }
+ },
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "which-boxed-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+ "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "dev": true,
+ "requires": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ }
+ },
+ "which-typed-array": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz",
+ "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==",
+ "dev": true,
+ "requires": {
+ "available-typed-arrays": "^1.0.5",
+ "call-bind": "^1.0.2",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "has-tostringtag": "^1.0.0",
+ "is-typed-array": "^1.1.10"
+ }
+ },
+ "word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true
+ },
+ "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
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true
+ }
+ }
+}
diff --git a/src/js/package.json b/src/js/package.json
new file mode 100644
index 000000000..a9d84814b
--- /dev/null
+++ b/src/js/package.json
@@ -0,0 +1,26 @@
+{
+ "license": "MIT",
+ "scripts": {
+ "publish": "npm --workspaces publish",
+ "test": "npm --workspaces test",
+ "build": "npm --workspaces run build",
+ "fix:format": "npm run prettier -- --write && npm run eslint -- --fix",
+ "check:format": "npm run prettier -- --check && npm run eslint",
+ "check:tests": "npm --workspaces run check:tests",
+ "check:types": "npm --workspaces run check:types",
+ "prettier": "prettier --ignore-path .gitignore .",
+ "eslint": "eslint --ignore-path .gitignore ."
+ },
+ "workspaces": [
+ "packages/event-to-object",
+ "packages/@reactpy/client",
+ "app"
+ ],
+ "devDependencies": {
+ "@typescript-eslint/eslint-plugin": "^5.58.0",
+ "@typescript-eslint/parser": "^5.58.0",
+ "eslint": "^8.38.0",
+ "eslint-plugin-react": "^7.32.2",
+ "prettier": "^3.0.0-alpha.6"
+ }
+}
diff --git a/src/client/packages/idom-client-react/.gitignore b/src/js/packages/@reactpy/client/.gitignore
similarity index 100%
rename from src/client/packages/idom-client-react/.gitignore
rename to src/js/packages/@reactpy/client/.gitignore
diff --git a/src/js/packages/@reactpy/client/README.md b/src/js/packages/@reactpy/client/README.md
new file mode 100644
index 000000000..a01929943
--- /dev/null
+++ b/src/js/packages/@reactpy/client/README.md
@@ -0,0 +1,3 @@
+# @reactpy/client
+
+A client for ReactPy implemented in React
diff --git a/src/js/packages/@reactpy/client/package.json b/src/js/packages/@reactpy/client/package.json
new file mode 100644
index 000000000..ab4bd34ad
--- /dev/null
+++ b/src/js/packages/@reactpy/client/package.json
@@ -0,0 +1,34 @@
+{
+ "author": "Ryan Morshead",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "description": "A client for ReactPy implemented in React",
+ "license": "MIT",
+ "name": "@reactpy/client",
+ "type": "module",
+ "version": "0.3.1",
+ "dependencies": {
+ "event-to-object": "^0.1.2",
+ "json-pointer": "^0.6.2"
+ },
+ "devDependencies": {
+ "@types/json-pointer": "^1.0.31",
+ "@types/react": "^17.0",
+ "@types/react-dom": "^17.0",
+ "typescript": "^4.9.5"
+ },
+ "peerDependencies": {
+ "react": ">=16 <18",
+ "react-dom": ">=16 <18"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/reactive-python/reactpy"
+ },
+ "scripts": {
+ "build": "tsc -b",
+ "test": "npm run check:tests",
+ "check:tests": "echo 'no tests'",
+ "check:types": "tsc --noEmit"
+ }
+}
diff --git a/src/js/packages/@reactpy/client/src/components.tsx b/src/js/packages/@reactpy/client/src/components.tsx
new file mode 100644
index 000000000..728c4cec7
--- /dev/null
+++ b/src/js/packages/@reactpy/client/src/components.tsx
@@ -0,0 +1,227 @@
+import React, {
+ createElement,
+ createContext,
+ useState,
+ useRef,
+ useContext,
+ useEffect,
+ Fragment,
+ MutableRefObject,
+ ChangeEvent,
+} from "react";
+// @ts-ignore
+import { set as setJsonPointer } from "json-pointer";
+import {
+ ReactPyVdom,
+ ReactPyComponent,
+ createChildren,
+ createAttributes,
+ loadImportSource,
+ ImportSourceBinding,
+} from "./reactpy-vdom";
+import { ReactPyClient } from "./reactpy-client";
+
+const ClientContext = createContext(null as any);
+
+export function Layout(props: { client: ReactPyClient }): JSX.Element {
+ const currentModel: ReactPyVdom = useState({ tagName: "" })[0];
+ const forceUpdate = useForceUpdate();
+
+ useEffect(
+ () =>
+ props.client.onMessage("layout-update", ({ path, model }) => {
+ if (path === "") {
+ Object.assign(currentModel, model);
+ } else {
+ setJsonPointer(currentModel, path, model);
+ }
+ forceUpdate();
+ }),
+ [currentModel, props.client],
+ );
+
+ return (
+
+
+
+ );
+}
+
+export function Element({ model }: { model: ReactPyVdom }): JSX.Element | null {
+ if (model.error !== undefined) {
+ if (model.error) {
+ return {model.error} ;
+ } else {
+ return null;
+ }
+ }
+
+ let SpecializedElement: ReactPyComponent;
+ if (model.tagName in SPECIAL_ELEMENTS) {
+ SpecializedElement =
+ SPECIAL_ELEMENTS[model.tagName as keyof typeof SPECIAL_ELEMENTS];
+ } else if (model.importSource) {
+ SpecializedElement = ImportedElement;
+ } else {
+ SpecializedElement = StandardElement;
+ }
+
+ return ;
+}
+
+function StandardElement({ model }: { model: ReactPyVdom }) {
+ const client = React.useContext(ClientContext);
+ // Use createElement here to avoid warning about variable numbers of children not
+ // having keys. Warning about this must now be the responsibility of the client
+ // providing the models instead of the client rendering them.
+ return createElement(
+ model.tagName === "" ? Fragment : model.tagName,
+ createAttributes(model, client),
+ ...createChildren(model, (child) => {
+ return ;
+ }),
+ );
+}
+
+function UserInputElement({ model }: { model: ReactPyVdom }): JSX.Element {
+ const client = useContext(ClientContext);
+ const props = createAttributes(model, client);
+ const [value, setValue] = React.useState(props.value);
+
+ // honor changes to value from the client via props
+ React.useEffect(() => setValue(props.value), [props.value]);
+
+ const givenOnChange = props.onChange;
+ if (typeof givenOnChange === "function") {
+ props.onChange = (event: ChangeEvent) => {
+ // immediately update the value to give the user feedback
+ setValue(event.target.value);
+ // allow the client to respond (and possibly change the value)
+ givenOnChange(event);
+ };
+ }
+
+ // Use createElement here to avoid warning about variable numbers of children not
+ // having keys. Warning about this must now be the responsibility of the client
+ // providing the models instead of the client rendering them.
+ return createElement(
+ model.tagName,
+ // overwrite
+ { ...props, value },
+ ...createChildren(model, (child) => (
+
+ )),
+ );
+}
+
+function ScriptElement({ model }: { model: ReactPyVdom }) {
+ const ref = useRef(null);
+
+ React.useEffect(() => {
+ if (!ref.current) {
+ return;
+ }
+ const scriptContent = model?.children?.filter(
+ (value): value is string => typeof value == "string",
+ )[0];
+
+ let scriptElement: HTMLScriptElement;
+ if (model.attributes) {
+ scriptElement = document.createElement("script");
+ for (const [k, v] of Object.entries(model.attributes)) {
+ scriptElement.setAttribute(k, v);
+ }
+ if (scriptContent) {
+ scriptElement.appendChild(document.createTextNode(scriptContent));
+ }
+ ref.current.appendChild(scriptElement);
+ } else if (scriptContent) {
+ const scriptResult = eval(scriptContent);
+ if (typeof scriptResult == "function") {
+ return scriptResult();
+ }
+ }
+ }, [model.key, ref.current]);
+
+ return
;
+}
+
+function ImportedElement({ model }: { model: ReactPyVdom }) {
+ const importSourceVdom = model.importSource;
+ const importSourceRef = useImportSource(model);
+
+ if (!importSourceVdom) {
+ return null;
+ }
+
+ const importSourceFallback = importSourceVdom.fallback;
+
+ if (!importSourceVdom) {
+ // display a fallback if one was given
+ if (!importSourceFallback) {
+ return null;
+ } else if (typeof importSourceFallback === "string") {
+ return {importSourceFallback} ;
+ } else {
+ return ;
+ }
+ } else {
+ return ;
+ }
+}
+
+function useForceUpdate() {
+ const [, setState] = useState(false);
+ return () => setState((old) => !old);
+}
+
+function useImportSource(model: ReactPyVdom): MutableRefObject {
+ const vdomImportSource = model.importSource;
+
+ const mountPoint = useRef(null);
+ const client = React.useContext(ClientContext);
+ const [binding, setBinding] = useState(null);
+
+ React.useEffect(() => {
+ let unmounted = false;
+
+ if (vdomImportSource) {
+ loadImportSource(vdomImportSource, client).then((bind) => {
+ if (!unmounted && mountPoint.current) {
+ setBinding(bind(mountPoint.current));
+ }
+ });
+ }
+
+ return () => {
+ unmounted = true;
+ if (
+ binding &&
+ vdomImportSource &&
+ !vdomImportSource.unmountBeforeUpdate
+ ) {
+ binding.unmount();
+ }
+ };
+ }, [client, vdomImportSource, setBinding, mountPoint.current]);
+
+ // this effect must run every time in case the model has changed
+ useEffect(() => {
+ if (!(binding && vdomImportSource)) {
+ return;
+ }
+ binding.render(model);
+ if (vdomImportSource.unmountBeforeUpdate) {
+ return binding.unmount;
+ }
+ });
+
+ return mountPoint;
+}
+
+const SPECIAL_ELEMENTS = {
+ input: UserInputElement,
+ script: ScriptElement,
+ select: UserInputElement,
+ textarea: UserInputElement,
+};
diff --git a/src/js/packages/@reactpy/client/src/index.ts b/src/js/packages/@reactpy/client/src/index.ts
new file mode 100644
index 000000000..548fcbfc7
--- /dev/null
+++ b/src/js/packages/@reactpy/client/src/index.ts
@@ -0,0 +1,5 @@
+export * from "./components";
+export * from "./messages";
+export * from "./mount";
+export * from "./reactpy-client";
+export * from "./reactpy-vdom";
diff --git a/src/js/packages/@reactpy/client/src/logger.ts b/src/js/packages/@reactpy/client/src/logger.ts
new file mode 100644
index 000000000..4c4cdd264
--- /dev/null
+++ b/src/js/packages/@reactpy/client/src/logger.ts
@@ -0,0 +1,5 @@
+export default {
+ log: (...args: any[]): void => console.log("[ReactPy]", ...args),
+ warn: (...args: any[]): void => console.warn("[ReactPy]", ...args),
+ error: (...args: any[]): void => console.error("[ReactPy]", ...args),
+};
diff --git a/src/js/packages/@reactpy/client/src/messages.ts b/src/js/packages/@reactpy/client/src/messages.ts
new file mode 100644
index 000000000..34001dcb0
--- /dev/null
+++ b/src/js/packages/@reactpy/client/src/messages.ts
@@ -0,0 +1,17 @@
+import { ReactPyVdom } from "./reactpy-vdom";
+
+export type LayoutUpdateMessage = {
+ type: "layout-update";
+ path: string;
+ model: ReactPyVdom;
+};
+
+export type LayoutEventMessage = {
+ type: "layout-event";
+ target: string;
+ data: any;
+};
+
+export type IncomingMessage = LayoutUpdateMessage;
+export type OutgoingMessage = LayoutEventMessage;
+export type Message = IncomingMessage | OutgoingMessage;
diff --git a/src/js/packages/@reactpy/client/src/mount.tsx b/src/js/packages/@reactpy/client/src/mount.tsx
new file mode 100644
index 000000000..0b824a4ee
--- /dev/null
+++ b/src/js/packages/@reactpy/client/src/mount.tsx
@@ -0,0 +1,8 @@
+import React from "react";
+import { render } from "react-dom";
+import { Layout } from "./components";
+import { ReactPyClient } from "./reactpy-client";
+
+export function mount(element: HTMLElement, client: ReactPyClient): void {
+ render( , element);
+}
diff --git a/src/js/packages/@reactpy/client/src/reactpy-client.ts b/src/js/packages/@reactpy/client/src/reactpy-client.ts
new file mode 100644
index 000000000..6f37b55a1
--- /dev/null
+++ b/src/js/packages/@reactpy/client/src/reactpy-client.ts
@@ -0,0 +1,264 @@
+import { ReactPyModule } from "./reactpy-vdom";
+import logger from "./logger";
+
+/**
+ * A client for communicating with a ReactPy server.
+ */
+export interface ReactPyClient {
+ /**
+ * Register a handler for a message type.
+ *
+ * The first time this is called, the client will be considered ready.
+ *
+ * @param type The type of message to handle.
+ * @param handler The handler to call when a message of the given type is received.
+ * @returns A function to unregister the handler.
+ */
+ onMessage(type: string, handler: (message: any) => void): () => void;
+
+ /**
+ * Send a message to the server.
+ *
+ * @param message The message to send. Messages must have a `type` property.
+ */
+ sendMessage(message: any): void;
+
+ /**
+ * Load a module from the server.
+ * @param moduleName The name of the module to load.
+ * @returns A promise that resolves to the module.
+ */
+ loadModule(moduleName: string): Promise;
+}
+
+export abstract class BaseReactPyClient implements ReactPyClient {
+ private readonly handlers: { [key: string]: ((message: any) => void)[] } = {};
+ protected readonly ready: Promise;
+ private resolveReady: (value: undefined) => void;
+
+ constructor() {
+ this.resolveReady = () => {};
+ this.ready = new Promise((resolve) => (this.resolveReady = resolve));
+ }
+
+ onMessage(type: string, handler: (message: any) => void): () => void {
+ (this.handlers[type] || (this.handlers[type] = [])).push(handler);
+ this.resolveReady(undefined);
+ return () => {
+ this.handlers[type] = this.handlers[type].filter((h) => h !== handler);
+ };
+ }
+
+ abstract sendMessage(message: any): void;
+ abstract loadModule(moduleName: string): Promise;
+
+ /**
+ * Handle an incoming message.
+ *
+ * This should be called by subclasses when a message is received.
+ *
+ * @param message The message to handle. The message must have a `type` property.
+ */
+ protected handleIncoming(message: any): void {
+ if (!message.type) {
+ logger.warn("Received message without type", message);
+ return;
+ }
+
+ const messageHandlers: ((m: any) => void)[] | undefined =
+ this.handlers[message.type];
+ if (!messageHandlers) {
+ logger.warn("Received message without handler", message);
+ return;
+ }
+
+ messageHandlers.forEach((h) => h(message));
+ }
+}
+
+export type SimpleReactPyClientProps = {
+ serverLocation?: LocationProps;
+ reconnectOptions?: ReconnectProps;
+};
+
+/**
+ * The location of the server.
+ *
+ * This is used to determine the location of the server's API endpoints. All endpoints
+ * are expected to be found at the base URL, with the following paths:
+ *
+ * - `_reactpy/stream/${route}${query}`: The websocket endpoint for the stream.
+ * - `_reactpy/modules`: The directory containing the dynamically loaded modules.
+ * - `_reactpy/assets`: The directory containing the static assets.
+ */
+type LocationProps = {
+ /**
+ * The base URL of the server.
+ *
+ * @default - document.location.origin
+ */
+ url: string;
+ /**
+ * The route to the page being rendered.
+ *
+ * @default - document.location.pathname
+ */
+ route: string;
+ /**
+ * The query string of the page being rendered.
+ *
+ * @default - document.location.search
+ */
+ query: string;
+};
+
+type ReconnectProps = {
+ maxInterval?: number;
+ maxRetries?: number;
+ backoffRate?: number;
+ intervalJitter?: number;
+};
+
+export class SimpleReactPyClient
+ extends BaseReactPyClient
+ implements ReactPyClient
+{
+ private readonly urls: ServerUrls;
+ private readonly socket: { current?: WebSocket };
+
+ constructor(props: SimpleReactPyClientProps) {
+ super();
+
+ this.urls = getServerUrls(
+ props.serverLocation || {
+ url: document.location.origin,
+ route: document.location.pathname,
+ query: document.location.search,
+ },
+ );
+
+ this.socket = createReconnectingWebSocket({
+ readyPromise: this.ready,
+ url: this.urls.stream,
+ onMessage: async ({ data }) => this.handleIncoming(JSON.parse(data)),
+ ...props.reconnectOptions,
+ });
+ }
+
+ sendMessage(message: any): void {
+ this.socket.current?.send(JSON.stringify(message));
+ }
+
+ loadModule(moduleName: string): Promise {
+ return import(`${this.urls.modules}/${moduleName}`);
+ }
+}
+
+type ServerUrls = {
+ base: URL;
+ stream: string;
+ modules: string;
+ assets: string;
+};
+
+function getServerUrls(props: LocationProps): ServerUrls {
+ const base = new URL(`${props.url || document.location.origin}/_reactpy`);
+ const modules = `${base}/modules`;
+ const assets = `${base}/assets`;
+
+ const streamProtocol = `ws${base.protocol === "https:" ? "s" : ""}`;
+ const streamPath = rtrim(`${base.pathname}/stream${props.route || ""}`, "/");
+ const stream = `${streamProtocol}://${base.host}${streamPath}${props.query}`;
+
+ return { base, modules, assets, stream };
+}
+
+function createReconnectingWebSocket(
+ props: {
+ url: string;
+ readyPromise: Promise;
+ onOpen?: () => void;
+ onMessage: (message: MessageEvent) => void;
+ onClose?: () => void;
+ } & ReconnectProps,
+) {
+ const {
+ maxInterval = 60000,
+ maxRetries = 50,
+ backoffRate = 1.1,
+ intervalJitter = 0.1,
+ } = props;
+
+ const startInterval = 750;
+ let retries = 0;
+ let interval = startInterval;
+ const closed = false;
+ let everConnected = false;
+ const socket: { current?: WebSocket } = {};
+
+ const connect = () => {
+ if (closed) {
+ return;
+ }
+ socket.current = new WebSocket(props.url);
+ socket.current.onopen = () => {
+ everConnected = true;
+ logger.log("client connected");
+ interval = startInterval;
+ retries = 0;
+ if (props.onOpen) {
+ props.onOpen();
+ }
+ };
+ socket.current.onmessage = props.onMessage;
+ socket.current.onclose = () => {
+ if (!everConnected) {
+ logger.log("failed to connect");
+ return;
+ }
+
+ logger.log("client disconnected");
+ if (props.onClose) {
+ props.onClose();
+ }
+
+ if (retries >= maxRetries) {
+ return;
+ }
+
+ const thisInterval = addJitter(interval, intervalJitter);
+ logger.log(
+ `reconnecting in ${(thisInterval / 1000).toPrecision(4)} seconds...`,
+ );
+ setTimeout(connect, thisInterval);
+ interval = nextInterval(interval, backoffRate, maxInterval);
+ retries++;
+ };
+ };
+
+ props.readyPromise.then(() => logger.log("starting client...")).then(connect);
+
+ return socket;
+}
+
+function nextInterval(
+ currentInterval: number,
+ backoffRate: number,
+ maxInterval: number,
+): number {
+ return Math.min(
+ currentInterval *
+ // increase interval by backoff rate
+ backoffRate,
+ // don't exceed max interval
+ maxInterval,
+ );
+}
+
+function addJitter(interval: number, jitter: number): number {
+ return interval + (Math.random() * jitter * interval * 2 - jitter * interval);
+}
+
+function rtrim(text: string, trim: string): string {
+ return text.replace(new RegExp(`${trim}+$`), "");
+}
diff --git a/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
new file mode 100644
index 000000000..22fa3e61d
--- /dev/null
+++ b/src/js/packages/@reactpy/client/src/reactpy-vdom.tsx
@@ -0,0 +1,261 @@
+import React, { ComponentType } from "react";
+import { ReactPyClient } from "./reactpy-client";
+import serializeEvent from "event-to-object";
+
+export async function loadImportSource(
+ vdomImportSource: ReactPyVdomImportSource,
+ client: ReactPyClient,
+): Promise {
+ let module: ReactPyModule;
+ if (vdomImportSource.sourceType === "URL") {
+ module = await import(vdomImportSource.source);
+ } else {
+ module = await client.loadModule(vdomImportSource.source);
+ }
+ if (typeof module.bind !== "function") {
+ throw new Error(
+ `${vdomImportSource.source} did not export a function 'bind'`,
+ );
+ }
+
+ return (node: HTMLElement) => {
+ const binding = module.bind(node, {
+ sendMessage: client.sendMessage,
+ onMessage: client.onMessage,
+ });
+ if (
+ !(
+ typeof binding.create === "function" &&
+ typeof binding.render === "function" &&
+ typeof binding.unmount === "function"
+ )
+ ) {
+ console.error(`${vdomImportSource.source} returned an impropper binding`);
+ return null;
+ }
+
+ return {
+ render: (model) =>
+ binding.render(
+ createImportSourceElement({
+ client,
+ module,
+ binding,
+ model,
+ currentImportSource: vdomImportSource,
+ }),
+ ),
+ unmount: binding.unmount,
+ };
+ };
+}
+
+function createImportSourceElement(props: {
+ client: ReactPyClient;
+ module: ReactPyModule;
+ binding: ReactPyModuleBinding;
+ model: ReactPyVdom;
+ currentImportSource: ReactPyVdomImportSource;
+}): any {
+ let type: any;
+ if (props.model.importSource) {
+ if (
+ !isImportSourceEqual(props.currentImportSource, props.model.importSource)
+ ) {
+ console.error(
+ "Parent element import source " +
+ stringifyImportSource(props.currentImportSource) +
+ " does not match child's import source " +
+ stringifyImportSource(props.model.importSource),
+ );
+ return null;
+ } else if (!props.module[props.model.tagName]) {
+ console.error(
+ "Module from source " +
+ stringifyImportSource(props.currentImportSource) +
+ ` does not export ${props.model.tagName}`,
+ );
+ return null;
+ } else {
+ type = props.module[props.model.tagName];
+ }
+ } else {
+ type = props.model.tagName;
+ }
+ return props.binding.create(
+ type,
+ createAttributes(props.model, props.client),
+ createChildren(props.model, (child) =>
+ createImportSourceElement({
+ ...props,
+ model: child,
+ }),
+ ),
+ );
+}
+
+function isImportSourceEqual(
+ source1: ReactPyVdomImportSource,
+ source2: ReactPyVdomImportSource,
+) {
+ return (
+ source1.source === source2.source &&
+ source1.sourceType === source2.sourceType
+ );
+}
+
+function stringifyImportSource(importSource: ReactPyVdomImportSource) {
+ return JSON.stringify({
+ source: importSource.source,
+ sourceType: importSource.sourceType,
+ });
+}
+
+export function createChildren(
+ model: ReactPyVdom,
+ createChild: (child: ReactPyVdom) => Child,
+): (Child | string)[] {
+ if (!model.children) {
+ return [];
+ } else {
+ return model.children.map((child) => {
+ switch (typeof child) {
+ case "object":
+ return createChild(child);
+ case "string":
+ return child;
+ }
+ });
+ }
+}
+
+export function createAttributes(
+ model: ReactPyVdom,
+ client: ReactPyClient,
+): { [key: string]: any } {
+ return Object.fromEntries(
+ Object.entries({
+ // Normal HTML attributes
+ ...model.attributes,
+ // Construct event handlers
+ ...Object.fromEntries(
+ Object.entries(model.eventHandlers || {}).map(([name, handler]) =>
+ createEventHandler(client, name, handler),
+ ),
+ ),
+ // Convert snake_case to camelCase names
+ }).map(normalizeAttribute),
+ );
+}
+
+function createEventHandler(
+ client: ReactPyClient,
+ name: string,
+ { target, preventDefault, stopPropagation }: ReactPyVdomEventHandler,
+): [string, () => void] {
+ return [
+ name,
+ function (...args: any[]) {
+ const data = Array.from(args).map((value) => {
+ if (!(typeof value === "object" && value.nativeEvent)) {
+ return value;
+ }
+ const event = value as React.SyntheticEvent;
+ if (preventDefault) {
+ event.preventDefault();
+ }
+ if (stopPropagation) {
+ event.stopPropagation();
+ }
+ return serializeEvent(event.nativeEvent);
+ });
+ client.sendMessage({ type: "layout-event", data, target });
+ },
+ ];
+}
+
+function normalizeAttribute([key, value]: [string, any]): [string, any] {
+ let normKey = key;
+ let normValue = value;
+
+ if (key === "style" && typeof value === "object") {
+ normValue = Object.fromEntries(
+ Object.entries(value).map(([k, v]) => [snakeToCamel(k), v]),
+ );
+ } else if (
+ key.startsWith("data_") ||
+ key.startsWith("aria_") ||
+ DASHED_HTML_ATTRS.includes(key)
+ ) {
+ normKey = key.split("_").join("-");
+ } else {
+ normKey = snakeToCamel(key);
+ }
+ return [normKey, normValue];
+}
+
+function snakeToCamel(str: string): string {
+ return str.replace(/([_][a-z])/g, (group) =>
+ group.toUpperCase().replace("_", ""),
+ );
+}
+
+// see list of HTML attributes with dashes in them:
+// https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes#attribute_list
+const DASHED_HTML_ATTRS = ["accept_charset", "http_equiv"];
+
+export type ReactPyComponent = ComponentType<{ model: ReactPyVdom }>;
+
+export type ReactPyVdom = {
+ tagName: string;
+ key?: string;
+ attributes?: { [key: string]: string };
+ children?: (ReactPyVdom | string)[];
+ error?: string;
+ eventHandlers?: { [key: string]: ReactPyVdomEventHandler };
+ importSource?: ReactPyVdomImportSource;
+};
+
+export type ReactPyVdomEventHandler = {
+ target: string;
+ preventDefault?: boolean;
+ stopPropagation?: boolean;
+};
+
+export type ReactPyVdomImportSource = {
+ source: string;
+ sourceType?: "URL" | "NAME";
+ fallback?: string | ReactPyVdom;
+ unmountBeforeUpdate?: boolean;
+};
+
+export type ReactPyModule = {
+ bind: (
+ node: HTMLElement,
+ context: ReactPyModuleBindingContext,
+ ) => ReactPyModuleBinding;
+} & { [key: string]: any };
+
+export type ReactPyModuleBindingContext = {
+ sendMessage: ReactPyClient["sendMessage"];
+ onMessage: ReactPyClient["onMessage"];
+};
+
+export type ReactPyModuleBinding = {
+ create: (
+ type: any,
+ props?: any,
+ children?: (any | string | ReactPyVdom)[],
+ ) => any;
+ render: (element: any) => void;
+ unmount: () => void;
+};
+
+export type BindImportSource = (
+ node: HTMLElement,
+) => ImportSourceBinding | null;
+
+export type ImportSourceBinding = {
+ render: (model: ReactPyVdom) => void;
+ unmount: () => void;
+};
diff --git a/src/js/packages/@reactpy/client/tsconfig.json b/src/js/packages/@reactpy/client/tsconfig.json
new file mode 100644
index 000000000..2e1483e10
--- /dev/null
+++ b/src/js/packages/@reactpy/client/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../../tsconfig.package.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "composite": true
+ },
+ "include": ["src"],
+ "references": [
+ {
+ "path": "../../event-to-object"
+ }
+ ]
+}
diff --git a/src/js/packages/event-to-object/package.json b/src/js/packages/event-to-object/package.json
new file mode 100644
index 000000000..eaeb99343
--- /dev/null
+++ b/src/js/packages/event-to-object/package.json
@@ -0,0 +1,30 @@
+{
+ "author": "Ryan Morshead",
+ "license": "MIT",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "name": "event-to-object",
+ "description": "Convert native events to JSON serializable objects",
+ "type": "module",
+ "version": "0.1.2",
+ "dependencies": {
+ "json-pointer": "^0.6.2"
+ },
+ "devDependencies": {
+ "happy-dom": "^8.9.0",
+ "lodash": "^4.17.21",
+ "tsm": "^2.0.0",
+ "typescript": "^4.9.5",
+ "uvu": "^0.5.1"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/reactive-python/reactpy"
+ },
+ "scripts": {
+ "build": "tsc -b",
+ "test": "npm run check:tests",
+ "check:tests": "uvu -r tsm tests",
+ "check:types": "tsc --noEmit"
+ }
+}
diff --git a/src/js/packages/event-to-object/src/events.ts b/src/js/packages/event-to-object/src/events.ts
new file mode 100644
index 000000000..cef37ff09
--- /dev/null
+++ b/src/js/packages/event-to-object/src/events.ts
@@ -0,0 +1,258 @@
+// TODO
+type FileListObject = any;
+type DataTransferItemListObject = any;
+
+export type EventToObjectMap = {
+ event: [Event, EventObject];
+ animation: [AnimationEvent, AnimationEventObject];
+ clipboard: [ClipboardEvent, ClipboardEventObject];
+ composition: [CompositionEvent, CompositionEventObject];
+ devicemotion: [DeviceMotionEvent, DeviceMotionEventObject];
+ deviceorientation: [DeviceOrientationEvent, DeviceOrientationEventObject];
+ drag: [DragEvent, DragEventObject];
+ focus: [FocusEvent, FocusEventObject];
+ formdata: [FormDataEvent, FormDataEventObject];
+ gamepad: [GamepadEvent, GamepadEventObject];
+ input: [InputEvent, InputEventObject];
+ keyboard: [KeyboardEvent, KeyboardEventObject];
+ mouse: [MouseEvent, MouseEventObject];
+ pointer: [PointerEvent, PointerEventObject];
+ submit: [SubmitEvent, SubmitEventObject];
+ touch: [TouchEvent, TouchEventObject];
+ transition: [TransitionEvent, TransitionEventObject];
+ ui: [UIEvent, UIEventObject];
+ wheel: [WheelEvent, WheelEventObject];
+};
+
+export interface EventObject {
+ bubbles: boolean;
+ composed: boolean;
+ currentTarget: ElementObject | null;
+ defaultPrevented: boolean;
+ eventPhase: number;
+ isTrusted: boolean;
+ target: ElementObject | null;
+ timeStamp: DOMHighResTimeStamp;
+ type: string;
+ selection: SelectionObject | null;
+}
+
+export interface SubmitEventObject extends EventObject {
+ submitter: ElementObject;
+}
+
+export interface InputEventObject extends UIEventObject {
+ data: string | null;
+ dataTransfer: DataTransferObject | null;
+ isComposing: boolean;
+ inputType: string;
+}
+
+export interface GamepadEventObject extends EventObject {
+ gamepad: GamepadObject;
+}
+
+export interface GamepadObject {
+ axes: number[];
+ buttons: GamepadButtonObject[];
+ connected: boolean;
+ hapticActuators: GamepadHapticActuatorObject[];
+ id: string;
+ index: number;
+ mapping: GamepadMappingType;
+ timestamp: DOMHighResTimeStamp;
+}
+
+export interface GamepadButtonObject {
+ pressed: boolean;
+ touched: boolean;
+ value: number;
+}
+export interface GamepadHapticActuatorObject {
+ type: string;
+}
+
+export interface DragEventObject extends MouseEventObject {
+ /** Returns the DataTransfer object for the event. */
+ readonly dataTransfer: DataTransferObject | null;
+}
+
+export interface DeviceMotionEventObject extends EventObject {
+ acceleration: DeviceAccelerationObject | null;
+ accelerationIncludingGravity: DeviceAccelerationObject | null;
+ interval: number;
+ rotationRate: DeviceRotationRateObject | null;
+}
+
+export interface DeviceAccelerationObject {
+ x: number | null;
+ y: number | null;
+ z: number | null;
+}
+
+export interface DeviceRotationRateObject {
+ alpha: number | null;
+ beta: number | null;
+ gamma: number | null;
+}
+
+export interface DeviceOrientationEventObject extends EventObject {
+ absolute: boolean;
+ alpha: number | null;
+ beta: number | null;
+ gamma: number | null;
+}
+
+export interface MouseEventObject extends EventObject {
+ altKey: boolean;
+ button: number;
+ buttons: number;
+ clientX: number;
+ clientY: number;
+ ctrlKey: boolean;
+ metaKey: boolean;
+ movementX: number;
+ movementY: number;
+ offsetX: number;
+ offsetY: number;
+ pageX: number;
+ pageY: number;
+ relatedTarget: ElementObject | null;
+ screenX: number;
+ screenY: number;
+ shiftKey: boolean;
+ x: number;
+ y: number;
+}
+
+export interface FormDataEventObject extends EventObject {
+ formData: FormDataObject;
+}
+
+export type FormDataObject = [string, string | FileObject][];
+
+export interface AnimationEventObject extends EventObject {
+ animationName: string;
+ elapsedTime: number;
+ pseudoElement: string;
+}
+
+export interface ClipboardEventObject extends EventObject {
+ clipboardData: DataTransferObject | null;
+}
+
+export interface UIEventObject extends EventObject {
+ detail: number;
+}
+
+/** The DOM CompositionEvent represents events that occur due to the user indirectly
+ * entering text. */
+export interface CompositionEventObject extends UIEventObject {
+ data: string;
+}
+
+export interface KeyboardEventObject extends UIEventObject {
+ altKey: boolean;
+ code: string;
+ ctrlKey: boolean;
+ isComposing: boolean;
+ key: string;
+ location: number;
+ metaKey: boolean;
+ repeat: boolean;
+ shiftKey: boolean;
+}
+
+export interface FocusEventObject extends UIEventObject {
+ relatedTarget: ElementObject | null;
+}
+
+export interface TouchEventObject extends UIEventObject {
+ altKey: boolean;
+ changedTouches: TouchObject[];
+ ctrlKey: boolean;
+ metaKey: boolean;
+ shiftKey: boolean;
+ targetTouches: TouchObject[];
+ touches: TouchObject[];
+}
+
+export interface PointerEventObject extends MouseEventObject {
+ height: number;
+ isPrimary: boolean;
+ pointerId: number;
+ pointerType: string;
+ pressure: number;
+ tangentialPressure: number;
+ tiltX: number;
+ tiltY: number;
+ twist: number;
+ width: number;
+}
+
+export interface TransitionEventObject extends EventObject {
+ elapsedTime: number;
+ propertyName: string;
+ pseudoElement: string;
+}
+
+export interface WheelEventObject extends MouseEventObject {
+ readonly deltaMode: number;
+ readonly deltaX: number;
+ readonly deltaY: number;
+ readonly deltaZ: number;
+}
+
+export interface TouchObject {
+ clientX: number;
+ clientY: number;
+ force: number;
+ identifier: number;
+ pageX: number;
+ pageY: number;
+ radiusX: number;
+ radiusY: number;
+ rotationAngle: number;
+ screenX: number;
+ screenY: number;
+ target: ElementObject;
+}
+
+export interface DataTransferObject {
+ dropEffect: "none" | "copy" | "link" | "move";
+ effectAllowed:
+ | "none"
+ | "copy"
+ | "copyLink"
+ | "copyMove"
+ | "link"
+ | "linkMove"
+ | "move"
+ | "all"
+ | "uninitialized";
+ files: FileListObject;
+ items: DataTransferItemListObject;
+ types: string[];
+}
+
+export interface SelectionObject {
+ anchorNode: ElementObject | null;
+ anchorOffset: number;
+ focusNode: ElementObject | null;
+ focusOffset: number;
+ isCollapsed: boolean;
+ rangeCount: number;
+ type: string;
+ selectedText: string;
+}
+
+export interface ElementObject {
+ value?: string;
+ textContent?: string;
+}
+
+export interface FileObject {
+ name: string;
+ size: number;
+ type: string;
+}
diff --git a/src/js/packages/event-to-object/src/index.ts b/src/js/packages/event-to-object/src/index.ts
new file mode 100644
index 000000000..9a40a2128
--- /dev/null
+++ b/src/js/packages/event-to-object/src/index.ts
@@ -0,0 +1,427 @@
+import * as e from "./events";
+
+export default function convert(
+ event: E,
+):
+ | {
+ [K in keyof e.EventToObjectMap]: e.EventToObjectMap[K] extends [
+ E,
+ infer P,
+ ]
+ ? P
+ : never;
+ }[keyof e.EventToObjectMap]
+ | null {
+ return event.type in eventConverters
+ ? eventConverters[event.type](event)
+ : convertEvent(event);
+}
+
+const convertEvent = (event: Event): e.EventObject => ({
+ /** Returns true or false depending on how event was initialized. True if event goes
+ * through its target's ancestors in reverse tree order, and false otherwise. */
+ bubbles: event.bubbles,
+ composed: event.composed,
+ currentTarget: convertElement(event.currentTarget),
+ defaultPrevented: event.defaultPrevented,
+ eventPhase: event.eventPhase,
+ isTrusted: event.isTrusted,
+ target: convertElement(event.target),
+ timeStamp: event.timeStamp,
+ type: event.type,
+ selection: convertSelection(window.getSelection()),
+});
+
+const convertClipboardEvent = (
+ event: ClipboardEvent,
+): e.ClipboardEventObject => ({
+ ...convertEvent(event),
+ clipboardData: convertDataTransferObject(event.clipboardData),
+});
+
+const convertCompositionEvent = (
+ event: CompositionEvent,
+): e.CompositionEventObject => ({
+ ...convertUiEvent(event),
+ data: event.data,
+});
+
+const convertInputEvent = (event: InputEvent): e.InputEventObject => ({
+ ...convertUiEvent(event),
+ data: event.data,
+ inputType: event.inputType,
+ dataTransfer: convertDataTransferObject(event.dataTransfer),
+ isComposing: event.isComposing,
+});
+
+const convertKeyboardEvent = (event: KeyboardEvent): e.KeyboardEventObject => ({
+ ...convertUiEvent(event),
+ code: event.code,
+ isComposing: event.isComposing,
+ altKey: event.altKey,
+ ctrlKey: event.ctrlKey,
+ key: event.key,
+ location: event.location,
+ metaKey: event.metaKey,
+ repeat: event.repeat,
+ shiftKey: event.shiftKey,
+});
+
+const convertMouseEvent = (event: MouseEvent): e.MouseEventObject => ({
+ ...convertEvent(event),
+ altKey: event.altKey,
+ button: event.button,
+ buttons: event.buttons,
+ clientX: event.clientX,
+ clientY: event.clientY,
+ ctrlKey: event.ctrlKey,
+ metaKey: event.metaKey,
+ pageX: event.pageX,
+ pageY: event.pageY,
+ screenX: event.screenX,
+ screenY: event.screenY,
+ shiftKey: event.shiftKey,
+ movementX: event.movementX,
+ movementY: event.movementY,
+ offsetX: event.offsetX,
+ offsetY: event.offsetY,
+ x: event.x,
+ y: event.y,
+ relatedTarget: convertElement(event.relatedTarget),
+});
+
+const convertTouchEvent = (event: TouchEvent): e.TouchEventObject => ({
+ ...convertUiEvent(event),
+ altKey: event.altKey,
+ ctrlKey: event.ctrlKey,
+ metaKey: event.metaKey,
+ shiftKey: event.shiftKey,
+ touches: Array.from(event.touches).map(convertTouch),
+ changedTouches: Array.from(event.changedTouches).map(convertTouch),
+ targetTouches: Array.from(event.targetTouches).map(convertTouch),
+});
+
+const convertUiEvent = (event: UIEvent): e.UIEventObject => ({
+ ...convertEvent(event),
+ detail: event.detail,
+});
+
+const convertAnimationEvent = (
+ event: AnimationEvent,
+): e.AnimationEventObject => ({
+ ...convertEvent(event),
+ animationName: event.animationName,
+ pseudoElement: event.pseudoElement,
+ elapsedTime: event.elapsedTime,
+});
+
+const convertTransitionEvent = (
+ event: TransitionEvent,
+): e.TransitionEventObject => ({
+ ...convertEvent(event),
+ propertyName: event.propertyName,
+ pseudoElement: event.pseudoElement,
+ elapsedTime: event.elapsedTime,
+});
+
+const convertFocusEvent = (event: FocusEvent): e.FocusEventObject => ({
+ ...convertUiEvent(event),
+ relatedTarget: convertElement(event.relatedTarget),
+});
+
+const convertDeviceOrientationEvent = (
+ event: DeviceOrientationEvent,
+): e.DeviceOrientationEventObject => ({
+ ...convertEvent(event),
+ absolute: event.absolute,
+ alpha: event.alpha,
+ beta: event.beta,
+ gamma: event.gamma,
+});
+
+const convertDragEvent = (event: DragEvent): e.DragEventObject => ({
+ ...convertMouseEvent(event),
+ dataTransfer: convertDataTransferObject(event.dataTransfer),
+});
+
+const convertGamepadEvent = (event: GamepadEvent): e.GamepadEventObject => ({
+ ...convertEvent(event),
+ gamepad: convertGamepad(event.gamepad),
+});
+
+const convertPointerEvent = (event: PointerEvent): e.PointerEventObject => ({
+ ...convertMouseEvent(event),
+ pointerId: event.pointerId,
+ width: event.width,
+ height: event.height,
+ pressure: event.pressure,
+ tiltX: event.tiltX,
+ tiltY: event.tiltY,
+ pointerType: event.pointerType,
+ isPrimary: event.isPrimary,
+ tangentialPressure: event.tangentialPressure,
+ twist: event.twist,
+});
+
+const convertWheelEvent = (event: WheelEvent): e.WheelEventObject => ({
+ ...convertMouseEvent(event),
+ deltaMode: event.deltaMode,
+ deltaX: event.deltaX,
+ deltaY: event.deltaY,
+ deltaZ: event.deltaZ,
+});
+
+const convertSubmitEvent = (event: SubmitEvent): e.SubmitEventObject => ({
+ ...convertEvent(event),
+ submitter: convertElement(event.submitter),
+});
+
+const eventConverters: { [key: string]: (event: any) => any } = {
+ // animation events
+ animationcancel: convertAnimationEvent,
+ animationend: convertAnimationEvent,
+ animationiteration: convertAnimationEvent,
+ animationstart: convertAnimationEvent,
+ // input events
+ beforeinput: convertInputEvent,
+ // composition events
+ compositionend: convertCompositionEvent,
+ compositionstart: convertCompositionEvent,
+ compositionupdate: convertCompositionEvent,
+ // clipboard events
+ copy: convertClipboardEvent,
+ cut: convertClipboardEvent,
+ paste: convertClipboardEvent,
+ // device orientation events
+ deviceorientation: convertDeviceOrientationEvent,
+ // drag events
+ drag: convertDragEvent,
+ dragend: convertDragEvent,
+ dragenter: convertDragEvent,
+ dragleave: convertDragEvent,
+ dragover: convertDragEvent,
+ dragstart: convertDragEvent,
+ drop: convertDragEvent,
+ // ui events
+ error: convertUiEvent,
+ // focus events
+ blur: convertFocusEvent,
+ focus: convertFocusEvent,
+ focusin: convertFocusEvent,
+ focusout: convertFocusEvent,
+ // gamepad events
+ gamepadconnected: convertGamepadEvent,
+ gamepaddisconnected: convertGamepadEvent,
+ // keyboard events
+ keydown: convertKeyboardEvent,
+ keypress: convertKeyboardEvent,
+ keyup: convertKeyboardEvent,
+ // mouse events
+ auxclick: convertMouseEvent,
+ click: convertMouseEvent,
+ dblclick: convertMouseEvent,
+ contextmenu: convertMouseEvent,
+ mousedown: convertMouseEvent,
+ mouseenter: convertMouseEvent,
+ mouseleave: convertMouseEvent,
+ mousemove: convertMouseEvent,
+ mouseout: convertMouseEvent,
+ mouseover: convertMouseEvent,
+ mouseup: convertMouseEvent,
+ scroll: convertMouseEvent,
+ // pointer events
+ gotpointercapture: convertPointerEvent,
+ lostpointercapture: convertPointerEvent,
+ pointercancel: convertPointerEvent,
+ pointerdown: convertPointerEvent,
+ pointerenter: convertPointerEvent,
+ pointerleave: convertPointerEvent,
+ pointerlockchange: convertPointerEvent,
+ pointerlockerror: convertPointerEvent,
+ pointermove: convertPointerEvent,
+ pointerout: convertPointerEvent,
+ pointerover: convertPointerEvent,
+ pointerup: convertPointerEvent,
+ // submit events
+ submit: convertSubmitEvent,
+ // touch events
+ touchcancel: convertTouchEvent,
+ touchend: convertTouchEvent,
+ touchmove: convertTouchEvent,
+ touchstart: convertTouchEvent,
+ // transition events
+ transitioncancel: convertTransitionEvent,
+ transitionend: convertTransitionEvent,
+ transitionrun: convertTransitionEvent,
+ transitionstart: convertTransitionEvent,
+ // wheel events
+ wheel: convertWheelEvent,
+};
+
+function convertElement(element: EventTarget | HTMLElement | null): any {
+ if (!element || !("tagName" in element)) {
+ return null;
+ }
+
+ const htmlElement = element as HTMLElement;
+
+ return {
+ ...convertGenericElement(htmlElement),
+ ...(htmlElement.tagName in elementConverters
+ ? elementConverters[htmlElement.tagName](htmlElement)
+ : {}),
+ };
+}
+
+const convertGenericElement = (element: HTMLElement) => ({
+ tagName: element.tagName,
+ boundingClientRect: { ...element.getBoundingClientRect() },
+});
+
+const convertMediaElement = (element: HTMLMediaElement) => ({
+ currentTime: element.currentTime,
+ duration: element.duration,
+ ended: element.ended,
+ error: element.error,
+ seeking: element.seeking,
+ volume: element.volume,
+});
+
+const elementConverters: { [key: string]: (element: any) => any } = {
+ AUDIO: convertMediaElement,
+ BUTTON: (element: HTMLButtonElement) => ({ value: element.value }),
+ DATA: (element: HTMLDataElement) => ({ value: element.value }),
+ DATALIST: (element: HTMLDataListElement) => ({
+ options: Array.from(element.options).map(elementConverters["OPTION"]),
+ }),
+ DIALOG: (element: HTMLDialogElement) => ({
+ returnValue: element.returnValue,
+ }),
+ FIELDSET: (element: HTMLFieldSetElement) => ({
+ elements: Array.from(element.elements).map(convertElement),
+ }),
+ FORM: (element: HTMLFormElement) => ({
+ elements: Array.from(element.elements).map(convertElement),
+ }),
+ INPUT: (element: HTMLInputElement) => ({ value: element.value }),
+ METER: (element: HTMLMeterElement) => ({ value: element.value }),
+ OPTION: (element: HTMLOptionElement) => ({ value: element.value }),
+ OUTPUT: (element: HTMLOutputElement) => ({ value: element.value }),
+ PROGRESS: (element: HTMLProgressElement) => ({ value: element.value }),
+ SELECT: (element: HTMLSelectElement) => ({ value: element.value }),
+ TEXTAREA: (element: HTMLTextAreaElement) => ({ value: element.value }),
+ VIDEO: convertMediaElement,
+};
+
+const convertGamepad = (gamepad: Gamepad): e.GamepadObject => ({
+ axes: Array.from(gamepad.axes),
+ buttons: Array.from(gamepad.buttons).map(convertGamepadButton),
+ connected: gamepad.connected,
+ id: gamepad.id,
+ index: gamepad.index,
+ mapping: gamepad.mapping,
+ timestamp: gamepad.timestamp,
+ hapticActuators: Array.from(gamepad.hapticActuators).map(
+ convertGamepadHapticActuator,
+ ),
+});
+
+const convertGamepadButton = (
+ button: GamepadButton,
+): e.GamepadButtonObject => ({
+ pressed: button.pressed,
+ touched: button.touched,
+ value: button.value,
+});
+
+const convertGamepadHapticActuator = (
+ actuator: GamepadHapticActuator,
+): e.GamepadHapticActuatorObject => ({
+ type: actuator.type,
+});
+
+const convertFile = (file: File) => ({
+ lastModified: file.lastModified,
+ name: file.name,
+ size: file.size,
+ type: file.type,
+});
+
+function convertDataTransferObject(
+ dataTransfer: DataTransfer | null,
+): e.DataTransferObject | null {
+ if (!dataTransfer) {
+ return null;
+ }
+ const { dropEffect, effectAllowed, files, items, types } = dataTransfer;
+ return {
+ dropEffect,
+ effectAllowed,
+ files: Array.from(files).map(convertFile),
+ items: Array.from(items).map((item) => ({
+ kind: item.kind,
+ type: item.type,
+ })),
+ types: Array.from(types),
+ };
+}
+
+function convertSelection(
+ selection: Selection | null,
+): e.SelectionObject | null {
+ if (!selection) {
+ return null;
+ }
+ const {
+ type,
+ anchorNode,
+ anchorOffset,
+ focusNode,
+ focusOffset,
+ isCollapsed,
+ rangeCount,
+ } = selection;
+ if (type === "None") {
+ return null;
+ }
+ return {
+ type,
+ anchorNode: convertElement(anchorNode),
+ anchorOffset,
+ focusNode: convertElement(focusNode),
+ focusOffset,
+ isCollapsed,
+ rangeCount,
+ selectedText: selection.toString(),
+ };
+}
+
+function convertTouch({
+ identifier,
+ pageX,
+ pageY,
+ screenX,
+ screenY,
+ clientX,
+ clientY,
+ force,
+ radiusX,
+ radiusY,
+ rotationAngle,
+ target,
+}: Touch): e.TouchObject {
+ return {
+ identifier,
+ pageX,
+ pageY,
+ screenX,
+ screenY,
+ clientX,
+ clientY,
+ force,
+ radiusX,
+ radiusY,
+ rotationAngle,
+ target: convertElement(target),
+ };
+}
diff --git a/src/js/packages/event-to-object/tests/event-to-object.test.ts b/src/js/packages/event-to-object/tests/event-to-object.test.ts
new file mode 100644
index 000000000..b7b8c68af
--- /dev/null
+++ b/src/js/packages/event-to-object/tests/event-to-object.test.ts
@@ -0,0 +1,381 @@
+// @ts-ignore
+import { window } from "./tooling/setup";
+import { test } from "uvu";
+import { Event } from "happy-dom";
+import { checkEventConversion } from "./tooling/check";
+import {
+ mockElementObject,
+ mockGamepad,
+ mockTouch,
+ mockTouchObject,
+} from "./tooling/mock";
+
+type SimpleTestCase = {
+ types: string[];
+ description: string;
+ givenEventType: new (type: string) => E;
+ expectedConversion: any;
+ initGivenEvent?: (event: E) => void;
+};
+
+const simpleTestCases: SimpleTestCase[] = [
+ {
+ types: [
+ "animationcancel",
+ "animationend",
+ "animationiteration",
+ "animationstart",
+ ],
+ description: "animation event",
+ givenEventType: window.AnimationEvent,
+ expectedConversion: {
+ animationName: "",
+ pseudoElement: "",
+ elapsedTime: 0,
+ },
+ },
+ {
+ types: ["beforeinput"],
+ description: "event",
+ givenEventType: window.InputEvent,
+ expectedConversion: {
+ detail: 0,
+ data: "",
+ inputType: "",
+ dataTransfer: null,
+ isComposing: false,
+ },
+ },
+ {
+ types: ["compositionend", "compositionstart", "compositionupdate"],
+ description: "composition event",
+ givenEventType: window.CompositionEvent,
+ expectedConversion: {
+ data: undefined,
+ detail: undefined,
+ },
+ },
+ {
+ types: ["copy", "cut", "paste"],
+ description: "clipboard event",
+ givenEventType: window.ClipboardEvent,
+ expectedConversion: { clipboardData: null },
+ },
+ {
+ types: [
+ "drag",
+ "dragend",
+ "dragenter",
+ "dragleave",
+ "dragover",
+ "dragstart",
+ "drop",
+ ],
+ description: "drag event",
+ givenEventType: window.DragEvent,
+ expectedConversion: {
+ altKey: undefined,
+ button: undefined,
+ buttons: undefined,
+ clientX: undefined,
+ clientY: undefined,
+ ctrlKey: undefined,
+ dataTransfer: null,
+ metaKey: undefined,
+ movementX: undefined,
+ movementY: undefined,
+ offsetX: undefined,
+ offsetY: undefined,
+ pageX: undefined,
+ pageY: undefined,
+ relatedTarget: null,
+ screenX: undefined,
+ screenY: undefined,
+ shiftKey: undefined,
+ x: undefined,
+ y: undefined,
+ },
+ },
+ {
+ types: ["error"],
+ description: "event",
+ givenEventType: window.ErrorEvent,
+ expectedConversion: { detail: 0 },
+ },
+ {
+ types: ["blur", "focus", "focusin", "focusout"],
+ description: "focus event",
+ givenEventType: window.FocusEvent,
+ expectedConversion: {
+ relatedTarget: null,
+ detail: 0,
+ },
+ },
+ {
+ types: ["gamepadconnected", "gamepaddisconnected"],
+ description: "gamepad event",
+ givenEventType: window.GamepadEvent,
+ expectedConversion: { gamepad: mockGamepad },
+ initGivenEvent: (event) => {
+ event.gamepad = mockGamepad;
+ },
+ },
+ {
+ types: ["keydown", "keypress", "keyup"],
+ description: "keyboard event",
+ givenEventType: window.KeyboardEvent,
+ expectedConversion: {
+ altKey: false,
+ code: "",
+ ctrlKey: false,
+ isComposing: false,
+ key: "",
+ location: 0,
+ metaKey: false,
+ repeat: false,
+ shiftKey: false,
+ detail: 0,
+ },
+ },
+ {
+ types: [
+ "click",
+ "auxclick",
+ "dblclick",
+ "mousedown",
+ "mouseenter",
+ "mouseleave",
+ "mousemove",
+ "mouseout",
+ "mouseover",
+ "mouseup",
+ "scroll",
+ ],
+ description: "mouse event",
+ givenEventType: window.MouseEvent,
+ expectedConversion: {
+ altKey: false,
+ button: 0,
+ buttons: 0,
+ clientX: 0,
+ clientY: 0,
+ ctrlKey: false,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ offsetX: 0,
+ offsetY: 0,
+ pageX: 0,
+ pageY: 0,
+ relatedTarget: null,
+ screenX: 0,
+ screenY: 0,
+ shiftKey: false,
+ x: undefined,
+ y: undefined,
+ },
+ },
+ {
+ types: [
+ "auxclick",
+ "click",
+ "contextmenu",
+ "dblclick",
+ "mousedown",
+ "mouseenter",
+ "mouseleave",
+ "mousemove",
+ "mouseout",
+ "mouseover",
+ "mouseup",
+ ],
+ description: "mouse event",
+ givenEventType: window.MouseEvent,
+ expectedConversion: {
+ altKey: false,
+ button: 0,
+ buttons: 0,
+ clientX: 0,
+ clientY: 0,
+ ctrlKey: false,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ offsetX: 0,
+ offsetY: 0,
+ pageX: 0,
+ pageY: 0,
+ relatedTarget: null,
+ screenX: 0,
+ screenY: 0,
+ shiftKey: false,
+ x: undefined,
+ y: undefined,
+ },
+ },
+ {
+ types: [
+ "gotpointercapture",
+ "lostpointercapture",
+ "pointercancel",
+ "pointerdown",
+ "pointerenter",
+ "pointerleave",
+ "pointerlockchange",
+ "pointerlockerror",
+ "pointermove",
+ "pointerout",
+ "pointerover",
+ "pointerup",
+ ],
+ description: "pointer event",
+ givenEventType: window.PointerEvent,
+ expectedConversion: {
+ altKey: false,
+ button: 0,
+ buttons: 0,
+ clientX: 0,
+ clientY: 0,
+ ctrlKey: false,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ offsetX: 0,
+ offsetY: 0,
+ pageX: 0,
+ pageY: 0,
+ relatedTarget: null,
+ screenX: 0,
+ screenY: 0,
+ shiftKey: false,
+ x: undefined,
+ y: undefined,
+ pointerId: 0,
+ pointerType: "",
+ pressure: 0,
+ tiltX: 0,
+ tiltY: 0,
+ width: 0,
+ height: 0,
+ isPrimary: false,
+ twist: 0,
+ tangentialPressure: 0,
+ },
+ },
+ {
+ types: ["submit"],
+ description: "event",
+ givenEventType: window.Event,
+ expectedConversion: { submitter: null },
+ initGivenEvent: (event) => {
+ event.submitter = null;
+ },
+ },
+ {
+ types: ["touchcancel", "touchend", "touchmove", "touchstart"],
+ description: "touch event",
+ givenEventType: window.TouchEvent,
+ expectedConversion: {
+ altKey: undefined,
+ changedTouches: [mockTouchObject],
+ ctrlKey: undefined,
+ metaKey: undefined,
+ targetTouches: [mockTouchObject],
+ touches: [mockTouchObject],
+ detail: undefined,
+ shiftKey: undefined,
+ },
+ initGivenEvent: (event) => {
+ event.changedTouches = [mockTouch];
+ event.targetTouches = [mockTouch];
+ event.touches = [mockTouch];
+ },
+ },
+ {
+ types: [
+ "transitioncancel",
+ "transitionend",
+ "transitionrun",
+ "transitionstart",
+ ],
+ description: "transition event",
+ givenEventType: window.TransitionEvent,
+ expectedConversion: {
+ propertyName: undefined,
+ elapsedTime: undefined,
+ pseudoElement: undefined,
+ },
+ },
+ {
+ types: ["wheel"],
+ description: "wheel event",
+ givenEventType: window.WheelEvent,
+ expectedConversion: {
+ altKey: undefined,
+ button: undefined,
+ buttons: undefined,
+ clientX: undefined,
+ clientY: undefined,
+ ctrlKey: undefined,
+ deltaMode: 0,
+ deltaX: 0,
+ deltaY: 0,
+ deltaZ: 0,
+ metaKey: undefined,
+ movementX: undefined,
+ movementY: undefined,
+ offsetX: undefined,
+ offsetY: undefined,
+ pageX: 0,
+ pageY: 0,
+ relatedTarget: null,
+ screenX: undefined,
+ screenY: undefined,
+ shiftKey: undefined,
+ x: undefined,
+ y: undefined,
+ },
+ },
+];
+
+simpleTestCases.forEach((testCase) => {
+ testCase.types.forEach((type) => {
+ test(`converts ${type} ${testCase.description}`, () => {
+ const event = new testCase.givenEventType(type);
+ if (testCase.initGivenEvent) {
+ testCase.initGivenEvent(event);
+ }
+ checkEventConversion(event, testCase.expectedConversion);
+ });
+ });
+});
+
+test("adds text of current selection", () => {
+ document.body.innerHTML = `
+
+ `;
+ const start = document.getElementById("start");
+ const end = document.getElementById("end");
+ window.getSelection()!.setBaseAndExtent(start!, 0, end!, 0);
+ checkEventConversion(new window.Event("fake"), {
+ type: "fake",
+ selection: {
+ type: "Range",
+ anchorNode: { ...mockElementObject, tagName: "P" },
+ anchorOffset: 0,
+ focusNode: { ...mockElementObject, tagName: "P" },
+ focusOffset: 0,
+ isCollapsed: false,
+ rangeCount: 1,
+ selectedText: "START\n MIDDLE\n ",
+ },
+ eventPhase: undefined,
+ isTrusted: undefined,
+ });
+});
+
+test.run();
diff --git a/src/js/packages/event-to-object/tests/tooling/check.ts b/src/js/packages/event-to-object/tests/tooling/check.ts
new file mode 100644
index 000000000..33ff5ed5b
--- /dev/null
+++ b/src/js/packages/event-to-object/tests/tooling/check.ts
@@ -0,0 +1,46 @@
+import * as assert from "uvu/assert";
+import { Event } from "happy-dom";
+// @ts-ignore
+import lodash from "lodash";
+import convert from "../../src/index";
+
+export function checkEventConversion(
+ givenEvent: Event,
+ expectedConversion: any,
+): void {
+ const actualSerializedEvent = convert(
+ // @ts-ignore
+ givenEvent,
+ );
+
+ if (!actualSerializedEvent) {
+ assert.equal(actualSerializedEvent, expectedConversion);
+ return;
+ }
+
+ // too hard to compare
+ assert.equal(typeof actualSerializedEvent.timeStamp, "number");
+
+ assert.equal(
+ actualSerializedEvent,
+ lodash.merge(
+ { timeStamp: actualSerializedEvent.timeStamp, type: givenEvent.type },
+ expectedConversionDefaults,
+ expectedConversion,
+ ),
+ );
+
+ // verify result is JSON serializable
+ JSON.stringify(actualSerializedEvent);
+}
+
+const expectedConversionDefaults = {
+ target: null,
+ currentTarget: null,
+ bubbles: false,
+ composed: false,
+ defaultPrevented: false,
+ eventPhase: undefined,
+ isTrusted: undefined,
+ selection: null,
+};
diff --git a/src/js/packages/event-to-object/tests/tooling/mock.ts b/src/js/packages/event-to-object/tests/tooling/mock.ts
new file mode 100644
index 000000000..81e506500
--- /dev/null
+++ b/src/js/packages/event-to-object/tests/tooling/mock.ts
@@ -0,0 +1,61 @@
+export const mockBoundingRect = {
+ left: 0,
+ top: 0,
+ right: 0,
+ bottom: 0,
+ x: 0,
+ y: 0,
+ height: 0,
+ width: 0,
+};
+
+export const mockElementObject = {
+ tagName: null,
+ boundingClientRect: mockBoundingRect,
+};
+
+export const mockElement = {
+ tagName: null,
+ getBoundingClientRect: () => mockBoundingRect,
+};
+
+export const mockGamepad = {
+ id: "test",
+ index: 0,
+ connected: true,
+ mapping: "standard",
+ axes: [],
+ buttons: [
+ {
+ pressed: false,
+ touched: false,
+ value: 0,
+ },
+ ],
+ hapticActuators: [
+ {
+ type: "vibration",
+ },
+ ],
+ timestamp: undefined,
+};
+
+export const mockTouch = {
+ identifier: 0,
+ pageX: 0,
+ pageY: 0,
+ screenX: 0,
+ screenY: 0,
+ clientX: 0,
+ clientY: 0,
+ force: 0,
+ radiusX: 0,
+ radiusY: 0,
+ rotationAngle: 0,
+ target: mockElement,
+};
+
+export const mockTouchObject = {
+ ...mockTouch,
+ target: mockElementObject,
+};
diff --git a/src/client/packages/idom-client-react/tests/tooling/setup.js b/src/js/packages/event-to-object/tests/tooling/setup.js
similarity index 79%
rename from src/client/packages/idom-client-react/tests/tooling/setup.js
rename to src/js/packages/event-to-object/tests/tooling/setup.js
index 86f50ed14..213578046 100644
--- a/src/client/packages/idom-client-react/tests/tooling/setup.js
+++ b/src/js/packages/event-to-object/tests/tooling/setup.js
@@ -1,7 +1,7 @@
import { test } from "uvu";
-import { JSDOM } from "jsdom";
+import { Window } from "happy-dom";
-const { window } = new JSDOM(" ");
+export const window = new Window();
export function setup() {
global.window = window;
@@ -15,6 +15,7 @@ export function reset() {
window.document.title = "";
window.document.head.innerHTML = "";
window.document.body.innerHTML = " ";
+ window.getSelection().removeAllRanges();
}
test.before(setup);
diff --git a/src/js/packages/event-to-object/tsconfig.json b/src/js/packages/event-to-object/tsconfig.json
new file mode 100644
index 000000000..b9a031fa9
--- /dev/null
+++ b/src/js/packages/event-to-object/tsconfig.json
@@ -0,0 +1,9 @@
+{
+ "extends": "../../tsconfig.package.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "rootDir": "src",
+ "composite": true
+ },
+ "include": ["src"]
+}
diff --git a/src/js/packages/event-to-object/tsconfig.tests.json b/src/js/packages/event-to-object/tsconfig.tests.json
new file mode 100644
index 000000000..33be69a56
--- /dev/null
+++ b/src/js/packages/event-to-object/tsconfig.tests.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "esnext",
+ "allowJs": false,
+ "skipLibCheck": false,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "esnext",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true
+ }
+}
diff --git a/src/js/tsconfig.package.json b/src/js/tsconfig.package.json
new file mode 100644
index 000000000..9e7fe5f74
--- /dev/null
+++ b/src/js/tsconfig.package.json
@@ -0,0 +1,22 @@
+{
+ "compilerOptions": {
+ "allowJs": false,
+ "allowSyntheticDefaultImports": true,
+ "declaration": true,
+ "declarationMap": true,
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "isolatedModules": true,
+ "jsx": "react",
+ "lib": ["DOM", "DOM.Iterable", "esnext"],
+ "module": "esnext",
+ "moduleResolution": "node",
+ "noEmitOnError": true,
+ "noUnusedLocals": true,
+ "resolveJsonModule": true,
+ "skipLibCheck": false,
+ "sourceMap": true,
+ "strict": true,
+ "target": "esnext"
+ }
+}
diff --git a/src/py/reactpy/.gitignore b/src/py/reactpy/.gitignore
new file mode 100644
index 000000000..0499d7590
--- /dev/null
+++ b/src/py/reactpy/.gitignore
@@ -0,0 +1,4 @@
+.coverage.*
+
+# --- Build Artifacts ---
+reactpy/_static
diff --git a/src/py/reactpy/.temp.py b/src/py/reactpy/.temp.py
new file mode 100644
index 000000000..d8881ad1e
--- /dev/null
+++ b/src/py/reactpy/.temp.py
@@ -0,0 +1,28 @@
+from reactpy import component, html, run, use_state
+from reactpy.core.types import State
+
+
+@component
+def Item(item: str, all_items: State[list[str]]):
+ color = use_state(None)
+
+ def deleteme(event):
+ all_items.set_value([i for i in all_items.value if (i != item)])
+
+ def colorize(event):
+ color.set_value("blue" if not color.value else None)
+
+ return html.div(
+ {"id": item, "style": {"background_color": color.value}},
+ html.button({"on_click": colorize}, f"Color {item}"),
+ html.button({"on_click": deleteme}, f"Delete {item}"),
+ )
+
+
+@component
+def App():
+ items = use_state(["A", "B", "C"])
+ return html._([Item(item, items, key=item) for item in items.value])
+
+
+run(App)
diff --git a/src/py/reactpy/MANIFEST.in b/src/py/reactpy/MANIFEST.in
new file mode 100644
index 000000000..b989938fa
--- /dev/null
+++ b/src/py/reactpy/MANIFEST.in
@@ -0,0 +1,3 @@
+recursive-include src/reactpy/_client *
+recursive-include src/reactpy/web/templates *
+include src/reactpy/py.typed
diff --git a/src/py/reactpy/README.md b/src/py/reactpy/README.md
new file mode 100644
index 000000000..910a573a5
--- /dev/null
+++ b/src/py/reactpy/README.md
@@ -0,0 +1,23 @@
+# ReactPy
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+[ReactPy](https://reactpy.dev/) is a library for building user interfaces in Python without Javascript. ReactPy interfaces are made from components that look and behave similar to those found in [ReactJS](https://reactjs.org/). Designed with simplicity in mind, ReactPy can be used by those without web development experience while also being powerful enough to grow with your ambitions.
diff --git a/src/py/reactpy/pyproject.toml b/src/py/reactpy/pyproject.toml
new file mode 100644
index 000000000..309248507
--- /dev/null
+++ b/src/py/reactpy/pyproject.toml
@@ -0,0 +1,179 @@
+[build-system]
+requires = ["hatchling", "hatch-build-scripts>=0.0.4"]
+build-backend = "hatchling.build"
+
+# --- Project --------------------------------------------------------------------------
+
+[project]
+name = "reactpy"
+dynamic = ["version"]
+description = 'Reactive user interfaces with pure Python'
+readme = "README.md"
+requires-python = ">=3.9"
+license = "MIT"
+keywords = ["react", "javascript", "reactpy", "component"]
+authors = [
+ { name = "Ryan Morshead", email = "ryan.morshead@gmail.com" },
+]
+classifiers = [
+ "Development Status :: 4 - Beta",
+ "Programming Language :: Python",
+ "Programming Language :: Python :: 3.9",
+ "Programming Language :: Python :: 3.10",
+ "Programming Language :: Python :: 3.11",
+ "Programming Language :: Python :: Implementation :: CPython",
+ "Programming Language :: Python :: Implementation :: PyPy",
+]
+dependencies = [
+ "exceptiongroup >=1.0",
+ "typing-extensions >=3.10",
+ "mypy-extensions >=0.4.3",
+ "anyio >=3",
+ "jsonpatch >=1.32",
+ "fastjsonschema >=2.14.5",
+ "requests >=2",
+ "colorlog >=6",
+ "asgiref >=3",
+ "lxml >=4",
+]
+[project.optional-dependencies]
+all = ["reactpy[starlette,sanic,fastapi,flask,tornado,testing]"]
+
+starlette = [
+ "starlette >=0.13.6",
+ "uvicorn[standard] >=0.19.0",
+]
+sanic = [
+ "sanic >=21",
+ "sanic-cors",
+ "tracerite>=1.1.1",
+ "setuptools",
+ "uvicorn[standard] >=0.19.0",
+]
+fastapi = [
+ "fastapi >=0.63.0",
+ "uvicorn[standard] >=0.19.0",
+]
+flask = [
+ "flask",
+ "markupsafe>=1.1.1,<2.1",
+ "flask-cors",
+ "flask-sock",
+]
+tornado = [
+ "tornado",
+]
+testing = [
+ "playwright",
+]
+
+[project.urls]
+Source = "https://github.com/reactive-python/reactpy"
+Documentation = "https://github.com/reactive-python/reactpy#readme"
+Issues = "https://github.com/reactive-python/reactpy/discussions"
+
+# --- Hatch ----------------------------------------------------------------------------
+
+[tool.hatch.version]
+path = "reactpy/__init__.py"
+
+[tool.hatch.envs.default]
+features = ["all"]
+pre-install-command = "hatch build --hooks-only"
+dependencies = [
+ "coverage[toml]>=6.5",
+ "pytest",
+ "pytest-asyncio>=0.23",
+ "pytest-mock",
+ "pytest-rerunfailures",
+ "pytest-timeout",
+ "responses",
+ "playwright",
+ # I'm not quite sure why this needs to be installed for tests with Sanic to pass
+ "sanic-testing",
+ # Used to generate model changes from layout update messages
+ "jsonpointer",
+]
+[tool.hatch.envs.default.scripts]
+test = "playwright install && pytest {args:tests}"
+test-cov = "playwright install && coverage run -m pytest {args:tests}"
+cov-report = [
+ # "- coverage combine",
+ "coverage report",
+]
+cov = [
+ "test-cov {args}",
+ "cov-report",
+]
+
+[tool.hatch.envs.default.env-vars]
+REACTPY_DEBUG_MODE="1"
+
+[tool.hatch.envs.lint]
+features = ["all"]
+dependencies = [
+ "mypy>=1.0.0",
+ "types-click",
+ "types-tornado",
+ "types-pkg-resources",
+ "types-flask",
+ "types-requests",
+]
+
+[tool.hatch.envs.lint.scripts]
+types = "mypy --strict reactpy"
+all = ["types"]
+
+[[tool.hatch.build.hooks.build-scripts.scripts]]
+work_dir = "../../js"
+out_dir = "reactpy/_static"
+commands = [
+ "npm ci",
+ "npm run build"
+]
+artifacts = [
+ "app/dist/"
+]
+
+# --- Pytest ---------------------------------------------------------------------------
+
+[tool.pytest.ini_options]
+testpaths = "tests"
+xfail_strict = true
+python_files = "*asserts.py test_*.py"
+asyncio_mode = "auto"
+log_cli_level = "INFO"
+
+# --- MyPy -----------------------------------------------------------------------------
+
+[tool.mypy]
+incremental = false
+ignore_missing_imports = true
+warn_unused_configs = true
+warn_redundant_casts = true
+warn_unused_ignores = true
+
+# --- Coverage -------------------------------------------------------------------------
+
+[tool.coverage.run]
+source_pkgs = ["reactpy"]
+branch = false
+parallel = false
+omit = [
+ "reactpy/__init__.py",
+]
+
+[tool.coverage.report]
+fail_under = 100
+show_missing = true
+skip_covered = true
+sort = "Name"
+exclude_lines = [
+ "no ?cov",
+ '\.\.\.',
+ "if __name__ == .__main__.:",
+ "if TYPE_CHECKING:",
+]
+omit = [
+ "reactpy/__main__.py",
+]
diff --git a/src/py/reactpy/reactpy/__init__.py b/src/py/reactpy/reactpy/__init__.py
new file mode 100644
index 000000000..49e357441
--- /dev/null
+++ b/src/py/reactpy/reactpy/__init__.py
@@ -0,0 +1,57 @@
+from reactpy import backend, config, html, logging, sample, svg, types, web, widgets
+from reactpy.backend.hooks import use_connection, use_location, use_scope
+from reactpy.backend.utils import run
+from reactpy.core import hooks
+from reactpy.core.component import component
+from reactpy.core.events import event
+from reactpy.core.hooks import (
+ create_context,
+ use_callback,
+ use_context,
+ use_debug_value,
+ use_effect,
+ use_memo,
+ use_reducer,
+ use_ref,
+ use_state,
+)
+from reactpy.core.layout import Layout
+from reactpy.core.vdom import vdom
+from reactpy.utils import Ref, html_to_vdom, vdom_to_html
+
+__author__ = "The Reactive Python Team"
+__version__ = "1.0.2" # DO NOT MODIFY
+
+__all__ = [
+ "backend",
+ "component",
+ "config",
+ "create_context",
+ "event",
+ "hooks",
+ "html_to_vdom",
+ "html",
+ "Layout",
+ "logging",
+ "Ref",
+ "run",
+ "sample",
+ "Stop",
+ "svg",
+ "types",
+ "use_callback",
+ "use_connection",
+ "use_context",
+ "use_debug_value",
+ "use_effect",
+ "use_location",
+ "use_memo",
+ "use_reducer",
+ "use_ref",
+ "use_scope",
+ "use_state",
+ "vdom_to_html",
+ "vdom",
+ "web",
+ "widgets",
+]
diff --git a/src/py/reactpy/reactpy/__main__.py b/src/py/reactpy/reactpy/__main__.py
new file mode 100644
index 000000000..d70ddf684
--- /dev/null
+++ b/src/py/reactpy/reactpy/__main__.py
@@ -0,0 +1,19 @@
+import click
+
+import reactpy
+from reactpy._console.rewrite_camel_case_props import rewrite_camel_case_props
+from reactpy._console.rewrite_keys import rewrite_keys
+
+
+@click.group()
+@click.version_option(reactpy.__version__, prog_name=reactpy.__name__)
+def app() -> None:
+ pass
+
+
+app.add_command(rewrite_keys)
+app.add_command(rewrite_camel_case_props)
+
+
+if __name__ == "__main__":
+ app()
diff --git a/src/idom/core/__init__.py b/src/py/reactpy/reactpy/_console/__init__.py
similarity index 100%
rename from src/idom/core/__init__.py
rename to src/py/reactpy/reactpy/_console/__init__.py
diff --git a/src/py/reactpy/reactpy/_console/ast_utils.py b/src/py/reactpy/reactpy/_console/ast_utils.py
new file mode 100644
index 000000000..220751119
--- /dev/null
+++ b/src/py/reactpy/reactpy/_console/ast_utils.py
@@ -0,0 +1,186 @@
+from __future__ import annotations
+
+import ast
+from collections.abc import Iterator, Sequence
+from dataclasses import dataclass
+from pathlib import Path
+from textwrap import indent
+from tokenize import COMMENT as COMMENT_TOKEN
+from tokenize import generate_tokens
+from typing import Any
+
+import click
+
+from reactpy import html
+
+
+def rewrite_changed_nodes(
+ file: Path,
+ source: str,
+ tree: ast.AST,
+ changed: list[ChangedNode],
+) -> str:
+ ast.fix_missing_locations(tree)
+
+ lines = source.split("\n")
+
+ # find closest parent nodes that should be re-written
+ nodes_to_unparse: list[ast.AST] = []
+ for change in changed:
+ node_lineage = [change.node, *change.parents]
+ for i in range(len(node_lineage) - 1):
+ current_node, next_node = node_lineage[i : i + 2]
+ if (
+ not hasattr(next_node, "lineno")
+ or next_node.lineno < change.node.lineno
+ or isinstance(next_node, (ast.ClassDef, ast.FunctionDef))
+ ):
+ nodes_to_unparse.append(current_node)
+ break
+ else: # nocov
+ msg = "Failed to change code"
+ raise RuntimeError(msg)
+
+ # check if an nodes to rewrite contain each other, pick outermost nodes
+ current_outermost_node, *sorted_nodes_to_unparse = sorted(
+ nodes_to_unparse, key=lambda n: n.lineno
+ )
+ outermost_nodes_to_unparse = [current_outermost_node]
+ for node in sorted_nodes_to_unparse:
+ if (
+ not current_outermost_node.end_lineno
+ or node.lineno > current_outermost_node.end_lineno
+ ):
+ current_outermost_node = node
+ outermost_nodes_to_unparse.append(node)
+
+ moved_comment_lines_from_end: list[int] = []
+ # now actually rewrite these nodes (in reverse to avoid changes earlier in file)
+ for node in reversed(outermost_nodes_to_unparse):
+ # make a best effort to preserve any comments that we're going to overwrite
+ comments = _find_comments(lines[node.lineno - 1 : node.end_lineno])
+
+ # there may be some content just before and after the content we're re-writing
+ before_replacement = lines[node.lineno - 1][: node.col_offset].lstrip()
+
+ after_replacement = (
+ lines[node.end_lineno - 1][node.end_col_offset :].strip()
+ if node.end_lineno is not None and node.end_col_offset is not None
+ else ""
+ )
+
+ replacement = indent(
+ before_replacement
+ + "\n".join([*comments, ast.unparse(node)])
+ + after_replacement,
+ " " * (node.col_offset - len(before_replacement)),
+ )
+
+ lines[node.lineno - 1 : node.end_lineno or node.lineno] = [replacement]
+
+ if comments:
+ moved_comment_lines_from_end.append(len(lines) - node.lineno)
+
+ for lineno_from_end in sorted(set(moved_comment_lines_from_end)):
+ click.echo(f"Moved comments to {file}:{len(lines) - lineno_from_end}")
+
+ return "\n".join(lines)
+
+
+@dataclass
+class ChangedNode:
+ node: ast.AST
+ parents: Sequence[ast.AST]
+
+
+def find_element_constructor_usages(
+ tree: ast.AST, add_props: bool = False
+) -> Iterator[ElementConstructorInfo]:
+ changed: list[Sequence[ast.AST]] = []
+ for parents, node in _walk_with_parent(tree):
+ if not (isinstance(node, ast.Call)):
+ continue
+
+ func = node.func
+ if isinstance(func, ast.Attribute) and (
+ (isinstance(func.value, ast.Name) and func.value.id == "html")
+ or (isinstance(func.value, ast.Attribute) and func.value.attr == "html")
+ ):
+ name = func.attr
+ elif isinstance(func, ast.Name):
+ name = func.id
+ else:
+ continue
+
+ maybe_attr_dict_node: Any | None = None
+
+ if name == "vdom":
+ if len(node.args) == 0:
+ continue
+ elif len(node.args) == 1:
+ maybe_attr_dict_node = ast.Dict(keys=[], values=[])
+ if add_props:
+ node.args.append(maybe_attr_dict_node)
+ else:
+ continue
+ elif isinstance(node.args[1], (ast.Constant, ast.JoinedStr)):
+ maybe_attr_dict_node = ast.Dict(keys=[], values=[])
+ if add_props:
+ node.args.insert(1, maybe_attr_dict_node)
+ else:
+ continue
+ elif len(node.args) >= 2: # noqa: PLR2004
+ maybe_attr_dict_node = node.args[1]
+ elif hasattr(html, name):
+ if len(node.args) == 0:
+ maybe_attr_dict_node = ast.Dict(keys=[], values=[])
+ if add_props:
+ node.args.append(maybe_attr_dict_node)
+ else:
+ continue
+ elif isinstance(node.args[0], (ast.Constant, ast.JoinedStr)):
+ maybe_attr_dict_node = ast.Dict(keys=[], values=[])
+ if add_props:
+ node.args.insert(0, maybe_attr_dict_node)
+ else:
+ continue
+ else:
+ maybe_attr_dict_node = node.args[0]
+
+ if not maybe_attr_dict_node:
+ continue
+
+ if isinstance(maybe_attr_dict_node, ast.Dict) or (
+ isinstance(maybe_attr_dict_node, ast.Call)
+ and isinstance(maybe_attr_dict_node.func, ast.Name)
+ and maybe_attr_dict_node.func.id == "dict"
+ and isinstance(maybe_attr_dict_node.func.ctx, ast.Load)
+ ):
+ yield ElementConstructorInfo(node, maybe_attr_dict_node, parents)
+
+ return changed
+
+
+@dataclass
+class ElementConstructorInfo:
+ call: ast.Call
+ props: ast.Dict | ast.Call
+ parents: Sequence[ast.AST]
+
+
+def _find_comments(lines: list[str]) -> list[str]:
+ iter_lines = iter(lines)
+ return [
+ token
+ for token_type, token, _, _, _ in generate_tokens(lambda: next(iter_lines))
+ if token_type == COMMENT_TOKEN
+ ]
+
+
+def _walk_with_parent(
+ node: ast.AST, parents: tuple[ast.AST, ...] = ()
+) -> Iterator[tuple[tuple[ast.AST, ...], ast.AST]]:
+ parents = (node, *parents)
+ for child in ast.iter_child_nodes(node):
+ yield parents, child
+ yield from _walk_with_parent(child, parents)
diff --git a/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py
new file mode 100644
index 000000000..d706adecf
--- /dev/null
+++ b/src/py/reactpy/reactpy/_console/rewrite_camel_case_props.py
@@ -0,0 +1,113 @@
+from __future__ import annotations
+
+import ast
+import re
+import sys
+from copy import copy
+from keyword import kwlist
+from pathlib import Path
+from typing import Callable
+
+import click
+
+from reactpy._console.ast_utils import (
+ ChangedNode,
+ find_element_constructor_usages,
+ rewrite_changed_nodes,
+)
+
+CAMEL_CASE_SUB_PATTERN = re.compile(r"(? None:
+ """Rewrite camelCase props to snake_case"""
+ if sys.version_info < (3, 9): # nocov
+ msg = "This command requires Python>=3.9"
+ raise RuntimeError(msg)
+
+ for p in map(Path, paths):
+ for f in [p] if p.is_file() else p.rglob("*.py"):
+ result = generate_rewrite(file=f, source=f.read_text(encoding="utf-8"))
+ if result is not None:
+ f.write_text(result)
+
+
+def generate_rewrite(file: Path, source: str) -> str | None:
+ tree = ast.parse(source)
+
+ changed = find_nodes_to_change(tree)
+ if not changed:
+ return None
+
+ new = rewrite_changed_nodes(file, source, tree, changed)
+ return new
+
+
+def find_nodes_to_change(tree: ast.AST) -> list[ChangedNode]:
+ changed: list[ChangedNode] = []
+ for el_info in find_element_constructor_usages(tree):
+ if _rewrite_props(el_info.props, _construct_prop_item):
+ changed.append(ChangedNode(el_info.call, el_info.parents))
+ return changed
+
+
+def conv_attr_name(name: str) -> str:
+ new_name = CAMEL_CASE_SUB_PATTERN.sub("_", name).lower()
+ return f"{new_name}_" if new_name in kwlist else new_name
+
+
+def _construct_prop_item(key: str, value: ast.expr) -> tuple[str, ast.expr]:
+ if key == "style" and isinstance(value, (ast.Dict, ast.Call)):
+ new_value = copy(value)
+ if _rewrite_props(
+ new_value,
+ lambda k, v: (
+ (k, v)
+ # avoid infinite recursion
+ if k == "style"
+ else _construct_prop_item(k, v)
+ ),
+ ):
+ value = new_value
+ else:
+ key = conv_attr_name(key)
+ return key, value
+
+
+def _rewrite_props(
+ props_node: ast.Dict | ast.Call,
+ constructor: Callable[[str, ast.expr], tuple[str, ast.expr]],
+) -> bool:
+ if isinstance(props_node, ast.Dict):
+ did_change = False
+ keys: list[ast.expr | None] = []
+ values: list[ast.expr] = []
+ for k, v in zip(props_node.keys, props_node.values):
+ if isinstance(k, ast.Constant) and isinstance(k.value, str):
+ k_value, new_v = constructor(k.value, v)
+ if k_value != k.value or new_v is not v:
+ did_change = True
+ k = ast.Constant(value=k_value)
+ v = new_v
+ keys.append(k)
+ values.append(v)
+ if not did_change:
+ return False
+ props_node.keys = keys
+ props_node.values = values
+ else:
+ did_change = False
+ keywords: list[ast.keyword] = []
+ for kw in props_node.keywords:
+ if kw.arg is not None:
+ kw_arg, kw_value = constructor(kw.arg, kw.value)
+ if kw_arg != kw.arg or kw_value is not kw.value:
+ did_change = True
+ kw = ast.keyword(arg=kw_arg, value=kw_value)
+ keywords.append(kw)
+ if not did_change:
+ return False
+ props_node.keywords = keywords
+ return True
diff --git a/src/py/reactpy/reactpy/_console/rewrite_keys.py b/src/py/reactpy/reactpy/_console/rewrite_keys.py
new file mode 100644
index 000000000..08db9e227
--- /dev/null
+++ b/src/py/reactpy/reactpy/_console/rewrite_keys.py
@@ -0,0 +1,112 @@
+from __future__ import annotations
+
+import ast
+import sys
+from pathlib import Path
+
+import click
+
+from reactpy import html
+from reactpy._console.ast_utils import (
+ ChangedNode,
+ find_element_constructor_usages,
+ rewrite_changed_nodes,
+)
+
+
+@click.command()
+@click.argument("paths", nargs=-1, type=click.Path(exists=True))
+def rewrite_keys(paths: list[str]) -> None:
+ """Rewrite files under the given paths using the new html element API.
+
+ The old API required users to pass a dictionary of attributes to html element
+ constructor functions. For example:
+
+ >>> html.div({"className": "x"}, "y")
+ {"tagName": "div", "attributes": {"className": "x"}, "children": ["y"]}
+
+ The latest API though allows for attributes to be passed as snake_cased keyword
+ arguments instead. The above example would be rewritten as:
+
+ >>> html.div("y", class_name="x")
+ {"tagName": "div", "attributes": {"class_name": "x"}, "children": ["y"]}
+
+ All snake_case attributes are converted to camelCase by the client where necessary.
+
+ ----- Notes -----
+
+ While this command does it's best to preserve as much of the original code as
+ possible, there are inevitably some limitations in doing this. As a result, we
+ recommend running your code formatter like Black against your code after executing
+ this command.
+
+ Additionally, We are unable to preserve the location of comments that lie within any
+ rewritten code. This command will place the comments in the code it plans to rewrite
+ just above its changes. As such it requires manual intervention to put those
+ comments back in their original location.
+ """
+ if sys.version_info < (3, 9): # nocov
+ msg = "This command requires Python>=3.9"
+ raise RuntimeError(msg)
+
+ for p in map(Path, paths):
+ for f in [p] if p.is_file() else p.rglob("*.py"):
+ result = generate_rewrite(file=f, source=f.read_text(encoding="utf-8"))
+ if result is not None:
+ f.write_text(result)
+
+
+def generate_rewrite(file: Path, source: str) -> str | None:
+ tree = ast.parse(source)
+
+ changed = find_nodes_to_change(tree)
+ if not changed:
+ log_could_not_rewrite(file, tree)
+ return None
+
+ new = rewrite_changed_nodes(file, source, tree, changed)
+ log_could_not_rewrite(file, ast.parse(new))
+
+ return new
+
+
+def find_nodes_to_change(tree: ast.AST) -> list[ChangedNode]:
+ changed: list[ChangedNode] = []
+ for el_info in find_element_constructor_usages(tree, add_props=True):
+ for kw in list(el_info.call.keywords):
+ if kw.arg == "key":
+ break
+ else:
+ continue
+
+ if isinstance(el_info.props, ast.Dict):
+ el_info.props.keys.append(ast.Constant("key"))
+ el_info.props.values.append(kw.value)
+ else:
+ el_info.props.keywords.append(ast.keyword(arg="key", value=kw.value))
+
+ el_info.call.keywords.remove(kw)
+ changed.append(ChangedNode(el_info.call, el_info.parents))
+
+ return changed
+
+
+def log_could_not_rewrite(file: Path, tree: ast.AST) -> None:
+ for node in ast.walk(tree):
+ if not (isinstance(node, ast.Call) and node.keywords):
+ continue
+
+ func = node.func
+ if isinstance(func, ast.Attribute):
+ name = func.attr
+ elif isinstance(func, ast.Name):
+ name = func.id
+ else:
+ continue
+
+ if (
+ name == "vdom"
+ or hasattr(html, name)
+ and any(kw.arg == "key" for kw in node.keywords)
+ ):
+ click.echo(f"Unable to rewrite usage at {file}:{node.lineno}")
diff --git a/src/py/reactpy/reactpy/_option.py b/src/py/reactpy/reactpy/_option.py
new file mode 100644
index 000000000..1db0857e3
--- /dev/null
+++ b/src/py/reactpy/reactpy/_option.py
@@ -0,0 +1,159 @@
+from __future__ import annotations
+
+import os
+from logging import getLogger
+from typing import Any, Callable, Generic, TypeVar, cast
+
+from reactpy._warnings import warn
+
+_O = TypeVar("_O")
+logger = getLogger(__name__)
+UNDEFINED = cast(Any, object())
+
+
+class Option(Generic[_O]):
+ """An option that can be set using an environment variable of the same name"""
+
+ def __init__(
+ self,
+ name: str,
+ default: _O = UNDEFINED,
+ mutable: bool = True,
+ parent: Option[_O] | None = None,
+ validator: Callable[[Any], _O] = lambda x: cast(_O, x),
+ ) -> None:
+ self._name = name
+ self._mutable = mutable
+ self._validator = validator
+ self._subscribers: list[Callable[[_O], None]] = []
+
+ if name in os.environ:
+ self._current = validator(os.environ[name])
+
+ if parent is not None:
+ if not (parent.mutable and self.mutable):
+ raise TypeError("Parent and child options must be mutable")
+ self._default = parent.default
+ parent.subscribe(self.set_current)
+ elif default is not UNDEFINED:
+ self._default = default
+ else:
+ raise TypeError("Must specify either a default or a parent option")
+
+ logger.debug(f"{self._name}={self.current}")
+
+ @property
+ def name(self) -> str:
+ """The name of this option (used to load environment variables)"""
+ return self._name
+
+ @property
+ def mutable(self) -> bool:
+ """Whether this option can be modified after being loaded"""
+ return self._mutable
+
+ @property
+ def default(self) -> _O:
+ """This option's default value"""
+ return self._default
+
+ @property
+ def current(self) -> _O:
+ try:
+ return self._current
+ except AttributeError:
+ return self._default
+
+ @current.setter
+ def current(self, new: _O) -> None:
+ self.set_current(new)
+
+ @current.deleter
+ def current(self) -> None:
+ self.unset()
+
+ def subscribe(self, handler: Callable[[_O], None]) -> Callable[[_O], None]:
+ """Register a callback that will be triggered when this option changes"""
+ if not self.mutable:
+ msg = "Immutable options cannot be subscribed to."
+ raise TypeError(msg)
+ self._subscribers.append(handler)
+ handler(self.current)
+ return handler
+
+ def is_set(self) -> bool:
+ """Whether this option has a value other than its default."""
+ return hasattr(self, "_current")
+
+ def set_current(self, new: Any) -> None:
+ """Set the value of this option
+
+ Raises a ``TypeError`` if this option is not :attr:`Option.mutable`.
+ """
+ old = self.current
+ if new is old:
+ return None
+
+ if not self._mutable:
+ msg = f"{self} cannot be modified after initial load"
+ raise TypeError(msg)
+
+ try:
+ new = self._current = self._validator(new)
+ except ValueError as error:
+ raise ValueError(f"Invalid value for {self._name}: {new!r}") from error
+
+ logger.debug(f"{self._name}={self._current}")
+ if new != old:
+ for sub_func in self._subscribers:
+ sub_func(new)
+
+ def set_default(self, new: _O) -> _O:
+ """Set the value of this option if not :meth:`Option.is_set`
+
+ Returns the current value (a la :meth:`dict.set_default`)
+ """
+ if not self.is_set():
+ self.set_current(new)
+ return self._current
+
+ def reload(self) -> None:
+ """Reload this option from its environment variable"""
+ self.set_current(os.environ.get(self._name, self._default))
+
+ def unset(self) -> None:
+ """Remove the current value, the default will be used until it is set again."""
+ if not self._mutable:
+ msg = f"{self} cannot be modified after initial load"
+ raise TypeError(msg)
+ old = self.current
+ if hasattr(self, "_current"):
+ delattr(self, "_current")
+ if self.current != old:
+ for sub_func in self._subscribers:
+ sub_func(self.current)
+
+ def __repr__(self) -> str:
+ return f"Option({self._name}={self.current!r})"
+
+
+class DeprecatedOption(Option[_O]):
+ """An option that will warn when it is accessed"""
+
+ def __init__(self, *args: Any, message: str, **kwargs: Any) -> None:
+ super().__init__(*args, **kwargs)
+ self._deprecation_message = message
+
+ @Option.current.getter # type: ignore
+ def current(self) -> _O:
+ try:
+ # we access the current value during init to debug log it
+ # no need to warn unless it's actually used. since this attr
+ # is only set after super().__init__ is called, we can check
+ # for it to determine if it's being accessed by a user.
+ msg = self._deprecation_message
+ except AttributeError:
+ pass
+ else:
+ warn(msg, DeprecationWarning)
+ return super().current
diff --git a/src/py/reactpy/reactpy/_warnings.py b/src/py/reactpy/reactpy/_warnings.py
new file mode 100644
index 000000000..c4520604d
--- /dev/null
+++ b/src/py/reactpy/reactpy/_warnings.py
@@ -0,0 +1,36 @@
+from collections.abc import Iterator
+from functools import wraps
+from inspect import currentframe
+from types import FrameType
+from typing import TYPE_CHECKING, Any
+from warnings import warn as _warn
+
+
+@wraps(_warn)
+def warn(*args: Any, **kwargs: Any) -> Any:
+ # warn at call site outside of ReactPy
+ _warn(*args, stacklevel=_frame_depth_in_module() + 1, **kwargs) # type: ignore
+
+
+if TYPE_CHECKING:
+ warn = _warn # noqa: F811
+
+
+def _frame_depth_in_module() -> int:
+ depth = 0
+ for frame in _iter_frames(2):
+ module_name = frame.f_globals.get("__name__")
+ if not module_name or not module_name.startswith("reactpy."):
+ break
+ depth += 1
+ return depth
+
+
+def _iter_frames(index: int = 1) -> Iterator[FrameType]:
+ frame = currentframe()
+ while frame is not None:
+ if index == 0:
+ yield frame
+ else:
+ index -= 1
+ frame = frame.f_back
diff --git a/src/py/reactpy/reactpy/backend/__init__.py b/src/py/reactpy/reactpy/backend/__init__.py
new file mode 100644
index 000000000..e08e50649
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/__init__.py
@@ -0,0 +1,22 @@
+import mimetypes
+from logging import getLogger
+
+_logger = getLogger(__name__)
+
+# Fix for missing mime types due to OS corruption/misconfiguration
+# Example: https://github.com/encode/starlette/issues/829
+if not mimetypes.inited:
+ mimetypes.init()
+for extension, mime_type in {
+ ".js": "application/javascript",
+ ".css": "text/css",
+ ".json": "application/json",
+}.items():
+ if not mimetypes.types_map.get(extension): # pragma: no cover
+ _logger.warning(
+ "Mime type '%s = %s' is missing. Please research how to "
+ "fix missing mime types on your operating system.",
+ extension,
+ mime_type,
+ )
+ mimetypes.add_type(mime_type, extension)
diff --git a/src/py/reactpy/reactpy/backend/_common.py b/src/py/reactpy/reactpy/backend/_common.py
new file mode 100644
index 000000000..b4d6af19c
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/_common.py
@@ -0,0 +1,144 @@
+from __future__ import annotations
+
+import asyncio
+import os
+from collections.abc import Awaitable, Sequence
+from dataclasses import dataclass
+from pathlib import Path, PurePosixPath
+from typing import TYPE_CHECKING, Any, cast
+
+from reactpy import __file__ as _reactpy_file_path
+from reactpy import html
+from reactpy.config import REACTPY_WEB_MODULES_DIR
+from reactpy.core.types import VdomDict
+from reactpy.utils import vdom_to_html
+
+if TYPE_CHECKING:
+ import uvicorn
+ from asgiref.typing import ASGIApplication
+
+PATH_PREFIX = PurePosixPath("/_reactpy")
+MODULES_PATH = PATH_PREFIX / "modules"
+ASSETS_PATH = PATH_PREFIX / "assets"
+STREAM_PATH = PATH_PREFIX / "stream"
+CLIENT_BUILD_DIR = Path(_reactpy_file_path).parent / "_static" / "app" / "dist"
+
+
+async def serve_with_uvicorn(
+ app: ASGIApplication | Any,
+ host: str,
+ port: int,
+ started: asyncio.Event | None,
+) -> None:
+ """Run a development server for an ASGI application"""
+ import uvicorn
+
+ server = uvicorn.Server(
+ uvicorn.Config(
+ app,
+ host=host,
+ port=port,
+ loop="asyncio",
+ )
+ )
+ server.config.setup_event_loop()
+ coros: list[Awaitable[Any]] = [server.serve()]
+
+ # If a started event is provided, then use it signal based on `server.started`
+ if started:
+ coros.append(_check_if_started(server, started))
+
+ try:
+ await asyncio.gather(*coros)
+ finally:
+ # Since we aren't using the uvicorn's `run()` API, we can't guarantee uvicorn's
+ # order of operations. So we need to make sure `shutdown()` always has an initialized
+ # list of `self.servers` to use.
+ if not hasattr(server, "servers"): # nocov
+ server.servers = []
+ await asyncio.wait_for(server.shutdown(), timeout=3)
+
+
+async def _check_if_started(server: uvicorn.Server, started: asyncio.Event) -> None:
+ while not server.started:
+ await asyncio.sleep(0.2)
+ started.set()
+
+
+def safe_client_build_dir_path(path: str) -> Path:
+ """Prevent path traversal out of :data:`CLIENT_BUILD_DIR`"""
+ return traversal_safe_path(
+ CLIENT_BUILD_DIR, *("index.html" if path in {"", "/"} else path).split("/")
+ )
+
+
+def safe_web_modules_dir_path(path: str) -> Path:
+ """Prevent path traversal out of :data:`reactpy.config.REACTPY_WEB_MODULES_DIR`"""
+ return traversal_safe_path(REACTPY_WEB_MODULES_DIR.current, *path.split("/"))
+
+
+def traversal_safe_path(root: str | Path, *unsafe: str | Path) -> Path:
+ """Raise a ``ValueError`` if the ``unsafe`` path resolves outside the root dir."""
+ root = os.path.abspath(root)
+
+ # Resolve relative paths but not symlinks - symlinks should be ok since their
+ # presence and where they point is under the control of the developer.
+ path = os.path.abspath(os.path.join(root, *unsafe))
+
+ if os.path.commonprefix([root, path]) != root:
+ # If the common prefix is not root directory we resolved outside the root dir
+ msg = "Unsafe path"
+ raise ValueError(msg)
+
+ return Path(path)
+
+
+def read_client_index_html(options: CommonOptions) -> str:
+ return (
+ (CLIENT_BUILD_DIR / "index.html")
+ .read_text()
+ .format(__head__=vdom_head_elements_to_html(options.head))
+ )
+
+
+def vdom_head_elements_to_html(head: Sequence[VdomDict] | VdomDict | str) -> str:
+ if isinstance(head, str):
+ return head
+ elif isinstance(head, dict):
+ if head.get("tagName") == "head":
+ head = cast(VdomDict, {**head, "tagName": ""})
+ return vdom_to_html(head)
+ else:
+ return vdom_to_html(html._(*head))
+
+
+@dataclass
+class CommonOptions:
+ """Options for ReactPy's built-in backed server implementations"""
+
+ head: Sequence[VdomDict] | VdomDict | str = (
+ html.title("ReactPy"),
+ html.link(
+ {
+ "rel": "icon",
+ "href": "/_reactpy/assets/reactpy-logo.ico",
+ "type": "image/x-icon",
+ }
+ ),
+ )
+ """Add elements to the ```` of the application.
+
+ For example, this can be used to customize the title of the page, link extra
+ scripts, or load stylesheets.
+ """
+
+ url_prefix: str = ""
+ """The URL prefix where ReactPy resources will be served from"""
+
+ serve_index_route: bool = True
+ """Automatically generate and serve the index route (``/``)"""
+
+ def __post_init__(self) -> None:
+ if self.url_prefix and not self.url_prefix.startswith("/"):
+ msg = "Expected 'url_prefix' to start with '/'"
+ raise ValueError(msg)
diff --git a/src/py/reactpy/reactpy/backend/default.py b/src/py/reactpy/reactpy/backend/default.py
new file mode 100644
index 000000000..37aad31af
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/default.py
@@ -0,0 +1,78 @@
+from __future__ import annotations
+
+import asyncio
+from logging import getLogger
+from sys import exc_info
+from typing import Any, NoReturn
+
+from reactpy.backend.types import BackendType
+from reactpy.backend.utils import SUPPORTED_BACKENDS, all_implementations
+from reactpy.types import RootComponentConstructor
+
+logger = getLogger(__name__)
+_DEFAULT_IMPLEMENTATION: BackendType[Any] | None = None
+
+
+# BackendType.Options
+class Options: # nocov
+ """Configuration options that can be provided to the backend.
+ This definition should not be used/instantiated. It exists only for
+ type hinting purposes."""
+
+ def __init__(self, *args: Any, **kwds: Any) -> NoReturn:
+ msg = "Default implementation has no options."
+ raise ValueError(msg)
+
+
+# BackendType.configure
+def configure(
+ app: Any, component: RootComponentConstructor, options: None = None
+) -> None:
+ """Configure the given app instance to display the given component"""
+ if options is not None: # nocov
+ msg = "Default implementation cannot be configured with options"
+ raise ValueError(msg)
+ return _default_implementation().configure(app, component)
+
+
+# BackendType.create_development_app
+def create_development_app() -> Any:
+ """Create an application instance for development purposes"""
+ return _default_implementation().create_development_app()
+
+
+# BackendType.serve_development_app
+async def serve_development_app(
+ app: Any,
+ host: str,
+ port: int,
+ started: asyncio.Event | None = None,
+) -> None:
+ """Run an application using a development server"""
+ return await _default_implementation().serve_development_app(
+ app, host, port, started
+ )
+
+
+def _default_implementation() -> BackendType[Any]:
+ """Get the first available server implementation"""
+ global _DEFAULT_IMPLEMENTATION # noqa: PLW0603
+
+ if _DEFAULT_IMPLEMENTATION is not None:
+ return _DEFAULT_IMPLEMENTATION
+
+ try:
+ implementation = next(all_implementations())
+ except StopIteration: # nocov
+ logger.debug("Backend implementation import failed", exc_info=exc_info())
+ supported_backends = ", ".join(SUPPORTED_BACKENDS)
+ msg = (
+ "It seems you haven't installed a backend. To resolve this issue, "
+ "you can install a backend by running:\n\n"
+ '\033[1mpip install "reactpy[starlette]"\033[0m\n\n'
+ f"Other supported backends include: {supported_backends}."
+ )
+ raise RuntimeError(msg) from None
+ else:
+ _DEFAULT_IMPLEMENTATION = implementation
+ return implementation
diff --git a/src/py/reactpy/reactpy/backend/fastapi.py b/src/py/reactpy/reactpy/backend/fastapi.py
new file mode 100644
index 000000000..a0137a3dc
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/fastapi.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+from fastapi import FastAPI
+
+from reactpy.backend import starlette
+
+# BackendType.Options
+Options = starlette.Options
+
+# BackendType.configure
+configure = starlette.configure
+
+
+# BackendType.create_development_app
+def create_development_app() -> FastAPI:
+ """Create a development ``FastAPI`` application instance."""
+ return FastAPI(debug=True)
+
+
+# BackendType.serve_development_app
+serve_development_app = starlette.serve_development_app
+
+use_connection = starlette.use_connection
+
+use_websocket = starlette.use_websocket
diff --git a/src/py/reactpy/reactpy/backend/flask.py b/src/py/reactpy/reactpy/backend/flask.py
new file mode 100644
index 000000000..faa979aa9
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/flask.py
@@ -0,0 +1,303 @@
+from __future__ import annotations
+
+import asyncio
+import json
+import logging
+import os
+from asyncio import Queue as AsyncQueue
+from dataclasses import dataclass
+from queue import Queue as ThreadQueue
+from threading import Event as ThreadEvent
+from threading import Thread
+from typing import Any, Callable, NamedTuple, NoReturn, cast
+
+from flask import (
+ Blueprint,
+ Flask,
+ Request,
+ copy_current_request_context,
+ request,
+ send_file,
+)
+from flask_cors import CORS
+from flask_sock import Sock
+from simple_websocket import Server as WebSocket
+from werkzeug.serving import BaseWSGIServer, make_server
+
+import reactpy
+from reactpy.backend._common import (
+ ASSETS_PATH,
+ MODULES_PATH,
+ PATH_PREFIX,
+ STREAM_PATH,
+ CommonOptions,
+ read_client_index_html,
+ safe_client_build_dir_path,
+ safe_web_modules_dir_path,
+)
+from reactpy.backend.hooks import ConnectionContext
+from reactpy.backend.hooks import use_connection as _use_connection
+from reactpy.backend.types import Connection, Location
+from reactpy.core.serve import serve_layout
+from reactpy.core.types import ComponentType, RootComponentConstructor
+from reactpy.utils import Ref
+
+logger = logging.getLogger(__name__)
+
+
+# BackendType.Options
+@dataclass
+class Options(CommonOptions):
+ """Render server config for :func:`reactpy.backend.flask.configure`"""
+
+ cors: bool | dict[str, Any] = False
+ """Enable or configure Cross Origin Resource Sharing (CORS)
+
+ For more information see docs for ``flask_cors.CORS``
+ """
+
+
+# BackendType.configure
+def configure(
+ app: Flask, component: RootComponentConstructor, options: Options | None = None
+) -> None:
+ """Configure the necessary ReactPy routes on the given app.
+
+ Parameters:
+ app: An application instance
+ component: A component constructor
+ options: Options for configuring server behavior
+ """
+ options = options or Options()
+
+ api_bp = Blueprint(f"reactpy_api_{id(app)}", __name__, url_prefix=str(PATH_PREFIX))
+ spa_bp = Blueprint(
+ f"reactpy_spa_{id(app)}", __name__, url_prefix=options.url_prefix
+ )
+
+ _setup_single_view_dispatcher_route(api_bp, options, component)
+ _setup_common_routes(api_bp, spa_bp, options)
+
+ app.register_blueprint(api_bp)
+ app.register_blueprint(spa_bp)
+
+
+# BackendType.create_development_app
+def create_development_app() -> Flask:
+ """Create an application instance for development purposes"""
+ os.environ["FLASK_DEBUG"] = "true"
+ return Flask(__name__)
+
+
+# BackendType.serve_development_app
+async def serve_development_app(
+ app: Flask,
+ host: str,
+ port: int,
+ started: asyncio.Event | None = None,
+) -> None:
+ """Run a development server for FastAPI"""
+ loop = asyncio.get_running_loop()
+ stopped = asyncio.Event()
+
+ server: Ref[BaseWSGIServer] = Ref()
+
+ def run_server() -> None:
+ server.current = make_server(host, port, app, threaded=True)
+ if started:
+ loop.call_soon_threadsafe(started.set)
+ try:
+ server.current.serve_forever() # type: ignore
+ finally:
+ loop.call_soon_threadsafe(stopped.set)
+
+ thread = Thread(target=run_server, daemon=True)
+ thread.start()
+
+ if started:
+ await started.wait()
+
+ try:
+ await stopped.wait()
+ finally:
+ # we may have exited because this task was cancelled
+ server.current.shutdown()
+ # the thread should eventually join
+ thread.join(timeout=3)
+ # just double check it happened
+ if thread.is_alive(): # nocov
+ msg = "Failed to shutdown server."
+ raise RuntimeError(msg)
+
+
+def use_websocket() -> WebSocket:
+ """A handle to the current websocket"""
+ return use_connection().carrier.websocket
+
+
+def use_request() -> Request:
+ """Get the current ``Request``"""
+ return use_connection().carrier.request
+
+
+def use_connection() -> Connection[_FlaskCarrier]:
+ """Get the current :class:`Connection`"""
+ conn = _use_connection()
+ if not isinstance(conn.carrier, _FlaskCarrier): # nocov
+ msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?"
+ raise TypeError(msg)
+ return conn
+
+
+def _setup_common_routes(
+ api_blueprint: Blueprint,
+ spa_blueprint: Blueprint,
+ options: Options,
+) -> None:
+ cors_options = options.cors
+ if cors_options: # nocov
+ cors_params = cors_options if isinstance(cors_options, dict) else {}
+ CORS(api_blueprint, **cors_params)
+
+ @api_blueprint.route(f"/{ASSETS_PATH.name}/")
+ def send_assets_dir(path: str = "") -> Any:
+ return send_file(safe_client_build_dir_path(f"assets/{path}"))
+
+ @api_blueprint.route(f"/{MODULES_PATH.name}/")
+ def send_modules_dir(path: str = "") -> Any:
+ return send_file(safe_web_modules_dir_path(path), mimetype="text/javascript")
+
+ index_html = read_client_index_html(options)
+
+ if options.serve_index_route:
+
+ @spa_blueprint.route("/")
+ @spa_blueprint.route("/")
+ def send_client_dir(_: str = "") -> Any:
+ return index_html
+
+
+def _setup_single_view_dispatcher_route(
+ api_blueprint: Blueprint, options: Options, constructor: RootComponentConstructor
+) -> None:
+ sock = Sock(api_blueprint)
+
+ def model_stream(ws: WebSocket, path: str = "") -> None:
+ def send(value: Any) -> None:
+ ws.send(json.dumps(value))
+
+ def recv() -> Any:
+ return json.loads(ws.receive())
+
+ _dispatch_in_thread(
+ ws,
+ # remove any url prefix from path
+ path[len(options.url_prefix) :],
+ constructor(),
+ send,
+ recv,
+ )
+
+ sock.route(STREAM_PATH.name, endpoint="without_path")(model_stream)
+ sock.route(f"{STREAM_PATH.name}/", endpoint="with_path")(model_stream)
+
+
+def _dispatch_in_thread(
+ websocket: WebSocket,
+ path: str,
+ component: ComponentType,
+ send: Callable[[Any], None],
+ recv: Callable[[], Any | None],
+) -> NoReturn:
+ dispatch_thread_info_created = ThreadEvent()
+ dispatch_thread_info_ref: reactpy.Ref[_DispatcherThreadInfo | None] = reactpy.Ref(
+ None
+ )
+
+ @copy_current_request_context
+ def run_dispatcher() -> None:
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ thread_send_queue: ThreadQueue[Any] = ThreadQueue()
+ async_recv_queue: AsyncQueue[Any] = AsyncQueue()
+
+ async def send_coro(value: Any) -> None:
+ thread_send_queue.put(value)
+
+ async def main() -> None:
+ search = request.query_string.decode()
+ await serve_layout(
+ reactpy.Layout(
+ ConnectionContext(
+ component,
+ value=Connection(
+ scope=request.environ,
+ location=Location(
+ pathname=f"/{path}",
+ search=f"?{search}" if search else "",
+ ),
+ carrier=_FlaskCarrier(request, websocket),
+ ),
+ ),
+ ),
+ send_coro,
+ async_recv_queue.get,
+ )
+
+ main_future = asyncio.ensure_future(main(), loop=loop)
+
+ dispatch_thread_info_ref.current = _DispatcherThreadInfo(
+ dispatch_loop=loop,
+ dispatch_future=main_future,
+ thread_send_queue=thread_send_queue,
+ async_recv_queue=async_recv_queue,
+ )
+ dispatch_thread_info_created.set()
+
+ loop.run_until_complete(main_future)
+
+ Thread(target=run_dispatcher, daemon=True).start()
+
+ dispatch_thread_info_created.wait()
+ dispatch_thread_info = cast(_DispatcherThreadInfo, dispatch_thread_info_ref.current)
+
+ if dispatch_thread_info is None:
+ raise RuntimeError("Failed to create dispatcher thread") # nocov
+
+ stop = ThreadEvent()
+
+ def run_send() -> None:
+ while not stop.is_set():
+ send(dispatch_thread_info.thread_send_queue.get())
+
+ Thread(target=run_send, daemon=True).start()
+
+ try:
+ while True:
+ value = recv()
+ dispatch_thread_info.dispatch_loop.call_soon_threadsafe(
+ dispatch_thread_info.async_recv_queue.put_nowait, value
+ )
+ finally: # nocov
+ dispatch_thread_info.dispatch_loop.call_soon_threadsafe(
+ dispatch_thread_info.dispatch_future.cancel
+ )
+
+
+class _DispatcherThreadInfo(NamedTuple):
+ dispatch_loop: asyncio.AbstractEventLoop
+ dispatch_future: asyncio.Future[Any]
+ thread_send_queue: ThreadQueue[Any]
+ async_recv_queue: AsyncQueue[Any]
+
+
+@dataclass
+class _FlaskCarrier:
+ """A simple wrapper for holding a Flask request and WebSocket"""
+
+ request: Request
+ """The current request object"""
+
+ websocket: WebSocket
+ """A handle to the current websocket"""
diff --git a/src/py/reactpy/reactpy/backend/hooks.py b/src/py/reactpy/reactpy/backend/hooks.py
new file mode 100644
index 000000000..ee4ce1b5c
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/hooks.py
@@ -0,0 +1,30 @@
+from __future__ import annotations
+
+from collections.abc import MutableMapping
+from typing import Any
+
+from reactpy.backend.types import Connection, Location
+from reactpy.core.hooks import create_context, use_context
+from reactpy.core.types import Context
+
+# backend implementations should establish this context at the root of an app
+ConnectionContext: Context[Connection[Any] | None] = create_context(None)
+
+
+def use_connection() -> Connection[Any]:
+ """Get the current :class:`~reactpy.backend.types.Connection`."""
+ conn = use_context(ConnectionContext)
+ if conn is None: # nocov
+ msg = "No backend established a connection."
+ raise RuntimeError(msg)
+ return conn
+
+
+def use_scope() -> MutableMapping[str, Any]:
+ """Get the current :class:`~reactpy.backend.types.Connection`'s scope."""
+ return use_connection().scope
+
+
+def use_location() -> Location:
+ """Get the current :class:`~reactpy.backend.types.Connection`'s location."""
+ return use_connection().location
diff --git a/src/py/reactpy/reactpy/backend/sanic.py b/src/py/reactpy/reactpy/backend/sanic.py
new file mode 100644
index 000000000..76eb0423e
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/sanic.py
@@ -0,0 +1,231 @@
+from __future__ import annotations
+
+import asyncio
+import json
+import logging
+from dataclasses import dataclass
+from typing import Any
+from urllib import parse as urllib_parse
+from uuid import uuid4
+
+from sanic import Blueprint, Sanic, request, response
+from sanic.config import Config
+from sanic.server.websockets.connection import WebSocketConnection
+from sanic_cors import CORS
+
+from reactpy.backend._common import (
+ ASSETS_PATH,
+ MODULES_PATH,
+ PATH_PREFIX,
+ STREAM_PATH,
+ CommonOptions,
+ read_client_index_html,
+ safe_client_build_dir_path,
+ safe_web_modules_dir_path,
+ serve_with_uvicorn,
+)
+from reactpy.backend.hooks import ConnectionContext
+from reactpy.backend.hooks import use_connection as _use_connection
+from reactpy.backend.types import Connection, Location
+from reactpy.core.layout import Layout
+from reactpy.core.serve import RecvCoroutine, SendCoroutine, Stop, serve_layout
+from reactpy.core.types import RootComponentConstructor
+
+logger = logging.getLogger(__name__)
+
+
+# BackendType.Options
+@dataclass
+class Options(CommonOptions):
+ """Render server config for :func:`reactpy.backend.sanic.configure`"""
+
+ cors: bool | dict[str, Any] = False
+ """Enable or configure Cross Origin Resource Sharing (CORS)
+
+ For more information see docs for ``sanic_cors.CORS``
+ """
+
+
+# BackendType.configure
+def configure(
+ app: Sanic[Any, Any],
+ component: RootComponentConstructor,
+ options: Options | None = None,
+) -> None:
+ """Configure an application instance to display the given component"""
+ options = options or Options()
+
+ spa_bp = Blueprint(f"reactpy_spa_{id(app)}", url_prefix=options.url_prefix)
+ api_bp = Blueprint(f"reactpy_api_{id(app)}", url_prefix=str(PATH_PREFIX))
+
+ _setup_common_routes(api_bp, spa_bp, options)
+ _setup_single_view_dispatcher_route(api_bp, component, options)
+
+ app.blueprint([spa_bp, api_bp])
+
+
+# BackendType.create_development_app
+def create_development_app() -> Sanic[Any, Any]:
+ """Return a :class:`Sanic` app instance in test mode"""
+ Sanic.test_mode = True
+ logger.warning("Sanic.test_mode is now active")
+ return Sanic(f"reactpy_development_app_{uuid4().hex}", Config())
+
+
+# BackendType.serve_development_app
+async def serve_development_app(
+ app: Sanic[Any, Any],
+ host: str,
+ port: int,
+ started: asyncio.Event | None = None,
+) -> None:
+ """Run a development server for :mod:`sanic`"""
+ await serve_with_uvicorn(app, host, port, started)
+
+
+def use_request() -> request.Request[Any, Any]:
+ """Get the current ``Request``"""
+ return use_connection().carrier.request
+
+
+def use_websocket() -> WebSocketConnection:
+ """Get the current websocket"""
+ return use_connection().carrier.websocket
+
+
+def use_connection() -> Connection[_SanicCarrier]:
+ """Get the current :class:`Connection`"""
+ conn = _use_connection()
+ if not isinstance(conn.carrier, _SanicCarrier): # nocov
+ msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Sanic server?"
+ raise TypeError(msg)
+ return conn
+
+
+def _setup_common_routes(
+ api_blueprint: Blueprint,
+ spa_blueprint: Blueprint,
+ options: Options,
+) -> None:
+ cors_options = options.cors
+ if cors_options: # nocov
+ cors_params = cors_options if isinstance(cors_options, dict) else {}
+ CORS(api_blueprint, **cors_params)
+
+ index_html = read_client_index_html(options)
+
+ async def single_page_app_files(
+ request: request.Request[Any, Any],
+ _: str = "",
+ ) -> response.HTTPResponse:
+ return response.html(index_html)
+
+ if options.serve_index_route:
+ spa_blueprint.add_route(
+ single_page_app_files,
+ "/",
+ name="single_page_app_files_root",
+ )
+ spa_blueprint.add_route(
+ single_page_app_files,
+ "/<_:path>",
+ name="single_page_app_files_path",
+ )
+
+ async def asset_files(
+ request: request.Request[Any, Any],
+ path: str = "",
+ ) -> response.HTTPResponse:
+ path = urllib_parse.unquote(path)
+ return await response.file(safe_client_build_dir_path(f"assets/{path}"))
+
+ api_blueprint.add_route(asset_files, f"/{ASSETS_PATH.name}/")
+
+ async def web_module_files(
+ request: request.Request[Any, Any],
+ path: str,
+ _: str = "", # this is not used
+ ) -> response.HTTPResponse:
+ path = urllib_parse.unquote(path)
+ return await response.file(
+ safe_web_modules_dir_path(path),
+ mime_type="text/javascript",
+ )
+
+ api_blueprint.add_route(web_module_files, f"/{MODULES_PATH.name}/")
+
+
+def _setup_single_view_dispatcher_route(
+ api_blueprint: Blueprint,
+ constructor: RootComponentConstructor,
+ options: Options,
+) -> None:
+ async def model_stream(
+ request: request.Request[Any, Any],
+ socket: WebSocketConnection,
+ path: str = "",
+ ) -> None:
+ asgi_app = getattr(request.app, "_asgi_app", None)
+ scope = asgi_app.transport.scope if asgi_app else {}
+ if not scope: # nocov
+ logger.warning("No scope. Sanic may not be running with an ASGI server")
+
+ send, recv = _make_send_recv_callbacks(socket)
+ await serve_layout(
+ Layout(
+ ConnectionContext(
+ constructor(),
+ value=Connection(
+ scope=scope,
+ location=Location(
+ pathname=f"/{path[len(options.url_prefix):]}",
+ search=(
+ f"?{request.query_string}"
+ if request.query_string
+ else ""
+ ),
+ ),
+ carrier=_SanicCarrier(request, socket),
+ ),
+ )
+ ),
+ send,
+ recv,
+ )
+
+ api_blueprint.add_websocket_route(
+ model_stream,
+ f"/{STREAM_PATH.name}",
+ name="model_stream_root",
+ )
+ api_blueprint.add_websocket_route(
+ model_stream,
+ f"/{STREAM_PATH.name}//",
+ name="model_stream_path",
+ )
+
+
+def _make_send_recv_callbacks(
+ socket: WebSocketConnection,
+) -> tuple[SendCoroutine, RecvCoroutine]:
+ async def sock_send(value: Any) -> None:
+ await socket.send(json.dumps(value))
+
+ async def sock_recv() -> Any:
+ data = await socket.recv()
+ if data is None:
+ raise Stop()
+ return json.loads(data)
+
+ return sock_send, sock_recv
+
+
+@dataclass
+class _SanicCarrier:
+ """A simple wrapper for holding connection information"""
+
+ request: request.Request[Sanic[Any, Any], Any]
+ """The current request object"""
+
+ websocket: WebSocketConnection
+ """A handle to the current websocket"""
diff --git a/src/py/reactpy/reactpy/backend/starlette.py b/src/py/reactpy/reactpy/backend/starlette.py
new file mode 100644
index 000000000..9bc68db47
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/starlette.py
@@ -0,0 +1,185 @@
+from __future__ import annotations
+
+import asyncio
+import json
+import logging
+from collections.abc import Awaitable
+from dataclasses import dataclass
+from typing import Any, Callable
+
+from exceptiongroup import BaseExceptionGroup
+from starlette.applications import Starlette
+from starlette.middleware.cors import CORSMiddleware
+from starlette.requests import Request
+from starlette.responses import HTMLResponse
+from starlette.staticfiles import StaticFiles
+from starlette.websockets import WebSocket, WebSocketDisconnect
+
+from reactpy.backend._common import (
+ ASSETS_PATH,
+ CLIENT_BUILD_DIR,
+ MODULES_PATH,
+ STREAM_PATH,
+ CommonOptions,
+ read_client_index_html,
+ serve_with_uvicorn,
+)
+from reactpy.backend.hooks import ConnectionContext
+from reactpy.backend.hooks import use_connection as _use_connection
+from reactpy.backend.types import Connection, Location
+from reactpy.config import REACTPY_WEB_MODULES_DIR
+from reactpy.core.layout import Layout
+from reactpy.core.serve import RecvCoroutine, SendCoroutine, serve_layout
+from reactpy.core.types import RootComponentConstructor
+
+logger = logging.getLogger(__name__)
+
+
+# BackendType.Options
+@dataclass
+class Options(CommonOptions):
+ """Render server config for :func:`reactpy.backend.starlette.configure`"""
+
+ cors: bool | dict[str, Any] = False
+ """Enable or configure Cross Origin Resource Sharing (CORS)
+
+ For more information see docs for ``starlette.middleware.cors.CORSMiddleware``
+ """
+
+
+# BackendType.configure
+def configure(
+ app: Starlette,
+ component: RootComponentConstructor,
+ options: Options | None = None,
+) -> None:
+ """Configure the necessary ReactPy routes on the given app.
+
+ Parameters:
+ app: An application instance
+ component: A component constructor
+ options: Options for configuring server behavior
+ """
+ options = options or Options()
+
+ # this route should take priority so set up it up first
+ _setup_single_view_dispatcher_route(options, app, component)
+
+ _setup_common_routes(options, app)
+
+
+# BackendType.create_development_app
+def create_development_app() -> Starlette:
+ """Return a :class:`Starlette` app instance in debug mode"""
+ return Starlette(debug=True)
+
+
+# BackendType.serve_development_app
+async def serve_development_app(
+ app: Starlette,
+ host: str,
+ port: int,
+ started: asyncio.Event | None = None,
+) -> None:
+ """Run a development server for starlette"""
+ await serve_with_uvicorn(app, host, port, started)
+
+
+def use_websocket() -> WebSocket:
+ """Get the current WebSocket object"""
+ return use_connection().carrier
+
+
+def use_connection() -> Connection[WebSocket]:
+ conn = _use_connection()
+ if not isinstance(conn.carrier, WebSocket): # nocov
+ msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?"
+ raise TypeError(msg)
+ return conn
+
+
+def _setup_common_routes(options: Options, app: Starlette) -> None:
+ cors_options = options.cors
+ if cors_options: # nocov
+ cors_params = (
+ cors_options if isinstance(cors_options, dict) else {"allow_origins": ["*"]}
+ )
+ app.add_middleware(CORSMiddleware, **cors_params)
+
+ # This really should be added to the APIRouter, but there's a bug in Starlette
+ # BUG: https://github.com/tiangolo/fastapi/issues/1469
+ url_prefix = options.url_prefix
+
+ app.mount(
+ str(MODULES_PATH),
+ StaticFiles(directory=REACTPY_WEB_MODULES_DIR.current, check_dir=False),
+ )
+ app.mount(
+ str(ASSETS_PATH),
+ StaticFiles(directory=CLIENT_BUILD_DIR / "assets", check_dir=False),
+ )
+ # register this last so it takes least priority
+ index_route = _make_index_route(options)
+
+ if options.serve_index_route:
+ app.add_route(f"{url_prefix}/", index_route)
+ app.add_route(url_prefix + "/{path:path}", index_route)
+
+
+def _make_index_route(options: Options) -> Callable[[Request], Awaitable[HTMLResponse]]:
+ index_html = read_client_index_html(options)
+
+ async def serve_index(request: Request) -> HTMLResponse:
+ return HTMLResponse(index_html)
+
+ return serve_index
+
+
+def _setup_single_view_dispatcher_route(
+ options: Options, app: Starlette, component: RootComponentConstructor
+) -> None:
+ async def model_stream(socket: WebSocket) -> None:
+ await socket.accept()
+ send, recv = _make_send_recv_callbacks(socket)
+
+ pathname = "/" + socket.scope["path_params"].get("path", "")
+ pathname = pathname[len(options.url_prefix) :] or "/"
+ search = socket.scope["query_string"].decode()
+
+ try:
+ await serve_layout(
+ Layout(
+ ConnectionContext(
+ component(),
+ value=Connection(
+ scope=socket.scope,
+ location=Location(pathname, f"?{search}" if search else ""),
+ carrier=socket,
+ ),
+ )
+ ),
+ send,
+ recv,
+ )
+ except BaseExceptionGroup as egroup:
+ for e in egroup.exceptions:
+ if isinstance(e, WebSocketDisconnect):
+ logger.info(f"WebSocket disconnect: {e.code}")
+ break
+ else: # nocov
+ raise
+
+ app.add_websocket_route(str(STREAM_PATH), model_stream)
+ app.add_websocket_route(f"{STREAM_PATH}/{{path:path}}", model_stream)
+
+
+def _make_send_recv_callbacks(
+ socket: WebSocket,
+) -> tuple[SendCoroutine, RecvCoroutine]:
+ async def sock_send(value: Any) -> None:
+ await socket.send_text(json.dumps(value))
+
+ async def sock_recv() -> Any:
+ return json.loads(await socket.receive_text())
+
+ return sock_send, sock_recv
diff --git a/src/py/reactpy/reactpy/backend/tornado.py b/src/py/reactpy/reactpy/backend/tornado.py
new file mode 100644
index 000000000..8f540ddb4
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/tornado.py
@@ -0,0 +1,235 @@
+from __future__ import annotations
+
+import asyncio
+import json
+from asyncio import Queue as AsyncQueue
+from asyncio.futures import Future
+from typing import Any
+from urllib.parse import urljoin
+
+from tornado.httpserver import HTTPServer
+from tornado.httputil import HTTPServerRequest
+from tornado.log import enable_pretty_logging
+from tornado.platform.asyncio import AsyncIOMainLoop
+from tornado.web import Application, RequestHandler, StaticFileHandler
+from tornado.websocket import WebSocketHandler
+from tornado.wsgi import WSGIContainer
+from typing_extensions import TypeAlias
+
+from reactpy.backend._common import (
+ ASSETS_PATH,
+ CLIENT_BUILD_DIR,
+ MODULES_PATH,
+ STREAM_PATH,
+ CommonOptions,
+ read_client_index_html,
+)
+from reactpy.backend.hooks import ConnectionContext
+from reactpy.backend.hooks import use_connection as _use_connection
+from reactpy.backend.types import Connection, Location
+from reactpy.config import REACTPY_WEB_MODULES_DIR
+from reactpy.core.layout import Layout
+from reactpy.core.serve import serve_layout
+from reactpy.core.types import ComponentConstructor
+
+# BackendType.Options
+Options = CommonOptions
+
+
+# BackendType.configure
+def configure(
+ app: Application,
+ component: ComponentConstructor,
+ options: CommonOptions | None = None,
+) -> None:
+ """Configure the necessary ReactPy routes on the given app.
+
+ Parameters:
+ app: An application instance
+ component: A component constructor
+ options: Options for configuring server behavior
+ """
+ options = options or Options()
+ _add_handler(
+ app,
+ options,
+ (
+ # this route should take priority so set up it up first
+ _setup_single_view_dispatcher_route(component, options)
+ + _setup_common_routes(options)
+ ),
+ )
+
+
+# BackendType.create_development_app
+def create_development_app() -> Application:
+ return Application(debug=True)
+
+
+# BackendType.serve_development_app
+async def serve_development_app(
+ app: Application,
+ host: str,
+ port: int,
+ started: asyncio.Event | None = None,
+) -> None:
+ enable_pretty_logging()
+
+ AsyncIOMainLoop.current().install()
+
+ server = HTTPServer(app)
+ server.listen(port, host)
+
+ if started:
+ # at this point the server is accepting connection
+ started.set()
+
+ try:
+ # block forever - tornado has already set up its own background tasks
+ await asyncio.get_running_loop().create_future()
+ finally:
+ # stop accepting new connections
+ server.stop()
+ # wait for existing connections to complete
+ await server.close_all_connections()
+
+
+def use_request() -> HTTPServerRequest:
+ """Get the current ``HTTPServerRequest``"""
+ return use_connection().carrier
+
+
+def use_connection() -> Connection[HTTPServerRequest]:
+ conn = _use_connection()
+ if not isinstance(conn.carrier, HTTPServerRequest): # nocov
+ msg = f"Connection has unexpected carrier {conn.carrier}. Are you running with a Flask server?"
+ raise TypeError(msg)
+ return conn
+
+
+_RouteHandlerSpecs: TypeAlias = "list[tuple[str, type[RequestHandler], Any]]"
+
+
+def _setup_common_routes(options: Options) -> _RouteHandlerSpecs:
+ return [
+ (
+ rf"{MODULES_PATH}/(.*)",
+ StaticFileHandler,
+ {"path": str(REACTPY_WEB_MODULES_DIR.current)},
+ ),
+ (
+ rf"{ASSETS_PATH}/(.*)",
+ StaticFileHandler,
+ {"path": str(CLIENT_BUILD_DIR / "assets")},
+ ),
+ ] + (
+ [
+ (
+ r"/(.*)",
+ IndexHandler,
+ {"index_html": read_client_index_html(options)},
+ ),
+ ]
+ if options.serve_index_route
+ else []
+ )
+
+
+def _add_handler(
+ app: Application, options: Options, handlers: _RouteHandlerSpecs
+) -> None:
+ prefixed_handlers: list[Any] = [
+ (urljoin(options.url_prefix, route_pattern), *tuple(handler_info))
+ for route_pattern, *handler_info in handlers
+ ]
+ app.add_handlers(r".*", prefixed_handlers)
+
+
+def _setup_single_view_dispatcher_route(
+ constructor: ComponentConstructor, options: Options
+) -> _RouteHandlerSpecs:
+ return [
+ (
+ rf"{STREAM_PATH}/(.*)",
+ ModelStreamHandler,
+ {"component_constructor": constructor, "url_prefix": options.url_prefix},
+ ),
+ (
+ str(STREAM_PATH),
+ ModelStreamHandler,
+ {"component_constructor": constructor, "url_prefix": options.url_prefix},
+ ),
+ ]
+
+
+class IndexHandler(RequestHandler):
+ _index_html: str
+
+ def initialize(self, index_html: str) -> None:
+ self._index_html = index_html
+
+ async def get(self, _: str) -> None:
+ self.finish(self._index_html)
+
+
+class ModelStreamHandler(WebSocketHandler):
+ """A web-socket handler that serves up a new model stream to each new client"""
+
+ _dispatch_future: Future[None]
+ _message_queue: AsyncQueue[str]
+
+ def initialize(
+ self, component_constructor: ComponentConstructor, url_prefix: str
+ ) -> None:
+ self._component_constructor = component_constructor
+ self._url_prefix = url_prefix
+
+ async def open(self, path: str = "", *args: Any, **kwargs: Any) -> None:
+ message_queue: AsyncQueue[str] = AsyncQueue()
+
+ async def send(value: Any) -> None:
+ await self.write_message(json.dumps(value))
+
+ async def recv() -> Any:
+ return json.loads(await message_queue.get())
+
+ self._message_queue = message_queue
+ self._dispatch_future = asyncio.ensure_future(
+ serve_layout(
+ Layout(
+ ConnectionContext(
+ self._component_constructor(),
+ value=Connection(
+ scope=_FAKE_WSGI_CONTAINER.environ(self.request),
+ location=Location(
+ pathname=f"/{path[len(self._url_prefix):]}",
+ search=(
+ f"?{self.request.query}"
+ if self.request.query
+ else ""
+ ),
+ ),
+ carrier=self.request,
+ ),
+ )
+ ),
+ send,
+ recv,
+ )
+ )
+
+ async def on_message(self, message: str | bytes) -> None:
+ await self._message_queue.put(
+ message if isinstance(message, str) else message.decode()
+ )
+
+ def on_close(self) -> None:
+ if not self._dispatch_future.done():
+ self._dispatch_future.cancel()
+
+
+# The interface for WSGIContainer.environ changed in Tornado version 6.3 from
+# a staticmethod to an instance method. Since we're not that concerned with
+# the details of the WSGI app itself, we can just use a fake one.
+# see: https://github.com/tornadoweb/tornado/pull/3231#issuecomment-1518957578
+_FAKE_WSGI_CONTAINER = WSGIContainer(lambda *a, **kw: iter([]))
diff --git a/src/py/reactpy/reactpy/backend/types.py b/src/py/reactpy/reactpy/backend/types.py
new file mode 100644
index 000000000..51e7bef04
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/types.py
@@ -0,0 +1,76 @@
+from __future__ import annotations
+
+import asyncio
+from collections.abc import MutableMapping
+from dataclasses import dataclass
+from typing import Any, Callable, Generic, Protocol, TypeVar, runtime_checkable
+
+from reactpy.core.types import RootComponentConstructor
+
+_App = TypeVar("_App")
+
+
+@runtime_checkable
+class BackendType(Protocol[_App]):
+ """Common interface for built-in web server/framework integrations"""
+
+ Options: Callable[..., Any]
+ """A constructor for options passed to :meth:`BackendType.configure`"""
+
+ def configure(
+ self,
+ app: _App,
+ component: RootComponentConstructor,
+ options: Any | None = None,
+ ) -> None:
+ """Configure the given app instance to display the given component"""
+
+ def create_development_app(self) -> _App:
+ """Create an application instance for development purposes"""
+
+ async def serve_development_app(
+ self,
+ app: _App,
+ host: str,
+ port: int,
+ started: asyncio.Event | None = None,
+ ) -> None:
+ """Run an application using a development server"""
+
+
+_Carrier = TypeVar("_Carrier")
+
+
+@dataclass
+class Connection(Generic[_Carrier]):
+ """Represents a connection with a client"""
+
+ scope: MutableMapping[str, Any]
+ """An ASGI scope or WSGI environment dictionary"""
+
+ location: Location
+ """The current location (URL)"""
+
+ carrier: _Carrier
+ """How the connection is mediated. For example, a request or websocket.
+
+ This typically depends on the backend implementation.
+ """
+
+
+@dataclass
+class Location:
+ """Represents the current location (URL)
+
+ Analogous to, but not necessarily identical to, the client-side
+ ``document.location`` object.
+ """
+
+ pathname: str
+ """the path of the URL for the location"""
+
+ search: str
+ """A search or query string - a '?' followed by the parameters of the URL.
+
+ If there are no search parameters this should be an empty string
+ """
diff --git a/src/py/reactpy/reactpy/backend/utils.py b/src/py/reactpy/reactpy/backend/utils.py
new file mode 100644
index 000000000..74e87bb7b
--- /dev/null
+++ b/src/py/reactpy/reactpy/backend/utils.py
@@ -0,0 +1,87 @@
+from __future__ import annotations
+
+import asyncio
+import logging
+import socket
+import sys
+from collections.abc import Iterator
+from contextlib import closing
+from importlib import import_module
+from typing import Any
+
+from reactpy.backend.types import BackendType
+from reactpy.types import RootComponentConstructor
+
+logger = logging.getLogger(__name__)
+
+SUPPORTED_BACKENDS = (
+ "fastapi",
+ "sanic",
+ "tornado",
+ "flask",
+ "starlette",
+)
+
+
+def run(
+ component: RootComponentConstructor,
+ host: str = "127.0.0.1",
+ port: int | None = None,
+ implementation: BackendType[Any] | None = None,
+) -> None:
+ """Run a component with a development server"""
+ logger.warning(_DEVELOPMENT_RUN_FUNC_WARNING)
+
+ implementation = implementation or import_module("reactpy.backend.default")
+ app = implementation.create_development_app()
+ implementation.configure(app, component)
+ port = port or find_available_port(host)
+ app_cls = type(app)
+
+ logger.info(
+ "ReactPy is running with '%s.%s' at http://%s:%s",
+ app_cls.__module__,
+ app_cls.__name__,
+ host,
+ port,
+ )
+ asyncio.run(implementation.serve_development_app(app, host, port))
+
+
+def find_available_port(host: str, port_min: int = 8000, port_max: int = 9000) -> int:
+ """Get a port that's available for the given host and port range"""
+ for port in range(port_min, port_max):
+ with closing(socket.socket()) as sock:
+ try:
+ if sys.platform in ("linux", "darwin"):
+ # Fixes bug on Unix-like systems where every time you restart the
+ # server you'll get a different port on Linux. This cannot be set
+ # on Windows otherwise address will always be reused.
+ # Ref: https://stackoverflow.com/a/19247688/3159288
+ sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ sock.bind((host, port))
+ except OSError:
+ pass
+ else:
+ return port
+ msg = f"Host {host!r} has no available port in range {port_max}-{port_max}"
+ raise RuntimeError(msg)
+
+
+def all_implementations() -> Iterator[BackendType[Any]]:
+ """Yield all available server implementations"""
+ for name in SUPPORTED_BACKENDS:
+ try:
+ import_module(name)
+ except ImportError: # nocov
+ logger.debug("Failed to import %s", name, exc_info=True)
+ continue
+
+ reactpy_backend_name = f"{__name__.rsplit('.', 1)[0]}.{name}"
+ yield import_module(reactpy_backend_name)
+
+
+_DEVELOPMENT_RUN_FUNC_WARNING = """\
+The `run()` function is only intended for testing during development! To run \
+in production, refer to the docs on how to use reactpy.backend.*.configure.\
+"""
diff --git a/src/py/reactpy/reactpy/config.py b/src/py/reactpy/reactpy/config.py
new file mode 100644
index 000000000..d08cdc218
--- /dev/null
+++ b/src/py/reactpy/reactpy/config.py
@@ -0,0 +1,90 @@
+"""
+ReactPy provides a series of configuration options that can be set using environment
+variables or, for those which allow it, a programmatic interface.
+"""
+
+from __future__ import annotations
+
+from pathlib import Path
+from tempfile import TemporaryDirectory
+
+from reactpy._option import Option
+
+TRUE_VALUES = {"true", "1"}
+FALSE_VALUES = {"false", "0"}
+
+
+def boolean(value: str | bool | int) -> bool:
+ if isinstance(value, bool):
+ return value
+ elif isinstance(value, int):
+ return bool(value)
+ elif not isinstance(value, str):
+ raise TypeError(f"Expected str or bool, got {type(value).__name__}")
+
+ if value.lower() in TRUE_VALUES:
+ return True
+ elif value.lower() in FALSE_VALUES:
+ return False
+ else:
+ raise ValueError(
+ f"Invalid boolean value {value!r} - expected "
+ f"one of {list(TRUE_VALUES | FALSE_VALUES)}"
+ )
+
+
+REACTPY_DEBUG_MODE = Option(
+ "REACTPY_DEBUG_MODE", default=False, validator=boolean, mutable=True
+)
+"""Get extra logs and validation checks at the cost of performance.
+
+This will enable the following:
+
+- :data:`REACTPY_CHECK_VDOM_SPEC`
+- :data:`REACTPY_CHECK_JSON_ATTRS`
+"""
+
+REACTPY_CHECK_VDOM_SPEC = Option("REACTPY_CHECK_VDOM_SPEC", parent=REACTPY_DEBUG_MODE)
+"""Checks which ensure VDOM is rendered to spec
+
+For more info on the VDOM spec, see here: :ref:`VDOM JSON Schema`
+"""
+
+REACTPY_CHECK_JSON_ATTRS = Option("REACTPY_CHECK_JSON_ATTRS", parent=REACTPY_DEBUG_MODE)
+"""Checks that all VDOM attributes are JSON serializable
+
+The VDOM spec is not able to enforce this on its own since attributes could anything.
+"""
+
+# Because these web modules will be linked dynamically at runtime this can be temporary.
+# Assigning to a variable here ensures that the directory is not deleted until the end
+# of the program.
+_DEFAULT_WEB_MODULES_DIR = TemporaryDirectory()
+
+REACTPY_WEB_MODULES_DIR = Option(
+ "REACTPY_WEB_MODULES_DIR",
+ default=Path(_DEFAULT_WEB_MODULES_DIR.name),
+ validator=Path,
+)
+"""The location ReactPy will use to store its client application
+
+This directory **MUST** be treated as a black box. Downstream applications **MUST NOT**
+assume anything about the structure of this directory see :mod:`reactpy.web.module` for a
+set of publicly available APIs for working with the client.
+"""
+
+REACTPY_TESTING_DEFAULT_TIMEOUT = Option(
+ "REACTPY_TESTING_DEFAULT_TIMEOUT",
+ 5.0,
+ mutable=False,
+ validator=float,
+)
+"""A default timeout for testing utilities in ReactPy"""
+
+REACTPY_ASYNC_RENDERING = Option(
+ "REACTPY_ASYNC_RENDERING",
+ default=False,
+ mutable=True,
+ validator=boolean,
+)
+"""Whether to render components asynchronously. This is currently an experimental feature."""
diff --git a/tests/__init__.py b/src/py/reactpy/reactpy/core/__init__.py
similarity index 100%
rename from tests/__init__.py
rename to src/py/reactpy/reactpy/core/__init__.py
diff --git a/src/py/reactpy/reactpy/core/_f_back.py b/src/py/reactpy/reactpy/core/_f_back.py
new file mode 100644
index 000000000..fe1a6c10c
--- /dev/null
+++ b/src/py/reactpy/reactpy/core/_f_back.py
@@ -0,0 +1,24 @@
+from __future__ import annotations
+
+import inspect
+from types import FrameType
+
+
+def f_module_name(index: int = 0) -> str:
+ frame = f_back(index + 1)
+ if frame is None:
+ return "" # nocov
+ name = frame.f_globals.get("__name__", "")
+ if not isinstance(name, str):
+ raise TypeError("Expected module name to be a string") # nocov
+ return name
+
+
+def f_back(index: int = 0) -> FrameType | None:
+ frame = inspect.currentframe()
+ while frame is not None:
+ if index < 0:
+ return frame
+ frame = frame.f_back
+ index -= 1
+ return None # nocov
diff --git a/src/py/reactpy/reactpy/core/_life_cycle_hook.py b/src/py/reactpy/reactpy/core/_life_cycle_hook.py
new file mode 100644
index 000000000..88d3386a8
--- /dev/null
+++ b/src/py/reactpy/reactpy/core/_life_cycle_hook.py
@@ -0,0 +1,244 @@
+from __future__ import annotations
+
+import logging
+from asyncio import Event, Task, create_task, gather
+from typing import Any, Callable, Protocol, TypeVar
+
+from anyio import Semaphore
+
+from reactpy.core._thread_local import ThreadLocal
+from reactpy.core.types import ComponentType, Context, ContextProviderType
+
+T = TypeVar("T")
+
+
+class EffectFunc(Protocol):
+ async def __call__(self, stop: Event) -> None: ...
+
+
+logger = logging.getLogger(__name__)
+
+_HOOK_STATE: ThreadLocal[list[LifeCycleHook]] = ThreadLocal(list)
+
+
+def current_hook() -> LifeCycleHook:
+ """Get the current :class:`LifeCycleHook`"""
+ hook_stack = _HOOK_STATE.get()
+ if not hook_stack:
+ msg = "No life cycle hook is active. Are you rendering in a layout?"
+ raise RuntimeError(msg)
+ return hook_stack[-1]
+
+
+class LifeCycleHook:
+ """An object which manages the "life cycle" of a layout component.
+
+ The "life cycle" of a component is the set of events which occur from the time
+ a component is first rendered until it is removed from the layout. The life cycle
+ is ultimately driven by the layout itself, but components can "hook" into those
+ events to perform actions. Components gain access to their own life cycle hook
+ by calling :func:`current_hook`. They can then perform actions such as:
+
+ 1. Adding state via :meth:`use_state`
+ 2. Adding effects via :meth:`add_effect`
+ 3. Setting or getting context providers via
+ :meth:`LifeCycleHook.set_context_provider` and
+ :meth:`get_context_provider` respectively.
+
+ Components can request access to their own life cycle events and state through hooks
+ while :class:`~reactpy.core.proto.LayoutType` objects drive drive the life cycle
+ forward by triggering events and rendering view changes.
+
+ Example:
+
+ If removed from the complexities of a layout, a very simplified full life cycle
+ for a single component with no child components would look a bit like this:
+
+ .. testcode::
+
+ from reactpy.core._life_cycle_hook import LifeCycleHook
+ from reactpy.core.hooks import current_hook
+
+ # this function will come from a layout implementation
+ schedule_render = lambda: ...
+
+ # --- start life cycle ---
+
+ hook = LifeCycleHook(schedule_render)
+
+ # --- start render cycle ---
+
+ component = ...
+ await hook.affect_component_will_render(component)
+ try:
+ # render the component
+ ...
+
+ # the component may access the current hook
+ assert current_hook() is hook
+
+ # and save state or add effects
+ current_hook().use_state(lambda: ...)
+
+ async def my_effect(stop_event):
+ ...
+
+ current_hook().add_effect(my_effect)
+ finally:
+ await hook.affect_component_did_render()
+
+ # This should only be called after the full set of changes associated with a
+ # given render have been completed.
+ await hook.affect_layout_did_render()
+
+ # Typically an event occurs and a new render is scheduled, thus beginning
+ # the render cycle anew.
+ hook.schedule_render()
+
+
+ # --- end render cycle ---
+
+ hook.affect_component_will_unmount()
+ del hook
+
+ # --- end render cycle ---
+ """
+
+ __slots__ = (
+ "__weakref__",
+ "_context_providers",
+ "_current_state_index",
+ "_effect_funcs",
+ "_effect_stops",
+ "_effect_tasks",
+ "_render_access",
+ "_rendered_atleast_once",
+ "_schedule_render_callback",
+ "_scheduled_render",
+ "_state",
+ "component",
+ )
+
+ component: ComponentType
+
+ def __init__(
+ self,
+ schedule_render: Callable[[], None],
+ ) -> None:
+ self._context_providers: dict[Context[Any], ContextProviderType[Any]] = {}
+ self._schedule_render_callback = schedule_render
+ self._scheduled_render = False
+ self._rendered_atleast_once = False
+ self._current_state_index = 0
+ self._state: tuple[Any, ...] = ()
+ self._effect_funcs: list[EffectFunc] = []
+ self._effect_tasks: list[Task[None]] = []
+ self._effect_stops: list[Event] = []
+ self._render_access = Semaphore(1) # ensure only one render at a time
+
+ def schedule_render(self) -> None:
+ if self._scheduled_render:
+ return None
+ try:
+ self._schedule_render_callback()
+ except Exception:
+ msg = f"Failed to schedule render via {self._schedule_render_callback}"
+ logger.exception(msg)
+ else:
+ self._scheduled_render = True
+
+ def use_state(self, function: Callable[[], T]) -> T:
+ """Add state to this hook
+
+ If this hook has not yet rendered, the state is appended to the state tuple.
+ Otherwise, the state is retrieved from the tuple. This allows state to be
+ preserved across renders.
+ """
+ if not self._rendered_atleast_once:
+ # since we're not initialized yet we're just appending state
+ result = function()
+ self._state += (result,)
+ else:
+ # once finalized we iterate over each succesively used piece of state
+ result = self._state[self._current_state_index]
+ self._current_state_index += 1
+ return result
+
+ def add_effect(self, effect_func: EffectFunc) -> None:
+ """Add an effect to this hook
+
+ A task to run the effect is created when the component is done rendering.
+ When the component will be unmounted, the event passed to the effect is
+ triggered and the task is awaited. The effect should eventually halt after
+ the event is triggered.
+ """
+ self._effect_funcs.append(effect_func)
+
+ def set_context_provider(self, provider: ContextProviderType[Any]) -> None:
+ """Set a context provider for this hook
+
+ The context provider will be used to provide state to any child components
+ of this hook's component which request a context provider of the same type.
+ """
+ self._context_providers[provider.type] = provider
+
+ def get_context_provider(
+ self, context: Context[T]
+ ) -> ContextProviderType[T] | None:
+ """Get a context provider for this hook of the given type
+
+ The context provider will have been set by a parent component. If no provider
+ is found, ``None`` is returned.
+ """
+ return self._context_providers.get(context)
+
+ async def affect_component_will_render(self, component: ComponentType) -> None:
+ """The component is about to render"""
+ await self._render_access.acquire()
+ self._scheduled_render = False
+ self.component = component
+ self.set_current()
+
+ async def affect_component_did_render(self) -> None:
+ """The component completed a render"""
+ self.unset_current()
+ self._rendered_atleast_once = True
+ self._current_state_index = 0
+ self._render_access.release()
+ del self.component
+
+ async def affect_layout_did_render(self) -> None:
+ """The layout completed a render"""
+ stop = Event()
+ self._effect_stops.append(stop)
+ self._effect_tasks.extend(create_task(e(stop)) for e in self._effect_funcs)
+ self._effect_funcs.clear()
+
+ async def affect_component_will_unmount(self) -> None:
+ """The component is about to be removed from the layout"""
+ for stop in self._effect_stops:
+ stop.set()
+ self._effect_stops.clear()
+ try:
+ await gather(*self._effect_tasks)
+ except Exception:
+ logger.exception("Error in effect")
+ finally:
+ self._effect_tasks.clear()
+
+ def set_current(self) -> None:
+ """Set this hook as the active hook in this thread
+
+ This method is called by a layout before entering the render method
+ of this hook's associated component.
+ """
+ hook_stack = _HOOK_STATE.get()
+ if hook_stack:
+ parent = hook_stack[-1]
+ self._context_providers.update(parent._context_providers)
+ hook_stack.append(self)
+
+ def unset_current(self) -> None:
+ """Unset this hook as the active hook in this thread"""
+ if _HOOK_STATE.get().pop() is not self:
+ raise RuntimeError("Hook stack is in an invalid state") # nocov
diff --git a/src/py/reactpy/reactpy/core/_thread_local.py b/src/py/reactpy/reactpy/core/_thread_local.py
new file mode 100644
index 000000000..b3d6a14b0
--- /dev/null
+++ b/src/py/reactpy/reactpy/core/_thread_local.py
@@ -0,0 +1,21 @@
+from threading import Thread, current_thread
+from typing import Callable, Generic, TypeVar
+from weakref import WeakKeyDictionary
+
+_StateType = TypeVar("_StateType")
+
+
+class ThreadLocal(Generic[_StateType]):
+ """Utility for managing per-thread state information"""
+
+ def __init__(self, default: Callable[[], _StateType]):
+ self._default = default
+ self._state: WeakKeyDictionary[Thread, _StateType] = WeakKeyDictionary()
+
+ def get(self) -> _StateType:
+ thread = current_thread()
+ if thread not in self._state:
+ state = self._state[thread] = self._default()
+ else:
+ state = self._state[thread]
+ return state
diff --git a/src/py/reactpy/reactpy/core/component.py b/src/py/reactpy/reactpy/core/component.py
new file mode 100644
index 000000000..f825aac71
--- /dev/null
+++ b/src/py/reactpy/reactpy/core/component.py
@@ -0,0 +1,66 @@
+from __future__ import annotations
+
+import inspect
+from functools import wraps
+from typing import Any, Callable
+
+from reactpy.core.types import ComponentType, VdomDict
+
+
+def component(
+ function: Callable[..., ComponentType | VdomDict | str | None]
+) -> Callable[..., Component]:
+ """A decorator for defining a new component.
+
+ Parameters:
+ function: The component's :meth:`reactpy.core.proto.ComponentType.render` function.
+ """
+ sig = inspect.signature(function)
+
+ if "key" in sig.parameters and sig.parameters["key"].kind in (
+ inspect.Parameter.KEYWORD_ONLY,
+ inspect.Parameter.POSITIONAL_OR_KEYWORD,
+ ):
+ msg = f"Component render function {function} uses reserved parameter 'key'"
+ raise TypeError(msg)
+
+ @wraps(function)
+ def constructor(*args: Any, key: Any | None = None, **kwargs: Any) -> Component:
+ return Component(function, key, args, kwargs, sig)
+
+ return constructor
+
+
+class Component:
+ """An object for rending component models."""
+
+ __slots__ = "__weakref__", "_func", "_args", "_kwargs", "_sig", "key", "type"
+
+ def __init__(
+ self,
+ function: Callable[..., ComponentType | VdomDict | str | None],
+ key: Any | None,
+ args: tuple[Any, ...],
+ kwargs: dict[str, Any],
+ sig: inspect.Signature,
+ ) -> None:
+ self.key = key
+ self.type = function
+ self._args = args
+ self._kwargs = kwargs
+ self._sig = sig
+
+ def render(self) -> ComponentType | VdomDict | str | None:
+ return self.type(*self._args, **self._kwargs)
+
+ def __repr__(self) -> str:
+ try:
+ args = self._sig.bind(*self._args, **self._kwargs).arguments
+ except TypeError:
+ return f"{self.type.__name__}(...)"
+ else:
+ items = ", ".join(f"{k}={v!r}" for k, v in args.items())
+ if items:
+ return f"{self.type.__name__}({id(self):02x}, {items})"
+ else:
+ return f"{self.type.__name__}({id(self):02x})"
diff --git a/src/idom/core/events.py b/src/py/reactpy/reactpy/core/events.py
similarity index 83%
rename from src/idom/core/events.py
rename to src/py/reactpy/reactpy/core/events.py
index 9afb84e9f..f715b7e9d 100644
--- a/src/idom/core/events.py
+++ b/src/py/reactpy/reactpy/core/events.py
@@ -1,12 +1,12 @@
from __future__ import annotations
import asyncio
-from typing import Any, Callable, Optional, Sequence, overload
+from collections.abc import Sequence
+from typing import Any, Callable, Literal, overload
from anyio import create_task_group
-from typing_extensions import Literal
-from idom.core.proto import EventHandlerFunc, EventHandlerType
+from reactpy.core.types import EventHandlerFunc, EventHandlerType
@overload
@@ -15,18 +15,16 @@ def event(
*,
stop_propagation: bool = ...,
prevent_default: bool = ...,
-) -> EventHandler:
- ...
+) -> EventHandler: ...
@overload
def event(
- function: Literal[None] = None,
+ function: Literal[None] = ...,
*,
stop_propagation: bool = ...,
prevent_default: bool = ...,
-) -> Callable[[Callable[..., Any]], EventHandler]:
- ...
+) -> Callable[[Callable[..., Any]], EventHandler]: ...
def event(
@@ -41,10 +39,10 @@ def event(
.. code-block:: python
- element = idom.html.button({"onClick": my_callback})
+ element = reactpy.html.button({"onClick": my_callback})
You may want the ability to prevent the default action associated with the event
- from taking place, or stoping the event from propagating up the DOM. This decorator
+ from taking place, or stopping the event from propagating up the DOM. This decorator
allows you to add that functionality to your callbacks.
.. code-block:: python
@@ -53,7 +51,7 @@ def event(
def my_callback(*data):
...
- element = idom.html.button({"onClick": my_callback})
+ element = reactpy.html.button({"onClick": my_callback})
Parameters:
function:
@@ -104,7 +102,7 @@ def __init__(
function: EventHandlerFunc,
stop_propagation: bool = False,
prevent_default: bool = False,
- target: Optional[str] = None,
+ target: str | None = None,
) -> None:
self.function = to_event_handler_function(function, positional_args=False)
self.prevent_default = prevent_default
@@ -134,7 +132,7 @@ def to_event_handler_function(
function: Callable[..., Any],
positional_args: bool = True,
) -> EventHandlerFunc:
- """Make a :data:`~idom.core.proto.EventHandlerFunc` from a function or coroutine
+ """Make a :data:`~reactpy.core.proto.EventHandlerFunc` from a function or coroutine
Parameters:
function:
@@ -170,11 +168,12 @@ def merge_event_handlers(
"""Merge multiple event handlers into one
Raises a ValueError if any handlers have conflicting
- :attr:`~idom.core.proto.EventHandlerType.stop_propagation` or
- :attr:`~idom.core.proto.EventHandlerType.prevent_default` attributes.
+ :attr:`~reactpy.core.proto.EventHandlerType.stop_propagation` or
+ :attr:`~reactpy.core.proto.EventHandlerType.prevent_default` attributes.
"""
if not event_handlers:
- raise ValueError("No event handlers to merge")
+ msg = "No event handlers to merge"
+ raise ValueError(msg)
elif len(event_handlers) == 1:
return event_handlers[0]
@@ -190,10 +189,8 @@ def merge_event_handlers(
or handler.prevent_default != prevent_default
or handler.target != target
):
- raise ValueError(
- "Cannot merge handlers - "
- "'stop_propagation', 'prevent_default' or 'target' mistmatch."
- )
+ msg = "Cannot merge handlers - 'stop_propagation', 'prevent_default' or 'target' mismatch."
+ raise ValueError(msg)
return EventHandler(
merge_event_handler_funcs([h.function for h in event_handlers]),
@@ -208,7 +205,8 @@ def merge_event_handler_funcs(
) -> EventHandlerFunc:
"""Make one event handler function from many"""
if not functions:
- raise ValueError("No event handler functions to merge")
+ msg = "No event handler functions to merge"
+ raise ValueError(msg)
elif len(functions) == 1:
return functions[0]
diff --git a/src/py/reactpy/reactpy/core/hooks.py b/src/py/reactpy/reactpy/core/hooks.py
new file mode 100644
index 000000000..640cbf14c
--- /dev/null
+++ b/src/py/reactpy/reactpy/core/hooks.py
@@ -0,0 +1,510 @@
+from __future__ import annotations
+
+import asyncio
+from collections.abc import Coroutine, Sequence
+from logging import getLogger
+from types import FunctionType
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Generic,
+ Protocol,
+ TypeVar,
+ cast,
+ overload,
+)
+
+from typing_extensions import TypeAlias
+
+from reactpy.config import REACTPY_DEBUG_MODE
+from reactpy.core._life_cycle_hook import current_hook
+from reactpy.core.types import Context, Key, State, VdomDict
+from reactpy.utils import Ref
+
+if not TYPE_CHECKING:
+ # make flake8 think that this variable exists
+ ellipsis = type(...)
+
+
+__all__ = [
+ "use_state",
+ "use_effect",
+ "use_reducer",
+ "use_callback",
+ "use_ref",
+ "use_memo",
+]
+
+logger = getLogger(__name__)
+
+_Type = TypeVar("_Type")
+
+
+@overload
+def use_state(initial_value: Callable[[], _Type]) -> State[_Type]: ...
+
+
+@overload
+def use_state(initial_value: _Type) -> State[_Type]: ...
+
+
+def use_state(initial_value: _Type | Callable[[], _Type]) -> State[_Type]:
+ """See the full :ref:`Use State` docs for details
+
+ Parameters:
+ initial_value:
+ Defines the initial value of the state. A callable (accepting no arguments)
+ can be used as a constructor function to avoid re-creating the initial value
+ on each render.
+
+ Returns:
+ A tuple containing the current state and a function to update it.
+ """
+ current_state = _use_const(lambda: _CurrentState(initial_value))
+ return State(current_state.value, current_state.dispatch)
+
+
+class _CurrentState(Generic[_Type]):
+ __slots__ = "value", "dispatch"
+
+ def __init__(
+ self,
+ initial_value: _Type | Callable[[], _Type],
+ ) -> None:
+ if callable(initial_value):
+ self.value = initial_value()
+ else:
+ self.value = initial_value
+
+ hook = current_hook()
+
+ def dispatch(new: _Type | Callable[[_Type], _Type]) -> None:
+ if callable(new):
+ next_value = new(self.value)
+ else:
+ next_value = new
+ if not strictly_equal(next_value, self.value):
+ self.value = next_value
+ hook.schedule_render()
+
+ self.dispatch = dispatch
+
+
+_EffectCleanFunc: TypeAlias = "Callable[[], None]"
+_SyncEffectFunc: TypeAlias = "Callable[[], _EffectCleanFunc | None]"
+_AsyncEffectFunc: TypeAlias = (
+ "Callable[[], Coroutine[None, None, _EffectCleanFunc | None]]"
+)
+_EffectApplyFunc: TypeAlias = "_SyncEffectFunc | _AsyncEffectFunc"
+
+
+@overload
+def use_effect(
+ function: None = None,
+ dependencies: Sequence[Any] | ellipsis | None = ...,
+) -> Callable[[_EffectApplyFunc], None]: ...
+
+
+@overload
+def use_effect(
+ function: _EffectApplyFunc,
+ dependencies: Sequence[Any] | ellipsis | None = ...,
+) -> None: ...
+
+
+def use_effect(
+ function: _EffectApplyFunc | None = None,
+ dependencies: Sequence[Any] | ellipsis | None = ...,
+) -> Callable[[_EffectApplyFunc], None] | None:
+ """See the full :ref:`Use Effect` docs for details
+
+ Parameters:
+ function:
+ Applies the effect and can return a clean-up function
+ dependencies:
+ Dependencies for the effect. The effect will only trigger if the identity
+ of any value in the given sequence changes (i.e. their :func:`id` is
+ different). By default these are inferred based on local variables that are
+ referenced by the given function.
+
+ Returns:
+ If not function is provided, a decorator. Otherwise ``None``.
+ """
+ hook = current_hook()
+
+ dependencies = _try_to_infer_closure_values(function, dependencies)
+ memoize = use_memo(dependencies=dependencies)
+ last_clean_callback: Ref[_EffectCleanFunc | None] = use_ref(None)
+
+ def add_effect(function: _EffectApplyFunc) -> None:
+ if not asyncio.iscoroutinefunction(function):
+ sync_function = cast(_SyncEffectFunc, function)
+ else:
+ async_function = cast(_AsyncEffectFunc, function)
+
+ def sync_function() -> _EffectCleanFunc | None:
+ task = asyncio.create_task(async_function())
+
+ def clean_future() -> None:
+ if not task.cancel():
+ try:
+ clean = task.result()
+ except asyncio.CancelledError:
+ pass
+ else:
+ if clean is not None:
+ clean()
+
+ return clean_future
+
+ async def effect(stop: asyncio.Event) -> None:
+ if last_clean_callback.current is not None:
+ last_clean_callback.current()
+ last_clean_callback.current = None
+ clean = last_clean_callback.current = sync_function()
+ await stop.wait()
+ if clean is not None:
+ clean()
+
+ return memoize(lambda: hook.add_effect(effect))
+
+ if function is not None:
+ add_effect(function)
+ return None
+ else:
+ return add_effect
+
+
+def use_debug_value(
+ message: Any | Callable[[], Any],
+ dependencies: Sequence[Any] | ellipsis | None = ...,
+) -> None:
+ """Log debug information when the given message changes.
+
+ .. note::
+ This hook only logs if :data:`~reactpy.config.REACTPY_DEBUG_MODE` is active.
+
+ Unlike other hooks, a message is considered to have changed if the old and new
+ values are ``!=``. Because this comparison is performed on every render of the
+ component, it may be worth considering the performance cost in some situations.
+
+ Parameters:
+ message:
+ The value to log or a memoized function for generating the value.
+ dependencies:
+ Dependencies for the memoized function. The message will only be recomputed
+ if the identity of any value in the given sequence changes (i.e. their
+ :func:`id` is different). By default these are inferred based on local
+ variables that are referenced by the given function.
+ """
+ old: Ref[Any] = _use_const(lambda: Ref(object()))
+ memo_func = message if callable(message) else lambda: message
+ new = use_memo(memo_func, dependencies)
+
+ if REACTPY_DEBUG_MODE.current and old.current != new:
+ old.current = new
+ logger.debug(f"{current_hook().component} {new}")
+
+
+def create_context(default_value: _Type) -> Context[_Type]:
+ """Return a new context type for use in :func:`use_context`"""
+
+ def context(
+ *children: Any,
+ value: _Type = default_value,
+ key: Key | None = None,
+ ) -> _ContextProvider[_Type]:
+ return _ContextProvider(
+ *children,
+ value=value,
+ key=key,
+ type=context,
+ )
+
+ context.__qualname__ = "context"
+
+ return context
+
+
+def use_context(context: Context[_Type]) -> _Type:
+ """Get the current value for the given context type.
+
+ See the full :ref:`Use Context` docs for more information.
+ """
+ hook = current_hook()
+ provider = hook.get_context_provider(context)
+
+ if provider is None:
+ # same assertions but with normal exceptions
+ if not isinstance(context, FunctionType):
+ raise TypeError(f"{context} is not a Context") # nocov
+ if context.__kwdefaults__ is None:
+ raise TypeError(f"{context} has no 'value' kwarg") # nocov
+ if "value" not in context.__kwdefaults__:
+ raise TypeError(f"{context} has no 'value' kwarg") # nocov
+ return cast(_Type, context.__kwdefaults__["value"])
+
+ return provider.value
+
+
+class _ContextProvider(Generic[_Type]):
+ def __init__(
+ self,
+ *children: Any,
+ value: _Type,
+ key: Key | None,
+ type: Context[_Type],
+ ) -> None:
+ self.children = children
+ self.key = key
+ self.type = type
+ self.value = value
+
+ def render(self) -> VdomDict:
+ current_hook().set_context_provider(self)
+ return {"tagName": "", "children": self.children}
+
+ def __repr__(self) -> str:
+ return f"ContextProvider({self.type})"
+
+
+_ActionType = TypeVar("_ActionType")
+
+
+def use_reducer(
+ reducer: Callable[[_Type, _ActionType], _Type],
+ initial_value: _Type,
+) -> tuple[_Type, Callable[[_ActionType], None]]:
+ """See the full :ref:`Use Reducer` docs for details
+
+ Parameters:
+ reducer:
+ A function which applies an action to the current state in order to
+ produce the next state.
+ initial_value:
+ The initial state value (same as for :func:`use_state`)
+
+ Returns:
+ A tuple containing the current state and a function to change it with an action
+ """
+ state, set_state = use_state(initial_value)
+ return state, _use_const(lambda: _create_dispatcher(reducer, set_state))
+
+
+def _create_dispatcher(
+ reducer: Callable[[_Type, _ActionType], _Type],
+ set_state: Callable[[Callable[[_Type], _Type]], None],
+) -> Callable[[_ActionType], None]:
+ def dispatch(action: _ActionType) -> None:
+ set_state(lambda last_state: reducer(last_state, action))
+
+ return dispatch
+
+
+_CallbackFunc = TypeVar("_CallbackFunc", bound=Callable[..., Any])
+
+
+@overload
+def use_callback(
+ function: None = None,
+ dependencies: Sequence[Any] | ellipsis | None = ...,
+) -> Callable[[_CallbackFunc], _CallbackFunc]: ...
+
+
+@overload
+def use_callback(
+ function: _CallbackFunc,
+ dependencies: Sequence[Any] | ellipsis | None = ...,
+) -> _CallbackFunc: ...
+
+
+def use_callback(
+ function: _CallbackFunc | None = None,
+ dependencies: Sequence[Any] | ellipsis | None = ...,
+) -> _CallbackFunc | Callable[[_CallbackFunc], _CallbackFunc]:
+ """See the full :ref:`Use Callback` docs for details
+
+ Parameters:
+ function:
+ The function whose identity will be preserved
+ dependencies:
+ Dependencies of the callback. The identity the ``function`` will be updated
+ if the identity of any value in the given sequence changes (i.e. their
+ :func:`id` is different). By default these are inferred based on local
+ variables that are referenced by the given function.
+
+ Returns:
+ The current function
+ """
+ dependencies = _try_to_infer_closure_values(function, dependencies)
+ memoize = use_memo(dependencies=dependencies)
+
+ def setup(function: _CallbackFunc) -> _CallbackFunc:
+ return memoize(lambda: function)
+
+ if function is not None:
+ return setup(function)
+ else:
+ return setup
+
+
+class _LambdaCaller(Protocol):
+ """MyPy doesn't know how to deal with TypeVars only used in function return"""
+
+ def __call__(self, func: Callable[[], _Type]) -> _Type: ...
+
+
+@overload
+def use_memo(
+ function: None = None,
+ dependencies: Sequence[Any] | ellipsis | None = ...,
+) -> _LambdaCaller: ...
+
+
+@overload
+def use_memo(
+ function: Callable[[], _Type],
+ dependencies: Sequence[Any] | ellipsis | None = ...,
+) -> _Type: ...
+
+
+def use_memo(
+ function: Callable[[], _Type] | None = None,
+ dependencies: Sequence[Any] | ellipsis | None = ...,
+) -> _Type | Callable[[Callable[[], _Type]], _Type]:
+ """See the full :ref:`Use Memo` docs for details
+
+ Parameters:
+ function:
+ The function to be memoized.
+ dependencies:
+ Dependencies for the memoized function. The memo will only be recomputed if
+ the identity of any value in the given sequence changes (i.e. their
+ :func:`id` is different). By default these are inferred based on local
+ variables that are referenced by the given function.
+
+ Returns:
+ The current state
+ """
+ dependencies = _try_to_infer_closure_values(function, dependencies)
+
+ memo: _Memo[_Type] = _use_const(_Memo)
+
+ if memo.empty():
+ # we need to initialize on the first run
+ changed = True
+ memo.deps = () if dependencies is None else dependencies
+ elif dependencies is None:
+ changed = True
+ memo.deps = ()
+ elif (
+ len(memo.deps) != len(dependencies)
+ # if deps are same length check identity for each item
+ or not all(
+ strictly_equal(current, new)
+ for current, new in zip(memo.deps, dependencies)
+ )
+ ):
+ memo.deps = dependencies
+ changed = True
+ else:
+ changed = False
+
+ setup: Callable[[Callable[[], _Type]], _Type]
+
+ if changed:
+
+ def setup(function: Callable[[], _Type]) -> _Type:
+ current_value = memo.value = function()
+ return current_value
+
+ else:
+
+ def setup(function: Callable[[], _Type]) -> _Type:
+ return memo.value
+
+ if function is not None:
+ return setup(function)
+ else:
+ return setup
+
+
+class _Memo(Generic[_Type]):
+ """Simple object for storing memoization data"""
+
+ __slots__ = "value", "deps"
+
+ value: _Type
+ deps: Sequence[Any]
+
+ def empty(self) -> bool:
+ try:
+ self.value # noqa: B018
+ except AttributeError:
+ return True
+ else:
+ return False
+
+
+def use_ref(initial_value: _Type) -> Ref[_Type]:
+ """See the full :ref:`Use State` docs for details
+
+ Parameters:
+ initial_value: The value initially assigned to the reference.
+
+ Returns:
+ A :class:`Ref` object.
+ """
+ return _use_const(lambda: Ref(initial_value))
+
+
+def _use_const(function: Callable[[], _Type]) -> _Type:
+ return current_hook().use_state(function)
+
+
+def _try_to_infer_closure_values(
+ func: Callable[..., Any] | None,
+ values: Sequence[Any] | ellipsis | None,
+) -> Sequence[Any] | None:
+ if values is ...:
+ if isinstance(func, FunctionType):
+ return (
+ [cell.cell_contents for cell in func.__closure__]
+ if func.__closure__
+ else []
+ )
+ else:
+ return None
+ else:
+ return values
+
+
+def strictly_equal(x: Any, y: Any) -> bool:
+ """Check if two values are identical or, for a limited set or types, equal.
+
+ Only the following types are checked for equality rather than identity:
+
+ - ``int``
+ - ``float``
+ - ``complex``
+ - ``str``
+ - ``bytes``
+ - ``bytearray``
+ - ``memoryview``
+ """
+ return x is y or (type(x) in _NUMERIC_TEXT_BINARY_TYPES and x == y)
+
+
+_NUMERIC_TEXT_BINARY_TYPES = {
+ # numeric
+ int,
+ float,
+ complex,
+ # text
+ str,
+ # binary types
+ bytes,
+ bytearray,
+ memoryview,
+}
diff --git a/src/idom/core/layout.py b/src/py/reactpy/reactpy/core/layout.py
similarity index 59%
rename from src/idom/core/layout.py
rename to src/py/reactpy/reactpy/core/layout.py
index 39c5d55fe..5cb1409d7 100644
--- a/src/idom/core/layout.py
+++ b/src/py/reactpy/reactpy/core/layout.py
@@ -1,106 +1,106 @@
from __future__ import annotations
import abc
-import asyncio
+from asyncio import (
+ FIRST_COMPLETED,
+ CancelledError,
+ Queue,
+ Task,
+ create_task,
+ get_running_loop,
+ wait,
+)
from collections import Counter
-from functools import wraps
+from collections.abc import Sequence
+from contextlib import AsyncExitStack
from logging import getLogger
from typing import (
Any,
Callable,
- Dict,
Generic,
- Iterator,
- List,
NamedTuple,
NewType,
- Optional,
- Set,
- Tuple,
TypeVar,
+ cast,
)
from uuid import uuid4
from weakref import ref as weakref
-from idom.config import (
- IDOM_CHECK_VDOM_SPEC,
- IDOM_DEBUG_MODE,
- IDOM_FEATURE_INDEX_AS_DEFAULT_KEY,
-)
-from idom.utils import Ref
-
-from ._event_proxy import _wrap_in_warning_event_proxies
-from .hooks import LifeCycleHook
-from .proto import ComponentType, EventHandlerDict, VdomJson
-from .vdom import validate_vdom_json
+from anyio import Semaphore
+from typing_extensions import TypeAlias
+from reactpy.config import (
+ REACTPY_ASYNC_RENDERING,
+ REACTPY_CHECK_VDOM_SPEC,
+ REACTPY_DEBUG_MODE,
+)
+from reactpy.core._life_cycle_hook import LifeCycleHook
+from reactpy.core.types import (
+ ComponentType,
+ EventHandlerDict,
+ Key,
+ LayoutEventMessage,
+ LayoutUpdateMessage,
+ VdomChild,
+ VdomDict,
+ VdomJson,
+)
+from reactpy.core.vdom import validate_vdom_json
+from reactpy.utils import Ref
logger = getLogger(__name__)
-class LayoutUpdate(NamedTuple):
- """A change to a view as a result of a :meth:`Layout.render`"""
-
- path: str
- """A "/" delimited path to the element from the root of the layout"""
-
- old: Optional[VdomJson]
- """The old state of the layout"""
-
- new: VdomJson
- """The new state of the layout"""
-
-
-class LayoutEvent(NamedTuple):
- """An event that should be relayed to its handler by :meth:`Layout.deliver`"""
-
- target: str
- """The ID of the event handler."""
- data: List[Any]
- """A list of event data passed to the event handler."""
-
-
-_Self = TypeVar("_Self", bound="Layout")
-
-
class Layout:
"""Responsible for "rendering" components. That is, turning them into VDOM."""
- __slots__ = [
+ __slots__: tuple[str, ...] = (
"root",
"_event_handlers",
"_rendering_queue",
+ "_render_tasks",
+ "_render_tasks_ready",
"_root_life_cycle_state_id",
"_model_states_by_life_cycle_state_id",
- ]
+ )
- if not hasattr(abc.ABC, "__weakref__"): # pragma: no cover
- __slots__.append("__weakref__")
+ if not hasattr(abc.ABC, "__weakref__"): # nocov
+ __slots__ += ("__weakref__",)
- def __init__(self, root: "ComponentType") -> None:
+ def __init__(self, root: ComponentType) -> None:
super().__init__()
if not isinstance(root, ComponentType):
- raise TypeError(f"Expected a ComponentType, not {type(root)!r}.")
+ msg = f"Expected a ComponentType, not {type(root)!r}."
+ raise TypeError(msg)
self.root = root
- def __enter__(self: _Self) -> _Self:
+ async def __aenter__(self) -> Layout:
# create attributes here to avoid access before entering context manager
self._event_handlers: EventHandlerDict = {}
+ self._render_tasks: set[Task[LayoutUpdateMessage]] = set()
+ self._render_tasks_ready: Semaphore = Semaphore(0)
self._rendering_queue: _ThreadSafeQueue[_LifeCycleStateId] = _ThreadSafeQueue()
- root_model_state = _new_root_model_state(self.root, self._rendering_queue.put)
+ root_model_state = _new_root_model_state(self.root, self._schedule_render_task)
self._root_life_cycle_state_id = root_id = root_model_state.life_cycle_state.id
- self._rendering_queue.put(root_id)
-
self._model_states_by_life_cycle_state_id = {root_id: root_model_state}
+ self._schedule_render_task(root_id)
return self
- def __exit__(self, *exc: Any) -> None:
+ async def __aexit__(self, *exc: Any) -> None:
root_csid = self._root_life_cycle_state_id
root_model_state = self._model_states_by_life_cycle_state_id[root_csid]
- self._unmount_model_states([root_model_state])
+
+ for t in self._render_tasks:
+ t.cancel()
+ try:
+ await t
+ except CancelledError:
+ pass
+
+ await self._unmount_model_states([root_model_state])
# delete attributes here to avoid access after exiting context manager
del self._event_handlers
@@ -108,148 +108,150 @@ def __exit__(self, *exc: Any) -> None:
del self._root_life_cycle_state_id
del self._model_states_by_life_cycle_state_id
- return None
-
- async def deliver(self, event: LayoutEvent) -> None:
+ async def deliver(self, event: LayoutEventMessage) -> None:
"""Dispatch an event to the targeted handler"""
# It is possible for an element in the frontend to produce an event
# associated with a backend model that has been deleted. We only handle
# events if the element and the handler exist in the backend. Otherwise
# we just ignore the event.
- handler = self._event_handlers.get(event.target)
+ handler = self._event_handlers.get(event["target"])
if handler is not None:
try:
- await handler.function(_wrap_in_warning_event_proxies(event.data))
+ await handler.function(event["data"])
except Exception:
logger.exception(f"Failed to execute event handler {handler}")
else:
logger.info(
- f"Ignored event - handler {event.target!r} does not exist or its component unmounted"
+ f"Ignored event - handler {event['target']!r} "
+ "does not exist or its component unmounted"
)
- async def render(self) -> LayoutUpdate:
+ async def render(self) -> LayoutUpdateMessage:
+ if REACTPY_ASYNC_RENDERING.current:
+ return await self._parallel_render()
+ else: # nocov
+ return await self._serial_render()
+
+ async def _serial_render(self) -> LayoutUpdateMessage: # nocov
"""Await the next available render. This will block until a component is updated"""
while True:
model_state_id = await self._rendering_queue.get()
try:
model_state = self._model_states_by_life_cycle_state_id[model_state_id]
except KeyError:
- logger.info(
+ logger.debug(
"Did not render component with model state ID "
- "{model_state_id!r} - component already unmounted"
+ f"{model_state_id!r} - component already unmounted"
)
else:
- return self._create_layout_update(model_state)
-
- if IDOM_CHECK_VDOM_SPEC.current:
- # If in debug mode inject a function that ensures all returned updates
- # contain valid VDOM models. We only do this in debug mode or when this check
- # is explicitely turned in order to avoid unnecessarily impacting performance.
-
- _debug_render = render
-
- @wraps(_debug_render)
- async def render(self) -> LayoutUpdate:
- result = await self._debug_render()
- # Ensure that the model is valid VDOM on each render
- root_id = self._root_life_cycle_state_id
- root_model = self._model_states_by_life_cycle_state_id[root_id]
- validate_vdom_json(root_model.model.current)
- return result
-
- def _create_layout_update(self, old_state: _ModelState) -> LayoutUpdate:
+ return await self._create_layout_update(model_state)
+
+ async def _parallel_render(self) -> LayoutUpdateMessage:
+ """Await the next available render within an asyncio task group."""
+ await self._render_tasks_ready.acquire()
+ done, _ = await wait(self._render_tasks, return_when=FIRST_COMPLETED)
+ update_task: Task[LayoutUpdateMessage] = done.pop()
+ self._render_tasks.remove(update_task)
+ return update_task.result()
+
+ async def _create_layout_update(
+ self, old_state: _ModelState
+ ) -> LayoutUpdateMessage:
new_state = _copy_component_model_state(old_state)
-
component = new_state.life_cycle_state.component
- self._render_component(old_state, new_state, component)
- # hook effects must run after the update is complete
- for model_state in _iter_model_state_children(new_state):
- if model_state.is_component_state:
- model_state.life_cycle_state.hook.component_did_render()
+ async with AsyncExitStack() as exit_stack:
+ await self._render_component(exit_stack, old_state, new_state, component)
- old_model: Optional[VdomJson]
- try:
- old_model = old_state.model.current
- except AttributeError:
- old_model = None
+ if REACTPY_CHECK_VDOM_SPEC.current:
+ validate_vdom_json(new_state.model.current)
- return LayoutUpdate(
- path=new_state.patch_path,
- old=old_model,
- new=new_state.model.current,
- )
+ return {
+ "type": "layout-update",
+ "path": new_state.patch_path,
+ "model": new_state.model.current,
+ }
- def _render_component(
+ async def _render_component(
self,
- old_state: Optional[_ModelState],
+ exit_stack: AsyncExitStack,
+ old_state: _ModelState | None,
new_state: _ModelState,
component: ComponentType,
) -> None:
life_cycle_state = new_state.life_cycle_state
- self._model_states_by_life_cycle_state_id[life_cycle_state.id] = new_state
-
life_cycle_hook = life_cycle_state.hook
- life_cycle_hook.component_will_render()
+ self._model_states_by_life_cycle_state_id[life_cycle_state.id] = new_state
+
+ await life_cycle_hook.affect_component_will_render(component)
+ exit_stack.push_async_callback(life_cycle_hook.affect_layout_did_render)
try:
- life_cycle_hook.set_current()
- try:
- raw_model = component.render()
- finally:
- life_cycle_hook.unset_current()
- self._render_model(old_state, new_state, raw_model)
+ raw_model = component.render()
+ # wrap the model in a fragment (i.e. tagName="") to ensure components have
+ # a separate node in the model state tree. This could be removed if this
+ # components are given a node in the tree some other way
+ wrapper_model: VdomDict = {"tagName": "", "children": [raw_model]}
+ await self._render_model(exit_stack, old_state, new_state, wrapper_model)
except Exception as error:
logger.exception(f"Failed to render {component}")
new_state.model.current = {
"tagName": "",
"error": (
f"{type(error).__name__}: {error}"
- if IDOM_DEBUG_MODE.current
+ if REACTPY_DEBUG_MODE.current
else ""
),
}
+ finally:
+ await life_cycle_hook.affect_component_did_render()
+
try:
parent = new_state.parent
except AttributeError:
- pass
+ pass # only happens for root component
else:
key, index = new_state.key, new_state.index
parent.children_by_key[key] = new_state
- # need to do insertion in case where old_state is None and we're appending
- parent.model.current["children"][index : index + 1] = [
- new_state.model.current
- ]
+ # need to add this model to parent's children without mutating parent model
+ old_parent_model = parent.model.current
+ old_parent_children = old_parent_model["children"]
+ parent.model.current = {
+ **old_parent_model,
+ "children": [
+ *old_parent_children[:index],
+ new_state.model.current,
+ *old_parent_children[index + 1 :],
+ ],
+ }
- def _render_model(
+ async def _render_model(
self,
- old_state: Optional[_ModelState],
+ exit_stack: AsyncExitStack,
+ old_state: _ModelState | None,
new_state: _ModelState,
raw_model: Any,
) -> None:
- new_state.model.current = {"tagName": raw_model["tagName"]}
+ try:
+ new_state.model.current = {"tagName": raw_model["tagName"]}
+ except Exception as e: # nocov
+ msg = f"Expected a VDOM element dict, not {raw_model}"
+ raise ValueError(msg) from e
if "key" in raw_model:
new_state.key = new_state.model.current["key"] = raw_model["key"]
if "importSource" in raw_model:
new_state.model.current["importSource"] = raw_model["importSource"]
-
- if old_state is not None and old_state.key != new_state.key:
- self._unmount_model_states([old_state])
- if new_state.is_component_state:
- self._model_states_by_life_cycle_state_id[
- new_state.life_cycle_state.id
- ] = new_state
- old_state = None
-
self._render_model_attributes(old_state, new_state, raw_model)
- self._render_model_children(old_state, new_state, raw_model.get("children", []))
+ await self._render_model_children(
+ exit_stack, old_state, new_state, raw_model.get("children", [])
+ )
def _render_model_attributes(
self,
- old_state: Optional[_ModelState],
+ old_state: _ModelState | None,
new_state: _ModelState,
- raw_model: Dict[str, Any],
+ raw_model: dict[str, Any],
) -> None:
# extract event handlers from 'eventHandlers' and 'attributes'
handlers_by_event: EventHandlerDict = raw_model.get("eventHandlers", {})
@@ -308,9 +310,10 @@ def _render_model_event_handlers_without_old_state(
return None
- def _render_model_children(
+ async def _render_model_children(
self,
- old_state: Optional[_ModelState],
+ exit_stack: AsyncExitStack,
+ old_state: _ModelState | None,
new_state: _ModelState,
raw_children: Any,
) -> None:
@@ -319,30 +322,31 @@ def _render_model_children(
if old_state is None:
if raw_children:
- self._render_model_children_without_old_state(new_state, raw_children)
+ await self._render_model_children_without_old_state(
+ exit_stack, new_state, raw_children
+ )
return None
elif not raw_children:
- self._unmount_model_states(list(old_state.children_by_key.values()))
+ await self._unmount_model_states(list(old_state.children_by_key.values()))
return None
- child_type_key_tuples = list(_process_child_type_and_key(raw_children))
+ children_info = _get_children_info(raw_children)
- new_keys = {item[2] for item in child_type_key_tuples}
- if len(new_keys) != len(raw_children):
- key_counter = Counter(item[2] for item in child_type_key_tuples)
+ new_keys = {k for _, _, k in children_info}
+ if len(new_keys) != len(children_info):
+ key_counter = Counter(item[2] for item in children_info)
duplicate_keys = [key for key, count in key_counter.items() if count > 1]
- raise ValueError(
- f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}"
- )
+ msg = f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}"
+ raise ValueError(msg)
old_keys = set(old_state.children_by_key).difference(new_keys)
if old_keys:
- self._unmount_model_states(
+ await self._unmount_model_states(
[old_state.children_by_key[key] for key in old_keys]
)
- new_children = new_state.model.current["children"] = []
- for index, (child, child_type, key) in enumerate(child_type_key_tuples):
+ new_state.model.current["children"] = []
+ for index, (child, child_type, key) in enumerate(children_info):
old_child_state = old_state.children_by_key.get(key)
if child_type is _DICT_TYPE:
old_child_state = old_state.children_by_key.get(key)
@@ -352,18 +356,27 @@ def _render_model_children(
index,
key,
)
+ elif old_child_state.is_component_state:
+ await self._unmount_model_states([old_child_state])
+ new_child_state = _make_element_model_state(
+ new_state,
+ index,
+ key,
+ )
+ old_child_state = None
else:
- if old_child_state.is_component_state:
- self._unmount_model_states([old_child_state])
new_child_state = _update_element_model_state(
old_child_state,
new_state,
index,
)
- self._render_model(old_child_state, new_child_state, child)
- new_children.append(new_child_state.model.current)
+ await self._render_model(
+ exit_stack, old_child_state, new_child_state, child
+ )
+ new_state.append_child(new_child_state.model.current)
new_state.children_by_key[key] = new_child_state
elif child_type is _COMPONENT_TYPE:
+ child = cast(ComponentType, child)
old_child_state = old_state.children_by_key.get(key)
if old_child_state is None:
new_child_state = _make_component_model_state(
@@ -371,7 +384,19 @@ def _render_model_children(
index,
key,
child,
- self._rendering_queue.put,
+ self._schedule_render_task,
+ )
+ elif old_child_state.is_component_state and (
+ old_child_state.life_cycle_state.component.type != child.type
+ ):
+ await self._unmount_model_states([old_child_state])
+ old_child_state = None
+ new_child_state = _make_component_model_state(
+ new_state,
+ index,
+ key,
+ child,
+ self._schedule_render_task,
)
else:
new_child_state = _update_component_model_state(
@@ -379,36 +404,48 @@ def _render_model_children(
new_state,
index,
child,
- self._rendering_queue.put,
+ self._schedule_render_task,
)
- self._render_component(old_child_state, new_child_state, child)
+ await self._render_component(
+ exit_stack, old_child_state, new_child_state, child
+ )
else:
old_child_state = old_state.children_by_key.get(key)
if old_child_state is not None:
- self._unmount_model_states([old_child_state])
- new_children.append(child)
+ await self._unmount_model_states([old_child_state])
+ new_state.append_child(child)
- def _render_model_children_without_old_state(
- self, new_state: _ModelState, raw_children: List[Any]
+ async def _render_model_children_without_old_state(
+ self,
+ exit_stack: AsyncExitStack,
+ new_state: _ModelState,
+ raw_children: list[Any],
) -> None:
- new_children = new_state.model.current["children"] = []
- for index, (child, child_type, key) in enumerate(
- _process_child_type_and_key(raw_children)
- ):
+ children_info = _get_children_info(raw_children)
+
+ new_keys = {k for _, _, k in children_info}
+ if len(new_keys) != len(children_info):
+ key_counter = Counter(k for _, _, k in children_info)
+ duplicate_keys = [key for key, count in key_counter.items() if count > 1]
+ msg = f"Duplicate keys {duplicate_keys} at {new_state.patch_path or '/'!r}"
+ raise ValueError(msg)
+
+ new_state.model.current["children"] = []
+ for index, (child, child_type, key) in enumerate(children_info):
if child_type is _DICT_TYPE:
child_state = _make_element_model_state(new_state, index, key)
- self._render_model(None, child_state, child)
- new_children.append(child_state.model.current)
+ await self._render_model(exit_stack, None, child_state, child)
+ new_state.append_child(child_state.model.current)
new_state.children_by_key[key] = child_state
elif child_type is _COMPONENT_TYPE:
child_state = _make_component_model_state(
- new_state, index, key, child, self._rendering_queue.put
+ new_state, index, key, child, self._schedule_render_task
)
- self._render_component(None, child_state, child)
+ await self._render_component(exit_stack, None, child_state, child)
else:
- new_children.append(child)
+ new_state.append_child(child)
- def _unmount_model_states(self, old_states: List[_ModelState]) -> None:
+ async def _unmount_model_states(self, old_states: list[_ModelState]) -> None:
to_unmount = old_states[::-1] # unmount in reversed order of rendering
while to_unmount:
model_state = to_unmount.pop()
@@ -419,20 +456,29 @@ def _unmount_model_states(self, old_states: List[_ModelState]) -> None:
if model_state.is_component_state:
life_cycle_state = model_state.life_cycle_state
del self._model_states_by_life_cycle_state_id[life_cycle_state.id]
- life_cycle_state.hook.component_will_unmount()
+ await life_cycle_state.hook.affect_component_will_unmount()
to_unmount.extend(model_state.children_by_key.values())
+ def _schedule_render_task(self, lcs_id: _LifeCycleStateId) -> None:
+ if not REACTPY_ASYNC_RENDERING.current:
+ self._rendering_queue.put(lcs_id)
+ return None
+ try:
+ model_state = self._model_states_by_life_cycle_state_id[lcs_id]
+ except KeyError:
+ logger.debug(
+ "Did not render component with model state ID "
+ f"{lcs_id!r} - component already unmounted"
+ )
+ else:
+ self._render_tasks.add(create_task(self._create_layout_update(model_state)))
+ self._render_tasks_ready.release()
+
def __repr__(self) -> str:
return f"{type(self).__name__}({self.root})"
-def _iter_model_state_children(model_state: _ModelState) -> Iterator[_ModelState]:
- yield model_state
- for child in model_state.children_by_key.values():
- yield from _iter_model_state_children(child)
-
-
def _new_root_model_state(
component: ComponentType, schedule_render: Callable[[_LifeCycleStateId], None]
) -> _ModelState:
@@ -470,7 +516,7 @@ def _make_component_model_state(
def _copy_component_model_state(old_model_state: _ModelState) -> _ModelState:
# use try/except here because not having a parent is rare (only the root state)
try:
- parent: Optional[_ModelState] = old_model_state.parent
+ parent: _ModelState | None = old_model_state.parent
except AttributeError:
parent = None
@@ -498,7 +544,7 @@ def _update_component_model_state(
index=new_index,
key=old_model_state.key,
model=Ref(), # does not copy the model
- patch_path=old_model_state.patch_path,
+ patch_path=f"{new_parent.patch_path}/children/{new_index}",
children_by_key={},
targets_by_event={},
life_cycle_state=(
@@ -547,6 +593,7 @@ class _ModelState:
__slots__ = (
"__weakref__",
"_parent_ref",
+ "_render_semaphore",
"children_by_key",
"index",
"key",
@@ -558,14 +605,14 @@ class _ModelState:
def __init__(
self,
- parent: Optional[_ModelState],
+ parent: _ModelState | None,
index: int,
key: Any,
model: Ref[VdomJson],
patch_path: str,
- children_by_key: Dict[str, _ModelState],
- targets_by_event: Dict[str, str],
- life_cycle_state: Optional[_LifeCycleState] = None,
+ children_by_key: dict[Key, _ModelState],
+ targets_by_event: dict[str, str],
+ life_cycle_state: _LifeCycleState | None = None,
):
self.index = index
"""The index of the element amongst its siblings"""
@@ -577,7 +624,7 @@ def __init__(
"""The actual model of the element"""
self.patch_path = patch_path
- """A "/" delimitted path to the element within the greater layout"""
+ """A "/" delimited path to the element within the greater layout"""
self.children_by_key = children_by_key
"""Child model states indexed by their unique keys"""
@@ -603,10 +650,14 @@ def is_component_state(self) -> bool:
@property
def parent(self) -> _ModelState:
parent = self._parent_ref()
- assert parent is not None, "detached model state"
+ if parent is None:
+ raise RuntimeError("detached model state") # nocov
return parent
- def __repr__(self) -> str: # pragma: no cover
+ def append_child(self, child: Any) -> None:
+ self.model.current["children"].append(child)
+
+ def __repr__(self) -> str: # nocov
return f"ModelState({ {s: getattr(self, s, None) for s in self.__slots__} })"
@@ -641,7 +692,7 @@ class _LifeCycleState(NamedTuple):
"""Component state for :class:`_ModelState`"""
id: _LifeCycleStateId
- """A unique identifier used in the :class:`~idom.core.hooks.LifeCycleHook` callback"""
+ """A unique identifier used in the :class:`~reactpy.core.hooks.LifeCycleHook` callback"""
hook: LifeCycleHook
"""The life cycle hook"""
@@ -654,19 +705,15 @@ class _LifeCycleState(NamedTuple):
class _ThreadSafeQueue(Generic[_Type]):
-
- __slots__ = "_loop", "_queue", "_pending"
-
def __init__(self) -> None:
- self._loop = asyncio.get_event_loop()
- self._queue: asyncio.Queue[_Type] = asyncio.Queue()
- self._pending: Set[_Type] = set()
+ self._loop = get_running_loop()
+ self._queue: Queue[_Type] = Queue()
+ self._pending: set[_Type] = set()
def put(self, value: _Type) -> None:
if value not in self._pending:
self._pending.add(value)
self._loop.call_soon_threadsafe(self._queue.put_nowait, value)
- return None
async def get(self) -> _Type:
value = await self._queue.get()
@@ -674,40 +721,34 @@ async def get(self) -> _Type:
return value
-def _process_child_type_and_key(
- children: List[Any],
-) -> Iterator[Tuple[Any, _ElementType, Any]]:
+def _get_children_info(children: list[VdomChild]) -> Sequence[_ChildInfo]:
+ infos: list[_ChildInfo] = []
for index, child in enumerate(children):
- if isinstance(child, dict):
+ if child is None:
+ continue
+ elif isinstance(child, dict):
child_type = _DICT_TYPE
key = child.get("key")
elif isinstance(child, ComponentType):
child_type = _COMPONENT_TYPE
- key = getattr(child, "key", None)
+ key = child.key
else:
child = f"{child}"
child_type = _STRING_TYPE
key = None
if key is None:
- key = _default_key(index)
+ key = index
+
+ infos.append((child, child_type, key))
+
+ return infos
- yield (child, child_type, key)
+_ChildInfo: TypeAlias = tuple[Any, "_ElementType", Key]
# used in _process_child_type_and_key
_ElementType = NewType("_ElementType", int)
_DICT_TYPE = _ElementType(1)
_COMPONENT_TYPE = _ElementType(2)
_STRING_TYPE = _ElementType(3)
-
-
-if IDOM_FEATURE_INDEX_AS_DEFAULT_KEY.current:
-
- def _default_key(index: int) -> Any:
- return index
-
-else:
-
- def _default_key(index: int) -> Any: # pragma: no cover
- return object()
diff --git a/src/py/reactpy/reactpy/core/serve.py b/src/py/reactpy/reactpy/core/serve.py
new file mode 100644
index 000000000..3a540af59
--- /dev/null
+++ b/src/py/reactpy/reactpy/core/serve.py
@@ -0,0 +1,83 @@
+from __future__ import annotations
+
+from collections.abc import Awaitable
+from logging import getLogger
+from typing import Callable
+from warnings import warn
+
+from anyio import create_task_group
+from anyio.abc import TaskGroup
+
+from reactpy.config import REACTPY_DEBUG_MODE
+from reactpy.core.types import LayoutEventMessage, LayoutType, LayoutUpdateMessage
+
+logger = getLogger(__name__)
+
+
+SendCoroutine = Callable[[LayoutUpdateMessage], Awaitable[None]]
+"""Send model patches given by a dispatcher"""
+
+RecvCoroutine = Callable[[], Awaitable[LayoutEventMessage]]
+"""Called by a dispatcher to return a :class:`reactpy.core.layout.LayoutEventMessage`
+
+The event will then trigger an :class:`reactpy.core.proto.EventHandlerType` in a layout.
+"""
+
+
+class Stop(BaseException):
+ """Deprecated
+
+ Stop serving changes and events
+
+ Raising this error will tell dispatchers to gracefully exit. Typically this is
+ called by code running inside a layout to tell it to stop rendering.
+ """
+
+
+async def serve_layout(
+ layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage],
+ send: SendCoroutine,
+ recv: RecvCoroutine,
+) -> None:
+ """Run a dispatch loop for a single view instance"""
+ async with layout:
+ try:
+ async with create_task_group() as task_group:
+ task_group.start_soon(_single_outgoing_loop, layout, send)
+ task_group.start_soon(_single_incoming_loop, task_group, layout, recv)
+ except Stop: # nocov
+ warn(
+ "The Stop exception is deprecated and will be removed in a future version",
+ UserWarning,
+ stacklevel=1,
+ )
+ logger.info(f"Stopped serving {layout}")
+
+
+async def _single_outgoing_loop(
+ layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage], send: SendCoroutine
+) -> None:
+ while True:
+ update = await layout.render()
+ try:
+ await send(update)
+ except Exception: # nocov
+ if not REACTPY_DEBUG_MODE.current:
+ msg = (
+ "Failed to send update. More info may be available "
+ "if you enabling debug mode by setting "
+ "`reactpy.config.REACTPY_DEBUG_MODE.current = True`."
+ )
+ logger.error(msg)
+ raise
+
+
+async def _single_incoming_loop(
+ task_group: TaskGroup,
+ layout: LayoutType[LayoutUpdateMessage, LayoutEventMessage],
+ recv: RecvCoroutine,
+) -> None:
+ while True:
+ # We need to fire and forget here so that we avoid waiting on the completion
+ # of this event handler before receiving and running the next one.
+ task_group.start_soon(layout.deliver, await recv())
diff --git a/src/py/reactpy/reactpy/core/types.py b/src/py/reactpy/reactpy/core/types.py
new file mode 100644
index 000000000..b451be30a
--- /dev/null
+++ b/src/py/reactpy/reactpy/core/types.py
@@ -0,0 +1,248 @@
+from __future__ import annotations
+
+import sys
+from collections import namedtuple
+from collections.abc import Mapping, Sequence
+from types import TracebackType
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ Callable,
+ Generic,
+ Literal,
+ NamedTuple,
+ Protocol,
+ TypeVar,
+ overload,
+ runtime_checkable,
+)
+
+from typing_extensions import TypeAlias, TypedDict
+
+_Type = TypeVar("_Type")
+
+
+if TYPE_CHECKING or sys.version_info < (3, 9) or sys.version_info >= (3, 11):
+
+ class State(NamedTuple, Generic[_Type]):
+ value: _Type
+ set_value: Callable[[_Type | Callable[[_Type], _Type]], None]
+
+else: # nocov
+ State = namedtuple("State", ("value", "set_value"))
+
+
+ComponentConstructor = Callable[..., "ComponentType"]
+"""Simple function returning a new component"""
+
+RootComponentConstructor = Callable[[], "ComponentType"]
+"""The root component should be constructed by a function accepting no arguments."""
+
+
+Key: TypeAlias = "str | int"
+
+
+_OwnType = TypeVar("_OwnType")
+
+
+@runtime_checkable
+class ComponentType(Protocol):
+ """The expected interface for all component-like objects"""
+
+ key: Key | None
+ """An identifier which is unique amongst a component's immediate siblings"""
+
+ type: Any
+ """The function or class defining the behavior of this component
+
+ This is used to see if two component instances share the same definition.
+ """
+
+ def render(self) -> VdomDict | ComponentType | str | None:
+ """Render the component's view model."""
+
+
+_Render_co = TypeVar("_Render_co", covariant=True)
+_Event_contra = TypeVar("_Event_contra", contravariant=True)
+
+
+@runtime_checkable
+class LayoutType(Protocol[_Render_co, _Event_contra]):
+ """Renders and delivers, updates to views and events to handlers, respectively"""
+
+ async def render(self) -> _Render_co:
+ """Render an update to a view"""
+
+ async def deliver(self, event: _Event_contra) -> None:
+ """Relay an event to its respective handler"""
+
+ async def __aenter__(self) -> LayoutType[_Render_co, _Event_contra]:
+ """Prepare the layout for its first render"""
+
+ async def __aexit__(
+ self,
+ exc_type: type[Exception],
+ exc_value: Exception,
+ traceback: TracebackType,
+ ) -> bool | None:
+ """Clean up the view after its final render"""
+
+
+VdomAttributes = Mapping[str, Any]
+"""Describes the attributes of a :class:`VdomDict`"""
+
+VdomChild: TypeAlias = "ComponentType | VdomDict | str | None | Any"
+"""A single child element of a :class:`VdomDict`"""
+
+VdomChildren: TypeAlias = "Sequence[VdomChild] | VdomChild"
+"""Describes a series of :class:`VdomChild` elements"""
+
+
+class _VdomDictOptional(TypedDict, total=False):
+ key: Key | None
+ children: Sequence[ComponentType | VdomChild]
+ attributes: VdomAttributes
+ eventHandlers: EventHandlerDict
+ importSource: ImportSourceDict
+
+
+class _VdomDictRequired(TypedDict, total=True):
+ tagName: str
+
+
+class VdomDict(_VdomDictRequired, _VdomDictOptional):
+ """A :ref:`VDOM` dictionary"""
+
+
+class ImportSourceDict(TypedDict):
+ source: str
+ fallback: Any
+ sourceType: str
+ unmountBeforeUpdate: bool
+
+
+class _OptionalVdomJson(TypedDict, total=False):
+ key: Key
+ error: str
+ children: list[Any]
+ attributes: dict[str, Any]
+ eventHandlers: dict[str, _JsonEventTarget]
+ importSource: _JsonImportSource
+
+
+class _RequiredVdomJson(TypedDict, total=True):
+ tagName: str
+
+
+class VdomJson(_RequiredVdomJson, _OptionalVdomJson):
+ """A JSON serializable form of :class:`VdomDict` matching the :data:`VDOM_JSON_SCHEMA`"""
+
+
+class _JsonEventTarget(TypedDict):
+ target: str
+ preventDefault: bool
+ stopPropagation: bool
+
+
+class _JsonImportSource(TypedDict):
+ source: str
+ fallback: Any
+
+
+EventHandlerMapping = Mapping[str, "EventHandlerType"]
+"""A generic mapping between event names to their handlers"""
+
+EventHandlerDict: TypeAlias = "dict[str, EventHandlerType]"
+"""A dict mapping between event names to their handlers"""
+
+
+class EventHandlerFunc(Protocol):
+ """A coroutine which can handle event data"""
+
+ async def __call__(self, data: Sequence[Any]) -> None: ...
+
+
+@runtime_checkable
+class EventHandlerType(Protocol):
+ """Defines a handler for some event"""
+
+ prevent_default: bool
+ """Whether to block the event from propagating further up the DOM"""
+
+ stop_propagation: bool
+ """Stops the default action associate with the event from taking place."""
+
+ function: EventHandlerFunc
+ """A coroutine which can respond to an event and its data"""
+
+ target: str | None
+ """Typically left as ``None`` except when a static target is useful.
+
+ When testing, it may be useful to specify a static target ID so events can be
+ triggered programmatically.
+
+ .. note::
+
+ When ``None``, it is left to a :class:`LayoutType` to auto generate a unique ID.
+ """
+
+
+class VdomDictConstructor(Protocol):
+ """Standard function for constructing a :class:`VdomDict`"""
+
+ @overload
+ def __call__(
+ self, attributes: VdomAttributes, *children: VdomChildren
+ ) -> VdomDict: ...
+
+ @overload
+ def __call__(self, *children: VdomChildren) -> VdomDict: ...
+
+ @overload
+ def __call__(
+ self, *attributes_and_children: VdomAttributes | VdomChildren
+ ) -> VdomDict: ...
+
+
+class LayoutUpdateMessage(TypedDict):
+ """A message describing an update to a layout"""
+
+ type: Literal["layout-update"]
+ """The type of message"""
+ path: str
+ """JSON Pointer path to the model element being updated"""
+ model: VdomJson
+ """The model to assign at the given JSON Pointer path"""
+
+
+class LayoutEventMessage(TypedDict):
+ """Message describing an event originating from an element in the layout"""
+
+ type: Literal["layout-event"]
+ """The type of message"""
+ target: str
+ """The ID of the event handler."""
+ data: Sequence[Any]
+ """A list of event data passed to the event handler."""
+
+
+class Context(Protocol[_Type]):
+ """Returns a :class:`ContextProvider` component"""
+
+ def __call__(
+ self,
+ *children: Any,
+ value: _Type = ...,
+ key: Key | None = ...,
+ ) -> ContextProviderType[_Type]: ...
+
+
+class ContextProviderType(ComponentType, Protocol[_Type]):
+ """A component which provides a context value to its children"""
+
+ type: Context[_Type]
+ """The context type"""
+
+ @property
+ def value(self) -> _Type:
+ "Current context value"
diff --git a/src/idom/core/vdom.py b/src/py/reactpy/reactpy/core/vdom.py
similarity index 54%
rename from src/idom/core/vdom.py
rename to src/py/reactpy/reactpy/core/vdom.py
index 853c412a8..e494b5269 100644
--- a/src/idom/core/vdom.py
+++ b/src/py/reactpy/reactpy/core/vdom.py
@@ -1,32 +1,30 @@
from __future__ import annotations
-import inspect
-import logging
-from typing import Any, Dict, List, Mapping, Optional, Sequence, Tuple, cast
+import json
+from collections.abc import Mapping, Sequence
+from functools import wraps
+from typing import Any, Protocol, cast, overload
from fastjsonschema import compile as compile_json_schema
-from typing_extensions import Protocol
-from idom.config import IDOM_DEBUG_MODE
-from idom.core.events import (
- EventHandler,
- merge_event_handlers,
- to_event_handler_function,
-)
-from idom.core.proto import (
+from reactpy._warnings import warn
+from reactpy.config import REACTPY_CHECK_JSON_ATTRS, REACTPY_DEBUG_MODE
+from reactpy.core._f_back import f_module_name
+from reactpy.core.events import EventHandler, to_event_handler_function
+from reactpy.core.types import (
+ ComponentType,
EventHandlerDict,
- EventHandlerMapping,
EventHandlerType,
ImportSourceDict,
- VdomAttributesAndChildren,
+ Key,
+ VdomAttributes,
+ VdomChild,
+ VdomChildren,
VdomDict,
+ VdomDictConstructor,
VdomJson,
)
-
-logger = logging.getLogger()
-
-
VDOM_JSON_SCHEMA = {
"$schema": "http://json-schema.org/draft-07/schema",
"$ref": "#/definitions/element",
@@ -35,7 +33,7 @@
"type": "object",
"properties": {
"tagName": {"type": "string"},
- "key": {"type": "string"},
+ "key": {"type": ["string", "number", "null"]},
"error": {"type": "string"},
"children": {"$ref": "#/definitions/elementChildren"},
"attributes": {"type": "object"},
@@ -57,10 +55,10 @@
"elementEventHandlers": {
"type": "object",
"patternProperties": {
- ".*": {"$ref": "#/definitions/eventHander"},
+ ".*": {"$ref": "#/definitions/eventHandler"},
},
},
- "eventHander": {
+ "eventHandler": {
"type": "object",
"properties": {
"target": {"type": "string"},
@@ -126,14 +124,20 @@ def is_vdom(value: Any) -> bool:
)
+@overload
+def vdom(tag: str, *children: VdomChildren) -> VdomDict: ...
+
+
+@overload
+def vdom(tag: str, attributes: VdomAttributes, *children: VdomChildren) -> VdomDict: ...
+
+
def vdom(
tag: str,
- *attributes_and_children: VdomAttributesAndChildren,
- key: str | int | None = None,
- event_handlers: Optional[EventHandlerMapping] = None,
- import_source: Optional[ImportSourceDict] = None,
+ *attributes_and_children: Any,
+ **kwargs: Any,
) -> VdomDict:
- """A helper function for creating VDOM dictionaries.
+ """A helper function for creating VDOM elements.
Parameters:
tag:
@@ -143,7 +147,7 @@ def vdom(
iterables of children. The attribute mapping **must** precede the children,
or children which will be merged into their respective parts of the model.
key:
- A string idicating the identity of a particular element. This is significant
+ A string indicating the identity of a particular element. This is significant
to preserve event handlers across updates - without a key, a re-render would
cause these handlers to be deleted, but with a key, they would be redirected
to any newly defined handlers.
@@ -153,66 +157,75 @@ def vdom(
(subject to change) specifies javascript that, when evaluated returns a
React component.
"""
+ if kwargs: # nocov
+ if "key" in kwargs:
+ if attributes_and_children:
+ maybe_attributes, *children = attributes_and_children
+ if _is_attributes(maybe_attributes):
+ attributes_and_children = (
+ {**maybe_attributes, "key": kwargs.pop("key")},
+ *children,
+ )
+ else:
+ attributes_and_children = (
+ {"key": kwargs.pop("key")},
+ maybe_attributes,
+ *children,
+ )
+ else:
+ attributes_and_children = ({"key": kwargs.pop("key")},)
+ warn(
+ "An element's 'key' must be declared in an attribute dict instead "
+ "of as a keyword argument. This will error in a future version.",
+ DeprecationWarning,
+ )
+
+ if kwargs:
+ msg = f"Extra keyword arguments {kwargs}"
+ raise ValueError(msg)
+
model: VdomDict = {"tagName": tag}
- attributes, children = coalesce_attributes_and_children(attributes_and_children)
- attributes, event_handlers = separate_attributes_and_event_handlers(
- attributes, event_handlers or {}
- )
+ if not attributes_and_children:
+ return model
+
+ attributes, children = separate_attributes_and_children(attributes_and_children)
+ key = attributes.pop("key", None)
+ attributes, event_handlers = separate_attributes_and_event_handlers(attributes)
if attributes:
+ if REACTPY_CHECK_JSON_ATTRS.current:
+ json.dumps(attributes)
model["attributes"] = attributes
if children:
model["children"] = children
- if event_handlers:
- model["eventHandlers"] = event_handlers
-
if key is not None:
model["key"] = key
- if import_source is not None:
- model["importSource"] = import_source
+ if event_handlers:
+ model["eventHandlers"] = event_handlers
return model
-class _VdomDictConstructor(Protocol):
- def __call__(
- self,
- *attributes_and_children: VdomAttributesAndChildren,
- key: str | int | None = ...,
- event_handlers: Optional[EventHandlerMapping] = ...,
- import_source: Optional[ImportSourceDict] = ...,
- ) -> VdomDict:
- ...
-
-
def make_vdom_constructor(
- tag: str, allow_children: bool = True
-) -> _VdomDictConstructor:
+ tag: str, allow_children: bool = True, import_source: ImportSourceDict | None = None
+) -> VdomDictConstructor:
"""Return a constructor for VDOM dictionaries with the given tag name.
The resulting callable will have the same interface as :func:`vdom` but without its
first ``tag`` argument.
"""
- def constructor(
- *attributes_and_children: VdomAttributesAndChildren,
- key: str | int | None = None,
- event_handlers: Optional[EventHandlerMapping] = None,
- import_source: Optional[ImportSourceDict] = None,
- ) -> VdomDict:
- model = vdom(
- tag,
- *attributes_and_children,
- key=key,
- event_handlers=event_handlers,
- import_source=import_source,
- )
+ def constructor(*attributes_and_children: Any, **kwargs: Any) -> VdomDict:
+ model = vdom(tag, *attributes_and_children, **kwargs)
if not allow_children and "children" in model:
- raise TypeError(f"{tag!r} nodes cannot have children.")
+ msg = f"{tag!r} nodes cannot have children."
+ raise TypeError(msg)
+ if import_source:
+ model["importSource"] = import_source
return model
# replicate common function attributes
@@ -223,30 +236,42 @@ def constructor(
"element represented by a :class:`VdomDict`."
)
- frame = inspect.currentframe()
- if frame is not None and frame.f_back is not None and frame.f_back is not None:
- module = frame.f_back.f_globals.get("__name__") # module in outer frame
- if module is not None:
- qualname = module + "." + tag
- constructor.__module__ = module
- constructor.__qualname__ = qualname
+ module_name = f_module_name(1)
+ if module_name:
+ constructor.__module__ = module_name
+ constructor.__qualname__ = f"{module_name}.{tag}"
+
+ return cast(VdomDictConstructor, constructor)
+
+
+def custom_vdom_constructor(func: _CustomVdomDictConstructor) -> VdomDictConstructor:
+ """Cast function to VdomDictConstructor"""
+
+ @wraps(func)
+ def wrapper(*attributes_and_children: Any) -> VdomDict:
+ attributes, children = separate_attributes_and_children(attributes_and_children)
+ key = attributes.pop("key", None)
+ attributes, event_handlers = separate_attributes_and_event_handlers(attributes)
+ return func(attributes, children, key, event_handlers)
- return constructor
+ return cast(VdomDictConstructor, wrapper)
-def coalesce_attributes_and_children(
+def separate_attributes_and_children(
values: Sequence[Any],
-) -> Tuple[Mapping[str, Any], List[Any]]:
+) -> tuple[dict[str, Any], list[Any]]:
if not values:
return {}, []
+ attributes: dict[str, Any]
children_or_iterables: Sequence[Any]
- attributes, *children_or_iterables = values
- if not _is_attributes(attributes):
+ if _is_attributes(values[0]):
+ attributes, *children_or_iterables = values
+ else:
attributes = {}
children_or_iterables = values
- children: List[Any] = []
+ children: list[Any] = []
for child in children_or_iterables:
if _is_single_child(child):
children.append(child)
@@ -257,22 +282,19 @@ def coalesce_attributes_and_children(
def separate_attributes_and_event_handlers(
- attributes: Mapping[str, Any], event_handlers: EventHandlerMapping
-) -> Tuple[Dict[str, Any], EventHandlerDict]:
+ attributes: Mapping[str, Any]
+) -> tuple[dict[str, Any], EventHandlerDict]:
separated_attributes = {}
- separated_event_handlers: Dict[str, List[EventHandlerType]] = {}
-
- for k, v in event_handlers.items():
- separated_event_handlers[k] = [v]
+ separated_event_handlers: dict[str, EventHandlerType] = {}
for k, v in attributes.items():
-
handler: EventHandlerType
if callable(v):
handler = EventHandler(to_event_handler_function(v))
elif (
- # isinstance check on protocols is slow, function attr check is a quick filter
+ # isinstance check on protocols is slow - use function attr pre-check as a
+ # quick filter before actually performing slow EventHandlerType type check
hasattr(v, "function")
and isinstance(v, EventHandlerType)
):
@@ -281,63 +303,49 @@ def separate_attributes_and_event_handlers(
separated_attributes[k] = v
continue
- if k not in separated_event_handlers:
- separated_event_handlers[k] = [handler]
- else:
- separated_event_handlers[k].append(handler)
+ separated_event_handlers[k] = handler
- flat_event_handlers_dict = {
- k: merge_event_handlers(h) for k, h in separated_event_handlers.items()
- }
-
- return separated_attributes, flat_event_handlers_dict
+ return separated_attributes, dict(separated_event_handlers.items())
def _is_attributes(value: Any) -> bool:
return isinstance(value, Mapping) and "tagName" not in value
-if IDOM_DEBUG_MODE.current:
-
- _debug_is_attributes = _is_attributes
-
- def _is_attributes(value: Any) -> bool:
- result = _debug_is_attributes(value)
- if result and "children" in value:
- logger.error(f"Reserved key 'children' found in attributes {value}")
- return result
-
-
def _is_single_child(value: Any) -> bool:
- return isinstance(value, (str, Mapping)) or not hasattr(value, "__iter__")
-
-
-if IDOM_DEBUG_MODE.current:
+ if isinstance(value, (str, Mapping)) or not hasattr(value, "__iter__"):
+ return True
+ if REACTPY_DEBUG_MODE.current:
+ _validate_child_key_integrity(value)
+ return False
+
+
+def _validate_child_key_integrity(value: Any) -> None:
+ if hasattr(value, "__iter__") and not hasattr(value, "__len__"):
+ warn(
+ f"Did not verify key-path integrity of children in generator {value} "
+ "- pass a sequence (i.e. list of finite length) in order to verify"
+ )
+ else:
+ for child in value:
+ if isinstance(child, ComponentType) and child.key is None:
+ warn(f"Key not specified for child in list {child}", UserWarning)
+ elif isinstance(child, Mapping) and "key" not in child:
+ # remove 'children' to reduce log spam
+ child_copy = {**child, "children": _EllipsisRepr()}
+ warn(f"Key not specified for child in list {child_copy}", UserWarning)
- _debug_is_single_child = _is_single_child
- def _is_single_child(value: Any) -> bool:
- if _debug_is_single_child(value):
- return True
+class _CustomVdomDictConstructor(Protocol):
+ def __call__(
+ self,
+ attributes: VdomAttributes,
+ children: Sequence[VdomChild],
+ key: Key | None,
+ event_handlers: EventHandlerDict,
+ ) -> VdomDict: ...
- from .proto import ComponentType
- if hasattr(value, "__iter__") and not hasattr(value, "__len__"):
- logger.error(
- f"Did not verify key-path integrity of children in generator {value} "
- "- pass a sequence (i.e. list of finite length) in order to verify"
- )
- else:
- for child in value:
- if isinstance(child, ComponentType) and child.key is None:
- logger.error(f"Key not specified for child in list {child}")
- elif isinstance(child, Mapping) and "key" not in child:
- # remove 'children' to reduce log spam
- child_copy = {**child, "children": _EllipsisRepr()}
- logger.error(f"Key not specified for child in list {child_copy}")
-
- return False
-
- class _EllipsisRepr:
- def __repr__(self) -> str:
- return "..."
+class _EllipsisRepr:
+ def __repr__(self) -> str:
+ return "..."
diff --git a/tests/test_core/__init__.py b/src/py/reactpy/reactpy/future.py
similarity index 100%
rename from tests/test_core/__init__.py
rename to src/py/reactpy/reactpy/future.py
diff --git a/src/idom/html.py b/src/py/reactpy/reactpy/html.py
similarity index 61%
rename from src/idom/html.py
rename to src/py/reactpy/reactpy/html.py
index dd7ac24af..22d318639 100644
--- a/src/idom/html.py
+++ b/src/py/reactpy/reactpy/html.py
@@ -1,5 +1,10 @@
"""
-**Dcument metadata**
+
+**Fragment**
+
+- :func:`_`
+
+**Document metadata**
- :func:`base`
- :func:`head`
@@ -10,7 +15,6 @@
**Content sectioning**
-- :func:`body`
- :func:`address`
- :func:`article`
- :func:`aside`
@@ -148,17 +152,164 @@
- :func:`slot`
- :func:`template`
+
+.. autofunction:: _
"""
from __future__ import annotations
-from typing import Any, Mapping
+from collections.abc import Sequence
+
+from reactpy.core.types import (
+ EventHandlerDict,
+ Key,
+ VdomAttributes,
+ VdomChild,
+ VdomDict,
+)
+from reactpy.core.vdom import custom_vdom_constructor, make_vdom_constructor
+
+__all__ = (
+ "_",
+ "a",
+ "abbr",
+ "address",
+ "area",
+ "article",
+ "aside",
+ "audio",
+ "b",
+ "base",
+ "bdi",
+ "bdo",
+ "blockquote",
+ "br",
+ "button",
+ "canvas",
+ "caption",
+ "cite",
+ "code",
+ "col",
+ "colgroup",
+ "data",
+ "dd",
+ "del_",
+ "details",
+ "dialog",
+ "div",
+ "dl",
+ "dt",
+ "em",
+ "embed",
+ "fieldset",
+ "figcaption",
+ "figure",
+ "footer",
+ "form",
+ "h1",
+ "h2",
+ "h3",
+ "h4",
+ "h5",
+ "h6",
+ "head",
+ "header",
+ "hr",
+ "i",
+ "iframe",
+ "img",
+ "input",
+ "ins",
+ "kbd",
+ "label",
+ "legend",
+ "li",
+ "link",
+ "main",
+ "map",
+ "mark",
+ "math",
+ "menu",
+ "menuitem",
+ "meta",
+ "meter",
+ "nav",
+ "noscript",
+ "object",
+ "ol",
+ "option",
+ "output",
+ "p",
+ "param",
+ "picture",
+ "portal",
+ "pre",
+ "progress",
+ "q",
+ "rp",
+ "rt",
+ "ruby",
+ "s",
+ "samp",
+ "script",
+ "section",
+ "select",
+ "slot",
+ "small",
+ "source",
+ "span",
+ "strong",
+ "style",
+ "sub",
+ "summary",
+ "sup",
+ "svg",
+ "table",
+ "tbody",
+ "td",
+ "template",
+ "textarea",
+ "tfoot",
+ "th",
+ "thead",
+ "time",
+ "title",
+ "tr",
+ "track",
+ "u",
+ "ul",
+ "var",
+ "video",
+ "wbr",
+)
+
+
+def _fragment(
+ attributes: VdomAttributes,
+ children: Sequence[VdomChild],
+ key: Key | None,
+ event_handlers: EventHandlerDict,
+) -> VdomDict:
+ """An HTML fragment - this element will not appear in the DOM"""
+ if attributes or event_handlers:
+ msg = "Fragments cannot have attributes besides 'key'"
+ raise TypeError(msg)
+ model: VdomDict = {"tagName": ""}
+
+ if children:
+ model["children"] = children
+
+ if key is not None:
+ model["key"] = key
+
+ return model
-from .core.proto import VdomDict
-from .core.vdom import coalesce_attributes_and_children, make_vdom_constructor
+# FIXME: https://github.com/PyCQA/pylint/issues/5784
+_ = custom_vdom_constructor(_fragment)
-# Dcument metadata
+
+# Document metadata
base = make_vdom_constructor("base")
head = make_vdom_constructor("head")
link = make_vdom_constructor("link")
@@ -167,7 +318,6 @@
title = make_vdom_constructor("title")
# Content sectioning
-body = make_vdom_constructor("body")
address = make_vdom_constructor("address")
article = make_vdom_constructor("article")
aside = make_vdom_constructor("aside")
@@ -232,14 +382,14 @@
area = make_vdom_constructor("area", allow_children=False)
audio = make_vdom_constructor("audio")
img = make_vdom_constructor("img", allow_children=False)
-map = make_vdom_constructor("map")
+map = make_vdom_constructor("map") # noqa: A001
track = make_vdom_constructor("track")
video = make_vdom_constructor("video")
# Embedded content
embed = make_vdom_constructor("embed", allow_children=False)
iframe = make_vdom_constructor("iframe", allow_children=False)
-object = make_vdom_constructor("object")
+object = make_vdom_constructor("object") # noqa: A001
param = make_vdom_constructor("param")
picture = make_vdom_constructor("picture")
portal = make_vdom_constructor("portal", allow_children=False)
@@ -254,11 +404,18 @@
noscript = make_vdom_constructor("noscript")
-def script(
- *attributes_and_children: Mapping[str, Any] | str,
- key: str | int | None = None,
+def _script(
+ attributes: VdomAttributes,
+ children: Sequence[VdomChild],
+ key: Key | None,
+ event_handlers: EventHandlerDict,
) -> VdomDict:
- """Create a new `<{script}> `__ element.
+ """Create a new `