├── site └── demos │ ├── components │ └── ajax-get │ │ ├── data.json │ │ ├── styles.css │ │ └── ajax-get.js.html │ ├── ajax-get.js │ └── index.html ├── captain-definition ├── babel.config.js ├── docs ├── source │ ├── img │ │ └── debug-error.png │ ├── installation.md │ ├── components │ │ ├── binding.md │ │ ├── template.md │ │ ├── store.md │ │ ├── events.md │ │ ├── directives.md │ │ ├── index.md │ │ └── configuration.md │ ├── conf.py │ ├── faq.md │ ├── index.md │ └── examples.md ├── requirements.txt ├── Makefile └── make.bat ├── test ├── helpers.js ├── component.test.js ├── emerj.test.js ├── onchange.test.js ├── events.test.js ├── component-helpers.test.js ├── directives.test.js └── utils.test.js ├── tsconfig.json ├── .gitignore ├── .github ├── workflows │ └── js.yml ├── ISSUE_TEMPLATE │ └── config.yml └── FUNDING.yml ├── Dockerfile ├── .readthedocs.yaml ├── pyproject.toml ├── rollup.config.js ├── DEVELOPING.md ├── LICENSE ├── logo.svg ├── package.json ├── README.md ├── CHANGELOG.md ├── src ├── events.js ├── directives.js ├── component-helpers.js ├── index.js ├── emerj.js ├── component.js └── utils.js ├── dist └── dlite.es.js └── poetry.lock /site/demos/components/ajax-get/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "neat": "cool" 3 | } -------------------------------------------------------------------------------- /site/demos/components/ajax-get/styles.css: -------------------------------------------------------------------------------- 1 | h2 { 2 | color: aquamarine; 3 | } -------------------------------------------------------------------------------- /captain-definition: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": 2, 3 | "dockerfilePath": "./Dockerfile" 4 | } 5 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ["@babel/preset-env"], 3 | plugins: [], 4 | }; 5 | -------------------------------------------------------------------------------- /docs/source/img/debug-error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamghill/dlite/main/docs/source/img/debug-error.png -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx 2 | linkify-it-py 3 | myst-parser 4 | furo 5 | sphinx-copybutton 6 | sphinx-autobuild 7 | toml -------------------------------------------------------------------------------- /test/helpers.js: -------------------------------------------------------------------------------- 1 | export class Fixture { 2 | constructor(data, expected, name = null) { 3 | this.data = data; 4 | this.expected = expected; 5 | 6 | self.name = name || data; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "noEmit": true, 6 | "target": "es6", 7 | "moduleResolution": "node" 8 | }, 9 | "include": ["src/**/*"], 10 | "exclude": ["node_modules/"] 11 | } 12 | -------------------------------------------------------------------------------- /site/demos/components/ajax-get/ajax-get.js.html: -------------------------------------------------------------------------------- 1 |
2 |

{this.activity.activity}

3 | 9 |
-------------------------------------------------------------------------------- /test/component.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @vitest-environment jsdom 3 | */ 4 | 5 | import { test, expect } from "vitest"; 6 | 7 | import Component from "../src/component.js"; 8 | 9 | test("initialize", () => { 10 | const template = `
Hello World
`; 11 | 12 | const actual = Component({ 13 | template: template, 14 | tagName: "my-tag", 15 | }); 16 | 17 | expect(actual).toBe(null); 18 | }); 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # JS Projects 2 | *.py[co] 3 | __pycache__* 4 | 5 | # Packages 6 | *.egg 7 | *.egg-info 8 | .cache 9 | eggs 10 | parts 11 | bin 12 | var 13 | sdist 14 | develop-eggs 15 | .installed.cfg 16 | MANIFEST 17 | .idea/* 18 | .idea 19 | .idea/libraries/sass_stdlib.xml 20 | .sass-cache 21 | .vscode 22 | .DS_Store 23 | node_modules/ 24 | package-lock.json 25 | 26 | dev/ 27 | perf/ 28 | docs/build/ 29 | deploykey 30 | deploykey.pub 31 | -------------------------------------------------------------------------------- /.github/workflows/js.yml: -------------------------------------------------------------------------------- 1 | name: JavaScript 2 | on: [push] 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | 8 | steps: 9 | - uses: actions/checkout@v2.3.4 10 | with: 11 | fetch-depth: 1 12 | 13 | - name: Set up node 14 | uses: actions/setup-node@v2.1.2 15 | 16 | - name: Install node packages 17 | run: npm install 18 | 19 | - name: Test 20 | run: npm run t 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.10.8-slim as python-base 2 | 3 | # Copy files and directories to docker 4 | COPY poetry.lock pyproject.toml package.json ./ 5 | COPY docs ./docs 6 | COPY site ./site 7 | 8 | # Install dependencies 9 | RUN pip install poetry 10 | RUN poetry install --only main 11 | 12 | # Copy changelog to docs 13 | RUN cp CHANGELOG.md docs/source/changelog.md 14 | 15 | # Build docs in the site directory 16 | RUN poetry run sphinx-build -W -b dirhtml docs/source site 17 | 18 | FROM nginx 19 | COPY --from=python-base ./site /usr/share/nginx/html 20 | -------------------------------------------------------------------------------- /test/emerj.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @vitest-environment jsdom 3 | */ 4 | 5 | import { describe, test, expect } from "vitest"; 6 | 7 | import { nodesByKey } from "../src/emerj.js"; 8 | 9 | test("nodesByKey", () => { 10 | const el = document.createElement("div"); 11 | const child = document.createElement("p"); 12 | child.id = "asdf"; 13 | el.prepend(child); 14 | 15 | const makeKey = (node) => node.id; 16 | 17 | const actual = nodesByKey(el, makeKey); 18 | 19 | const expected = '

'; 20 | 21 | expect(actual.asdf.outerHTML).toEqual(expected); 22 | }); 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Bug Report 4 | url: https://github.com/adamghill/dlite/discussions/new 5 | about: Submit a bug or an issue 6 | - name: Feature request 7 | url: https://github.com/adamghill/dlite/discussions/new 8 | about: For ideas or feature requests 9 | - name: Support questions & other 10 | url: https://github.com/adamghill/dlite/discussions/new 11 | about: If you have a question or need help using the library 12 | - name: Documentation issue 13 | url: https://github.com/adamghill/dlite/discussions/new 14 | about: For documentation improvements 15 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # Read the Docs configuration file 2 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 3 | 4 | # Required 5 | version: 2 6 | 7 | # Set the OS, Python version, and other tools you might need 8 | build: 9 | os: ubuntu-24.04 10 | tools: 11 | python: "3.13" 12 | 13 | # Build documentation in the "docs/" directory with Sphinx 14 | sphinx: 15 | configuration: docs/source/conf.py 16 | 17 | # Optionally, but recommended, 18 | # declare the Python requirements required to build your documentation 19 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 20 | python: 21 | install: 22 | - requirements: docs/requirements.txt 23 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "dlite-docs" 3 | version = "0.0.0" 4 | description = "Documentation for dlite" 5 | authors = ["adamghill "] 6 | license = "MIT" 7 | repository = "https://github.com/adamghill/dlite/" 8 | homepage = "https://dlitejs.com" 9 | documentation = "https://dlitejs.com" 10 | 11 | [tool.poetry.urls] 12 | "Funding" = "https://github.com/sponsors/adamghill" 13 | 14 | [tool.poetry.dependencies] 15 | python = ">=3.7,<4.0" 16 | Sphinx = "*" 17 | linkify-it-py = "*" 18 | myst-parser = "*" 19 | furo = "*" 20 | sphinx-copybutton = "*" 21 | sphinx-autobuild = "*" 22 | toml = "*" 23 | attrs = "*" 24 | 25 | [build-system] 26 | requires = ["poetry-core>=1.0.0"] 27 | build-backend = "poetry.core.masonry.api" 28 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Rollup config 3 | * 4 | * This script config allows us to create a bundle of the library 5 | * the library is meant to be used at ES module, or 15 | ``` 16 | 17 | You can also specify a version using the `unpkg` CDN to minimize the effect of breaking changes. 18 | 19 | ```html 20 | 23 | ``` 24 | 25 | ## NPM 26 | 27 | You can also install via NPM. 28 | 29 | ```shell 30 | npm install dlite 31 | ``` 32 | 33 | Then, import `dlite` in your JavaScript. 34 | 35 | ```js 36 | import Dlite from 'dlite'; 37 | ``` 38 | -------------------------------------------------------------------------------- /docs/source/components/binding.md: -------------------------------------------------------------------------------- 1 | # Data Binding 2 | 3 | The `@bind` attribute creates two-way data bindings on `input`, `textarea`, and `select` elements. Bound elements will update the appropriate `data` on input events. 4 | 5 | :::{code} html 6 | :force: true 7 | 18 | 19 | 32 | ::: 33 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=source 11 | set BUILDDIR=build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /DEVELOPING.md: -------------------------------------------------------------------------------- 1 | # Developing `dlite` 2 | 3 | ## Installation for documentation 4 | 5 | 1. `poetry install` 6 | 7 | ## Run documentation site 8 | 9 | 1. `npm run sa` 10 | 1. Go to http://localhost:8000 in your browser 11 | 12 | ## Run documentation site via Docker 13 | 14 | 1. docker build -t dlite . && docker container run -p 8557:80 dlite:latest 15 | 1. Go to http://localhost:8557 in your browser 16 | 17 | ## Build documentation site 18 | 19 | 1. `npm run sb` 20 | 21 | ## Installation for demos 22 | 23 | 1. `brew install --cask growlnotify` 24 | 1. `npm install dev-server -g` 25 | 26 | ## Run demo site 27 | 28 | 1. `npm run r` 29 | 1. Go to http://localhost:4000/site/demos/ in your browser 30 | 31 | ## Publish new version 32 | 33 | 1. Update `CHANGELOG.md` 34 | 1. Update version in `package.json` 35 | 1. `npm run t && npm run b` 36 | 1. Commit/tag/push changes 37 | 1. `npm publish` 38 | 1. [Create a new release](https://github.com/adamghill/dlite/releases/new) 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 Adam Hill 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | 3 | import json 4 | 5 | 6 | # -- Project information 7 | 8 | project = "dlite" 9 | copyright = "2023, Adam Hill" 10 | author = "Adam Hill" 11 | 12 | with open("../../package.json") as f: 13 | package = json.load(f) 14 | 15 | version = package["version"] 16 | release = version 17 | 18 | 19 | # -- General configuration 20 | 21 | extensions = [ 22 | "sphinx.ext.duration", 23 | "sphinx.ext.doctest", 24 | "sphinx.ext.autodoc", 25 | "sphinx.ext.autosummary", 26 | "sphinx.ext.intersphinx", 27 | "myst_parser", 28 | "sphinx_copybutton", 29 | "sphinx.ext.napoleon", 30 | "sphinx.ext.autosectionlabel", 31 | ] 32 | 33 | intersphinx_mapping = { 34 | "python": ("https://docs.python.org/3/", None), 35 | "sphinx": ("https://www.sphinx-doc.org/en/master/", None), 36 | } 37 | intersphinx_disabled_domains = ["std"] 38 | 39 | templates_path = ["_templates"] 40 | 41 | # -- Options for HTML output 42 | 43 | html_theme = "furo" 44 | 45 | # -- Options for EPUB output 46 | epub_show_urls = "footnote" 47 | 48 | autosectionlabel_prefix_document = True 49 | autosectionlabel_maxdepth = 3 50 | 51 | myst_heading_anchors = 3 52 | myst_enable_extensions = ["linkify", "colon_fence"] 53 | -------------------------------------------------------------------------------- /test/onchange.test.js: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import { objectOnChange } from "../src/utils.js"; 3 | 4 | test("set new property", () => { 5 | const initialData = {}; 6 | const data = objectOnChange(initialData, () => {}); 7 | data.name = "Litedom"; 8 | expect(data.name).toBe("Litedom"); 9 | }); 10 | 11 | test("set new property affecting initial source", () => { 12 | const initialData = {}; 13 | const data = objectOnChange(initialData, () => {}); 14 | data.name = "Litedom"; 15 | expect(initialData.name).toBe("Litedom"); 16 | }); 17 | 18 | test("get ___target___ #", () => { 19 | const initialData = {}; 20 | const data = objectOnChange(initialData, () => {}); 21 | data.name = "Litedom"; 22 | expect(data["#"]).toEqual({ name: "Litedom" }); 23 | }); 24 | 25 | test("objectOnChange callback function", () => 26 | new Promise((done) => { 27 | const initialData = {}; 28 | const data = objectOnChange(initialData, () => { 29 | expect(data.name).toBe("Litedom"); 30 | done(); 31 | }); 32 | 33 | data.name = "Litedom"; 34 | })); 35 | 36 | test("with array", () => { 37 | const initialData = { 38 | myObj: {}, 39 | }; 40 | const data = objectOnChange(initialData, () => {}); 41 | data.myObj.myArray = [1, 2, 3]; 42 | expect(data.myObj.myArray.length).toEqual(3); 43 | }); 44 | -------------------------------------------------------------------------------- /site/demos/ajax-get.js: -------------------------------------------------------------------------------- 1 | import Dlite, { fetcher } from "//unpkg.com/dlite"; 2 | // import Dlite, { fetcher } from "../../src/index.js"; 3 | 4 | const template = await fetcher("components/ajax-get/ajax-get.js.html"); 5 | 6 | const formatter = new Intl.NumberFormat("en-US", { 7 | style: "currency", 8 | currency: "USD", 9 | }); 10 | 11 | const componentConfigurations = [ 12 | { 13 | tagName: "ajax-get", 14 | template: template, 15 | }, 16 | { 17 | el: "#template-shadow", 18 | template: template, 19 | }, 20 | { 21 | el: "#template-no-shadow", 22 | template: template, 23 | shadowDOM: false, 24 | }, 25 | { 26 | el: "#in-place-template", 27 | }, 28 | ]; 29 | 30 | const components = Dlite(componentConfigurations, { 31 | getPrice() { 32 | if (this.data.activity.price == 0) { 33 | return "Free!"; 34 | } else if (!this.data.activity.price) { 35 | return ""; 36 | } 37 | 38 | return formatter.format(this.data.activity.price); 39 | }, 40 | debug: true, 41 | shadowDOM: true, 42 | data: { activity: {} }, 43 | async created() { 44 | const activity = await fetcher("https://www.boredapi.com/api/activity"); 45 | 46 | // Simulate network latency 47 | setTimeout(() => { 48 | this.data.activity = activity; 49 | }, 500); 50 | }, 51 | }); 52 | 53 | console.log("components", components); 54 | -------------------------------------------------------------------------------- /site/demos/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 26 | 27 | 32 | 33 | 62 | 63 |
64 | Not a template 65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/source/components/template.md: -------------------------------------------------------------------------------- 1 | # Template 2 | 3 | `dlite` uses an HTML-based template syntax that declaratively binds the rendered DOM to the component’s data. 4 | 5 | ## Interpolation 6 | 7 | Access `data` in HTML templates via `this.{data-property-name}`. 8 | 9 | ```html 10 | 23 | 24 | 28 | ``` 29 | 30 | ## Scoped CSS 31 | 32 | Components are created as `Custom Elements` which are attached to a `Shadow DOM` by default. The `Shadow DOM` provides encapsulation for the component so that it will not be affected by page CSS. The component can include a `style` tag which will affect just that component. 33 | 34 | :::{code} html 35 | :force: true 36 | 46 | 47 | 52 | 53 | 62 | ::: 63 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dlite", 3 | "pkgName": "dlite", 4 | "version": "0.19.0", 5 | "author": "adamghill", 6 | "license": "MIT", 7 | "description": "A tiny, blazing fast view library that creates reactive Web Components.", 8 | "main": "dist/dlite.es.js", 9 | "module": "dist/dlite.es.js", 10 | "type": "module", 11 | "files": [ 12 | "dist", 13 | "src", 14 | "docs/source" 15 | ], 16 | "keywords": [ 17 | "reactive", 18 | "bind", 19 | "view", 20 | "templating", 21 | "directives", 22 | "dom" 23 | ], 24 | "scripts": { 25 | "size": "gzip-size ./dist/dlite.es.js --include-original && echo `brotli dist/dlite.es.js -c | wc -c | numfmt --to=iec --suffix=B --format='%.2f'` '(brotli)'", 26 | "check-size": "gzip-size ./dist/dlite.es.js --raw", 27 | "ts": "tsc", 28 | "t": "vitest run", 29 | "tw": "vitest", 30 | "b": "npm run build && stat -f%z dist/dlite.es.js | numfmt --to=iec --suffix=B --format='%.2f' && npm run size", 31 | "build": "rollup -c", 32 | "r": "DEV_SERVER_PORT=4000 DEV_SERVER_ROOT=. dev-server", 33 | "sb": "cp CHANGELOG.md docs/source/changelog.md && poetry run sphinx-build -W -b dirhtml docs/source site", 34 | "sa": "cp CHANGELOG.md docs/source/changelog.md && poetry run sphinx-autobuild -W -b dirhtml docs/source docs/build" 35 | }, 36 | "jest": { 37 | "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$" 38 | }, 39 | "repository": { 40 | "type": "git", 41 | "url": "git+https://github.com/adamghill/dlite.git" 42 | }, 43 | "bugs": { 44 | "url": "https://github.com/adamghill/dlite/discussions" 45 | }, 46 | "homepage": "https://github.com/adamghill/dlite", 47 | "devDependencies": { 48 | "@babel/core": "^7.4.3", 49 | "@babel/preset-env": "^7.4.3", 50 | "cross-var": "^1.1.0", 51 | "gh-pages": "^2.0.1", 52 | "gzip-size-cli": "^5.1.0", 53 | "jsdom": "^21.1.0", 54 | "mutationobserver-shim": "^0.3.3", 55 | "rollup": "^3.13.0", 56 | "rollup-plugin-banner2": "^1.2.2", 57 | "@rollup/plugin-terser": "^0.4.0", 58 | "typescript": "^4.9.5", 59 | "vite": "^4.1.1", 60 | "vitest": "^0.28.4" 61 | }, 62 | "dependencies": { 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/source/faq.md: -------------------------------------------------------------------------------- 1 | # FAQ 2 | 3 | ## How big is `dlite`? 4 | 5 | `dlite` is **<5 kB** when gzipped. 6 | 7 | ## Does size _really_ matter? 8 | 9 | For JavaScript libraries, yes. The less JavaScript to download and parse, the faster your site will render. Ipso facto, your users will be happier and the world will be a better place. 10 | 11 | ## Why yet another JavaScript library? 12 | 13 | Why not?! 😉 14 | 15 | _A real answer from the original author of `Litedom`:_ 16 | 17 | [Mardix is] an UI Tech Lead Application Engineer at Bank of America, who deals with many static sites, and see how stuff can sometimes be frustrating for team members when it come to choices. 18 | 19 | So, one week-end afternoon (4/20 weekend 2019 :), while working on a personal project using a static site generator, I thought it was way too much of an overhead to bring in something like Vue, React or Angular, just to make a small piece reactive on the personal static site. 20 | 21 | So [Mardix] decided to create Litedom, to just be a simple drop-in view library that can make any sections of the site reactive without the overhead. [Mardix] wanted... HTML to stay as is. No React, no Vue, just... HTML and me. 22 | 23 | ## Does it replace React, Vue etc? 24 | 25 | Not really. `dlite` is targeting a different set of applications, small web apps or static sites. Some times you just want a little bit of reactivity without including a huge library. It follows the same paradigms as Vue.js, just on a much smaller scale. 26 | 27 | ## Isn't the DOM slow? 28 | 29 | No, the DOM is ridiculously fast. 30 | 31 | [The DOM isn't slow, you are](https://korynunn.wordpress.com/2013/03/19/the-dom-isnt-slow-you-are/) goes into more depth, although skip it if you do not want to read some spicy takes. 32 | 33 | `dlite` uses `emerj` to modify the DOM and it has a section about performance in [Emerj.js: efficient HTML UI in 60 lines](https://blog.brush.co.nz/2017/11/emerj-js-efficient-html-ui-in-60-lines). 34 | 35 | ## Who created this? 36 | 37 | [Mardix](https://github.com/mardix) created [Litedom](https://github.com/mardix/litedom) although it hasn't been updated since 2019. [adamghill](https://github.com/adamghill) tried it out in 2023 and was so impressed by the approach he wanted to improve it. He decided to hard fork it, fix bugs, update the code, re-write the docs, and re-brand the library so that it can get more ❤️. -------------------------------------------------------------------------------- /docs/source/index.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | `dlite` creates `Web Components` and interactive web pages easily without the bloat of big frameworks. It can be effortlessly added into existing HTML pages to create reusable components for web applications. `dlite` is perfect for simple, but dynamic static sites or when you want to progressively enhance a site without changing too much HTML. 4 | 5 | The [code](https://github.com/adamghill/dlite) is licensed as MIT. PRs and bug reports with failing tests are extremely appreciated. 6 | 7 | ## ⭐ Features 8 | 9 | - Seriously tiny: **<10 kB** (**<5 kB** when gzipped) 10 | - No dependencies, no virtual DOM, no JSX, and no build tool required 11 | - Reactive Web Components 12 | - Progressive [template language](components/template.md#interpolation) that leverages `template literals` 13 | - Props 14 | - Computed properties 15 | - [Two-way data binding](components/binding.md) 16 | - [Events handling](components/events.md) 17 | - Component lifecycle hooks 18 | - [Directives](components/directives.md) (e.g. `if`/`else`, `for`, `style`, `class`) 19 | - Shadow DOM by default with [scoped CSS](components/template.md#scoped-css) 20 | - Put a script tag in your HTML and _go_ ⚡ 21 | 22 | It is compatible with all modern browsers that support [`ES2015`/`ES6`](https://caniuse.com/#feat=es6), [`ESM`](https://caniuse.com/?search=esm), and [`Proxy`](https://caniuse.com/#search=proxy). 23 | 24 | ## 🧠 Related projects 25 | 26 | Similar projects to `dlite` are listed on https://unsuckjs.com/. 27 | 28 | ## 🙌 Acknowledgements 29 | 30 | `dlite` is forked from the fantastic work done by [Mardix](https://github.com/mardix) with [Litedom](https://github.com/mardix/litedom). 31 | 32 | It includes code from these great libraries: 33 | - https://github.com/bryhoyt/emerj 34 | - https://github.com/sindresorhus/on-change 35 | 36 | Logo: https://openmoji.org/library/emoji-1F4A1/ 37 | 38 | ```{toctree} 39 | :maxdepth: 2 40 | :hidden: 41 | 42 | self 43 | changelog 44 | installation 45 | faq 46 | examples 47 | 48 | ``` 49 | 50 | ```{toctree} 51 | :caption: Components 52 | :maxdepth: 2 53 | :hidden: 54 | 55 | components/index 56 | components/configuration 57 | components/template 58 | components/binding 59 | components/directives 60 | components/events 61 | components/store 62 | ``` 63 | 64 | ```{toctree} 65 | :caption: Misc 66 | :maxdepth: 2 67 | :hidden: 68 | 69 | GitHub 70 | Releases 71 | NPM 72 | Sponsor 73 | ``` 74 | -------------------------------------------------------------------------------- /docs/source/components/store.md: -------------------------------------------------------------------------------- 1 | # Store 2 | 3 | To share state with multiple instances, it's recommended to use a state manager such as Redux or [Litestate](https://github.com/mardix/litestate). 4 | 5 | ````{note} 6 | The store must implement the following methods. 7 | 8 | - `getState()`: Returns the full state of the store. 9 | - `subscribe(callback: function)`: A subscription method that will execute when the state is updated. 10 | 11 | If the state manager doesn't provide these methods by default, it is possible to extend it. 12 | 13 | ```js 14 | const myStateManager = new someStore() 15 | 16 | // Now the store contains getState() and subscribe(callback) 17 | const store = { 18 | getState() { 19 | return myStateManager.state; 20 | }, 21 | subscribe(callback) { 22 | return myStateManager.onChange(callback); 23 | }, 24 | ...myStateManager 25 | } 26 | ``` 27 | ```` 28 | 29 | ## Setup 30 | 31 | ```js 32 | Dlite({ 33 | el: '#app', 34 | $store: STORE_INSTANCE, 35 | }); 36 | ``` 37 | 38 | ## Access the store 39 | 40 | ### JavaScript 41 | 42 | The store is available in methods via `this.$store`. 43 | 44 | ```js 45 | Dlite({ 46 | el: '#app', 47 | $store: STORE_INSTANCE, 48 | doSomething() { 49 | this.$store.doSomething(); 50 | }, 51 | }); 52 | ``` 53 | 54 | ### Template 55 | 56 | To access the store `this.$store` will return the values from `$store.getState()`. 57 | 58 | ```html 59 | 62 | ``` 63 | 64 | ## Example with `Litestate` 65 | 66 | :::{code} html 67 | :force: true 68 | 108 | 109 | 113 | 114 | 119 | ::: 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | dlite logo 3 |

4 |

dlite

5 |

A tiny, blazing fast view library that creates reactive Web Components

6 | 7 | ![npm (tag)](https://img.shields.io/npm/v/dlite/latest.svg?style=flat-square) [![gzip bundle size](http://img.badgesize.io/https://unpkg.com/dlite@latest/dist/dlite.es.js?compression=gzip&style=flat-square)](https://unpkg.com/dlite) ![NPM](https://img.shields.io/npm/l/dlite.svg?style=flat-square) ![GitHub Sponsors](https://img.shields.io/github/sponsors/adamghill?color=blue&style=flat-square) 8 | 9 | ## 📖 Complete documentation 10 | 11 | https://dlitejs.com 12 | 13 | ## 🧐 Introduction 14 | 15 | `dlite` creates `Web Components` and interactive web pages easily without the bloat of big frameworks. It can be effortlessly added into existing HTML pages to create reusable components for web applications. `dlite` is perfect for simple, but dynamic static sites or when you want to progressively upgrade a site without changing too much. 16 | 17 | ## ⭐ Features 18 | 19 | - Seriously tiny: **<10 kB** (**<5 kB** when gzipped) 20 | - No dependencies, no virtual DOM, no JSX, and no build tool required 21 | - Reactive Web Components 22 | - Progressive template language that leverages `template literals` 23 | - Props support 24 | - Computed properties 25 | - Two-way data binding 26 | - Events handling 27 | - Component lifecycle hooks 28 | - Directives (e.g. `if`/`else`, `for`, `style`, `class`) 29 | - Shadow DOM by default with scoped CSS 30 | - Put a script tag in your HTML and _go_ ⚡ 31 | 32 | It is compatible with all modern browsers that support [`ES2015`/`ES6`](https://caniuse.com/#feat=es6), [`ESM`](https://caniuse.com/?search=esm), and [`Proxy`](https://caniuse.com/#search=proxy). 33 | 34 | ## 🔧 Installation 35 | 36 | The easiest way to use `dlite` is with a script tag. 37 | 38 | ```html 39 | 42 | ``` 43 | 44 | More [details about installation](https://dlitejs.com/installation/). 45 | 46 | ## 🔄 Canonical counter example 47 | 48 | An example counter component to give you a sense of what `dlite` looks like. See more [examples](https://dlitejs.com/examples/). 49 | 50 | ```html 51 | 67 | 68 | 76 | ``` 77 | 78 | ## 🙋 FAQ 79 | 80 | See the whole FAQ at https://dlitejs.com/faq/. 81 | 82 | ## 🧠 Related projects 83 | 84 | Similar projects to `dlite` are listed on https://unsuckjs.com/. 85 | 86 | ## 🙌 Acknowledgements 87 | 88 | `dlite` is forked from the fantastic work done by [Mardix](https://github.com/mardix) with [Litedom](https://github.com/mardix/litedom). 89 | 90 | It includes code from these great libraries: 91 | - https://github.com/bryhoyt/emerj 92 | - https://github.com/sindresorhus/on-change 93 | 94 | The lightbulb logo is provided from https://openmoji.org/library/emoji-1F4A1/. 95 | -------------------------------------------------------------------------------- /docs/source/components/events.md: -------------------------------------------------------------------------------- 1 | # Events 2 | 3 | Add event listeners to elements with an `@{event-name}` attribute and assign it to a [custom method](configuration.md#custom-methods) in the current component. An `Event` will be passed to the method as the only argument. 4 | 5 | ```{note} 6 | The attribute must be the name of the event without `on`, e.g. `@click` will listen for the `onclick` event. 7 | ``` 8 | 9 | :::{code} html 10 | :force: true 11 | 22 | 23 | 26 | ::: 27 | 28 | ## Passing values 29 | 30 | Use HTML data attributes to pass data to the event. 31 | 32 | :::{code} html 33 | :force: true 34 | 46 | 47 | 52 | ::: 53 | 54 | ## @call 55 | 56 | `@call` is a shortcut that will listen to correct event based on the HTML element type. `@call` will be converted to `@click`, except for the scenarios below. 57 | 58 | ### `HTMLAnchorElement` 59 | 60 | `@call` will get converted to `@click`. 61 | 62 | :::{code} html 63 | :force: true 64 | Click Me 65 | ::: 66 | 67 | will get converted to: 68 | 69 | :::{code} html 70 | :force: true 71 | Click Me 72 | ::: 73 | 74 | ### `HTMLInputElement` and `HTMLTextAreaElement` 75 | 76 | `@call` will get converted to `@input` + `@paste`. 77 | 78 | :::{code} html 79 | :force: true 80 | 81 | ::: 82 | 83 | will get converted to: 84 | 85 | :::{code} html 86 | :force: true 87 | 88 | ::: 89 | 90 | ### `HTMLSelectElement` 91 | 92 | `@call` will get converted to `@change`. 93 | 94 | :::{code} html 95 | :force: true 96 | 99 | ::: 100 | 101 | will get converted to: 102 | 103 | :::{code} html 104 | :force: true 105 | 108 | ::: 109 | 110 | ### `HTMLFormElement` 111 | 112 | `@call` will get converted to `@submit`. 113 | 114 | :::{code} html 115 | :force: true 116 |
117 | ... 118 |
119 | ::: 120 | 121 | will get converted to: 122 | 123 | :::{code} html 124 | :force: true 125 |
126 | ... 127 |
128 | ::: 129 | 130 | 131 | ## All event names 132 | 133 | - `@call` 134 | - `@click` 135 | - `@submit` 136 | - `@change` 137 | - `@input` 138 | - `@select` 139 | - `@focus` 140 | - `@blur` 141 | - `@hover` 142 | - `@reset` 143 | - `@keydown` 144 | - `@keypress` 145 | - `@keyup` 146 | - `@dblclick` 147 | - `@mouseenter` 148 | - `@mouseleave` 149 | - `@mousedown` 150 | - `@mousemove` 151 | - `@mouseout` 152 | - `@mouseover` 153 | - `@mouseup` 154 | - `@contextmenu` 155 | - `@drag` 156 | - `@dragend` 157 | - `@dragenter` 158 | - `@dragstart` 159 | - `@dragleave` 160 | - `@drop` 161 | - `@cut` 162 | - `@copy` 163 | - `@paste` 164 | -------------------------------------------------------------------------------- /test/events.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @vitest-environment jsdom 3 | */ 4 | 5 | import { describe, test, expect } from "vitest"; 6 | import { bindEvents, tokenizeEvents } from "../src/events.js"; 7 | 8 | // require('jsdom-global')(); 9 | // require('mutationobserver-shim'); 10 | 11 | // Object.defineProperty(global, 'MutationObserver', { 12 | // value: function() { 13 | // this.observe = function() {} 14 | // }, 15 | // writable:true 16 | // }); 17 | 18 | describe("tokenizeEvents", () => { 19 | test("@click exists", () => { 20 | document.body.innerHTML = `x`; 21 | tokenizeEvents(document.body); 22 | const el = document.querySelector("#myId"); 23 | expect(el.hasAttribute("dl--on-click")).toBe(true); 24 | }); 25 | 26 | test("@click get method", () => { 27 | document.body.innerHTML = `x`; 28 | tokenizeEvents(document.body); 29 | const el = document.querySelector("#myId"); 30 | expect(el.getAttribute("dl--on-click")).toBe("fn"); 31 | }); 32 | 33 | test("@click + @mouseover dl--elist", () => { 34 | document.body.innerHTML = `x`; 35 | tokenizeEvents(document.body); 36 | const el = document.querySelector("#myId"); 37 | expect(el.hasAttribute("dl--elist")).toBe(true); 38 | expect( 39 | el 40 | .getAttribute("dl--elist") 41 | .split(",") 42 | .filter((v) => v).length 43 | ).toBe(2); 44 | }); 45 | 46 | test("@call ahref to dl--on-click", () => { 47 | document.body.innerHTML = `x`; 48 | tokenizeEvents(document.body); 49 | const el = document.querySelector("#myId"); 50 | expect(el.hasAttribute("dl--on-click")).toBe(true); 51 | expect(el.getAttribute("href")).toEqual("javascript:void(0);"); 52 | }); 53 | 54 | test("@call on input to dl--on-input, dl--on-paste", () => { 55 | document.body.innerHTML = ``; 56 | tokenizeEvents(document.body); 57 | const el = document.querySelector("#myId"); 58 | expect(el.hasAttribute("dl--on-input")).toBe(true); 59 | expect(el.hasAttribute("dl--on-paste")).toBe(true); 60 | }); 61 | 62 | test("@call on select to dl--on-change", () => { 63 | document.body.innerHTML = ``; 64 | tokenizeEvents(document.body); 65 | const el = document.querySelector("#myId"); 66 | expect(el.hasAttribute("dl--on-change")).toBe(true); 67 | }); 68 | 69 | test("@call on form to dl--on-submit", () => { 70 | document.body.innerHTML = `
`; 71 | tokenizeEvents(document.body); 72 | const el = document.querySelector("#myId"); 73 | expect(el.hasAttribute("dl--on-submit")).toBe(true); 74 | }); 75 | 76 | test("@call anything to dl--on-click", () => { 77 | document.body.innerHTML = `
x
`; 78 | tokenizeEvents(document.body); 79 | const el = document.querySelector("#myId"); 80 | expect(el.hasAttribute("dl--on-click")).toBe(true); 81 | expect(el.hasAttribute("href")).not.toEqual("javascript:void(0);"); 82 | }); 83 | }); 84 | 85 | describe("bindEvents", () => { 86 | test.skip("@click fire method", (done) => { 87 | const context = { 88 | fn(e) { 89 | done(); 90 | console.log("wow"); 91 | }, 92 | }; 93 | document.body.innerHTML = `x`; 94 | tokenizeEvents(document.body); 95 | bindEvents(document.body, context); 96 | const el = document.querySelector("#myId"); 97 | el.click(); 98 | }); 99 | }); 100 | -------------------------------------------------------------------------------- /test/component-helpers.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @vitest-environment jsdom 3 | */ 4 | 5 | import { describe, test, expect } from "vitest"; 6 | 7 | import { 8 | filterMethods, 9 | filterInitialState, 10 | filterComputedState, 11 | storeConnector, 12 | domConnector, 13 | } from "../src/component-helpers.js"; 14 | 15 | const INITSTATE1 = { 16 | el: null, 17 | data: { 18 | a: "a", 19 | b: "b", 20 | c() { 21 | return null; 22 | }, 23 | d() {}, 24 | e() {}, 25 | }, 26 | method1() {}, 27 | methodx() {}, 28 | method3: () => null, 29 | }; 30 | 31 | test("filterMethods", () => { 32 | expect(filterMethods(INITSTATE1)).toBeInstanceOf(Object); 33 | expect(Object.keys(filterMethods(INITSTATE1)).length).toBe(3); 34 | }); 35 | 36 | test("filterInitialState", () => { 37 | expect(filterInitialState(INITSTATE1.data)).toBeInstanceOf(Object); 38 | expect(Object.keys(filterInitialState(INITSTATE1.data)).length).toBe(2); 39 | }); 40 | 41 | test("filterComputedState", () => { 42 | expect(filterComputedState(INITSTATE1.data)).toBeInstanceOf(Object); 43 | expect(Object.keys(filterComputedState(INITSTATE1.data)).length).toBe(3); 44 | }); 45 | 46 | test("storeConnector", () => { 47 | const store = storeConnector({ 48 | getState: () => {}, 49 | subscribe: (state) => (state) => {}, 50 | }); 51 | const storeInstance = store({}); 52 | 53 | expect(store).toBeInstanceOf(Function); 54 | expect(storeInstance).toBeInstanceOf(Function); 55 | }); 56 | 57 | describe("domConnector", () => { 58 | test("div", () => { 59 | const template = "
Hello World
"; 60 | const dom = domConnector(template); 61 | 62 | expect(dom).toBeInstanceOf(Object); 63 | expect(dom.html).toBe(template); 64 | expect(dom.render).toBeInstanceOf(Function); 65 | }); 66 | 67 | test("div with template literal", () => { 68 | const template = `
69 | Hello {this.world} 70 |
71 | `; 72 | const dom = domConnector(template); 73 | 74 | const target = document.createElement("div"); 75 | const state = { world: "World" }; 76 | 77 | // Check that the template literal gets converted as expected 78 | expect(dom).toBeInstanceOf(Object); 79 | expect(dom.html).toBe( 80 | `
81 | Hello \${typeof this.world != 'undefined' ? this.world : ''} 82 |
83 | ` 84 | ); 85 | expect(dom.render).toBeInstanceOf(Function); 86 | 87 | // Check that it renders as expected 88 | expect(target.innerHTML).toBe(""); 89 | expect(dom.render(target, state)).toBe(true); 90 | expect(target.innerHTML).toBe( 91 | `
92 | Hello World 93 |
94 | ` 95 | ); 96 | }); 97 | 98 | test("div with template literal and style", () => { 99 | const template = `
100 | 105 | Hello {this.world} 106 |
107 | `; 108 | const dom = domConnector(template); 109 | 110 | const target = document.createElement("div"); 111 | const state = { world: "World" }; 112 | 113 | // Check that the template literal gets converted as expected 114 | expect(dom).toBeInstanceOf(Object); 115 | expect(dom.html).toBe( 116 | `
117 | 122 | Hello \${typeof this.world != 'undefined' ? this.world : ''} 123 |
124 | ` 125 | ); 126 | expect(dom.render).toBeInstanceOf(Function); 127 | 128 | // Check that it renders as expected 129 | expect(target.innerHTML).toBe(""); 130 | expect(dom.render(target, state)).toBe(true); 131 | expect(target.innerHTML).toBe( 132 | `
133 | 138 | Hello World 139 |
140 | ` 141 | ); 142 | }); 143 | }); 144 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.19.0 4 | - Update `emerj` merging algorithm to the latest version. 5 | 6 | ## 0.18.0 7 | - Don't show `undefined` when rendering the template. 8 | 9 | ## 0.17.0 10 | - Make component methods available from template. 11 | 12 | ## 0.16.2 13 | - Remove `visibility: hide` on first component render 14 | 15 | ## 0.16.1 16 | - Include `link` tags with a `rel` of `stylesheet` for `Shadow DOM` scoped css 17 | 18 | ## 0.16.0 19 | - Scoped CSS when attached to a `Shadow DOM` 20 | - Make `shadowDOM` default `true` (again) now that scoped CSS is working 21 | - Add `debug` setting to show `dlite` error messages on the page while developing 22 | - Include attributes when converting from a regular DOM element to a Web Component 23 | - Return components from `Dlite` initializer for use in JavaScript 24 | 25 | ### Breaking changes 26 | 27 | - Remove `refId` setting because attributes (i.e. `id`) are copied to custom elements and components are now returned from the `Dlite` initializer 28 | 29 | ## 0.15.0 30 | - Revert `shadowDOM` default back to `false` since that will be less disruptive and a better first experience 31 | 32 | ## 0.14.0 33 | - Prevent flickering when rendering for all component types (`tagName`, `el`, and in-place elements) 34 | - Attaching components to a `Shadow DOM` is now the default; this will be useful for encapsulation and scoped styles in the future 35 | - Set styles on custom element when on an initial `el` 36 | - Add demo for how to use shared component template 37 | - Switch to `vitest` from `jest` for quicker unit tests 38 | 39 | ## 0.13.1 40 | - Remove unnecessary files 41 | 42 | ## 0.13.0 43 | - Rename `litedom` to `dlite` 44 | - Update readme 45 | - Reformat code to reduce package size 46 | 47 | ## 0.12.1 48 | - Add try/catch to fix wrong type 49 | 50 | ## 0.12.0 51 | - Fixed two way data binding to set initial value 52 | 53 | ## 0.11.2 54 | - Fixed race condition in component-helpers.storeConnector. 55 | 56 | ## 0.11.1 57 | - fixed removal of visibility/display if it exist in el to prevent flickering of placeholders 58 | 59 | ## 0.11.0 60 | - Rename `reliftHTML` to `Litedom` 61 | - Directives use color, instead of r-*. :for, :if, :else, :class, :key 62 | - Remove r-* directives 63 | - Added :class 64 | `
` 65 | `
` 66 | - Changed options: 67 | - If template is provided, it will take precedence over el.innerHTML 68 | - If tagName provided, it will make it automatically Custom Element 69 | - If template is not provided, el.innerHTML will be used 70 | - Rename isShadow to shadowDOM 71 | - Added refId: when doing in place element, it will allow to id the element 72 | - Removed asTemplate 73 | - Add el#.data on the component to expose data 74 | `` 75 | `document.querySelector('#id').data;` 76 | - Open custom methods to be public in the element when doing document.querySelector('element-something'). 77 | To make a method private prefix it with _ underscore. 78 | - Added :key to uniquely identify an element for reordering when looping 79 | - Added :style to dynamically pass inline styling as an object for stylemap 80 | - Fixed other special characters in conditionals 81 | - Update docs 82 | 83 | ## 0.10.2 84 | - Fixed reported issue when using < + >, it changes it into html entities, < >, i.e. 'this.count > 5' should not be 'this.count > 5'. Fixed. 85 | 86 | ## 0.10.1 87 | - Fixed rollup bundling error that was re-declaring variable 88 | 89 | ## 0.10.0 90 | - reLiftHTML now accepts array of config, to instantiate multiple components at once. 91 | `reLiftHTML({...}) or reLiftHTML([{...}, {...}])` 92 | 93 | ## 0.0.9 94 | - Fixed bug that doesn't remove the element when doing inline 95 | 96 | ## 0.0.8 97 | - Fixed bug in $store. 98 | - Put $store in the context 99 | - Make sure $store return the copy of the object 100 | 101 | ## 0.0.7 102 | - Anonymous Component will be named 'relift-ce-$random-id' 103 | - Added el.style.display=block so hidden can be prepared before compiled. Adding style="display:none" will hide the template so the content is not shown. This option, will then show the element. 104 | 105 | ## 0.0.6 106 | - Changed the instances to become Web Component (shadow dom or custom element). 107 | This will allow composability, sharability and use of component in other 108 | components. 109 | - Two way data binding (via one way data flow) 110 | - __$bindInput() function for two way data binding 111 | - Added `utils.get` and `utils.set` to retrive data from dot notation 112 | - Template lit can be written without dollar sign, {...} => ${...}. Specially when being written in JS to prevent interpolation by JS 113 | 114 | ## 0.0.5 115 | - Initial 116 | -------------------------------------------------------------------------------- /src/events.js: -------------------------------------------------------------------------------- 1 | import { isFn, get } from "./utils.js"; 2 | 3 | /** 4 | * Holds all the browser's event list, ie: click, mouseover, keyup 5 | * @type {array} 6 | */ 7 | const EVENTS_LIST = []; 8 | 9 | for (const key in document) { 10 | const isEvent = document[key] === null || isFn(document[key]); 11 | 12 | if (key.startsWith("on") && isEvent) { 13 | EVENTS_LIST.push(key.substring(2)); 14 | } 15 | } 16 | 17 | /** 18 | * @type {string} attribute to hold all events name 19 | */ 20 | const ATTR_EVENTS_LIST = "dl--elist"; 21 | 22 | /** 23 | * Make an event name 24 | * @param {string} e the event name 25 | * @returns {string} 26 | */ 27 | const generateEventName = (e) => { 28 | return `dl--on-${e}`; 29 | }; 30 | 31 | /** 32 | * Tokenize all the events, change @* to dl--on-* 33 | * @param {HTMLElement} selector 34 | * @returns {void} 35 | */ 36 | export function tokenizeEvents(selector) { 37 | /** 38 | * '@call' 39 | * Wildcard events, base of the type of the element it will assign the right event name 40 | * ie: on input element, '@call' will turn into 'dl--on-input' and 'dl--on-paste' 41 | * on ahref, '@call' will turn into 'dl--on-click' 42 | * 43 | * '@bind' 44 | * For two way data binding in input elements 45 | * 46 | */ 47 | for (const el of selector.querySelectorAll("[\\@call], [\\@bind]")) { 48 | let method = el.getAttribute("@call"); 49 | el.removeAttribute("@call"); 50 | 51 | const isBind = el.hasAttribute("@bind"); 52 | 53 | if (isBind) { 54 | el.setAttribute("dl--bind", el.getAttribute("@bind")); 55 | el.removeAttribute("@bind"); 56 | method = "__$bindInput"; 57 | } 58 | 59 | let events = ["click"]; 60 | 61 | if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) { 62 | events = ["input", "paste"]; 63 | } else if (el instanceof HTMLSelectElement) { 64 | events = ["change"]; 65 | } else if (el instanceof HTMLFormElement) { 66 | events = ["submit"]; 67 | } else if (el instanceof HTMLAnchorElement) { 68 | el.setAttribute("href", "javascript:void(0);"); 69 | } 70 | 71 | let eventsList = (el.getAttribute(ATTR_EVENTS_LIST) || "") 72 | .split(",") 73 | .filter((v) => v); 74 | 75 | eventsList = eventsList.concat(events); 76 | el.setAttribute(ATTR_EVENTS_LIST, eventsList.join(",")); 77 | 78 | for (const e of events) { 79 | el.setAttribute(generateEventName(e), method); 80 | } 81 | } 82 | 83 | // Regular event list 84 | for (const e of EVENTS_LIST) { 85 | for (const el of selector.querySelectorAll(`[\\@${e}]`)) { 86 | const eventsList = (el.getAttribute(ATTR_EVENTS_LIST) || "") 87 | .split(",") 88 | .filter((v) => v); 89 | 90 | eventsList.push(e); 91 | el.setAttribute(ATTR_EVENTS_LIST, eventsList.join(",")); 92 | el.setAttribute(generateEventName(e), el.getAttribute(`@${e}`)); 93 | el.removeAttribute(`@${e}`); 94 | 95 | if (el instanceof HTMLAnchorElement) 96 | el.setAttribute("href", "javascript:void(0);"); 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * Bind events to all elements with dl--on-* 103 | * @param {Element|ShadowRoot} selector The element to look 104 | * @param {Object} context object of function to bind the events to 105 | * @returns {MutationObserver} 106 | */ 107 | export function bindEvents(selector, context) { 108 | function mapEvents(selector) { 109 | Array.from(selector.querySelectorAll(`[${ATTR_EVENTS_LIST}]`)).map((el) => { 110 | (el.getAttribute(ATTR_EVENTS_LIST) || "") 111 | .split(",") 112 | .filter((v) => v) 113 | .map((e) => { 114 | el[`on${e}`] = (event) => { 115 | event.preventDefault(); 116 | 117 | const method = el.getAttribute(generateEventName(e)); 118 | context[method].call(context, event); 119 | }; 120 | }); 121 | }); 122 | } 123 | 124 | // set initial values 125 | Array.from(selector.querySelectorAll(`[dl--bind]`)).map((el) => { 126 | const value = get(context.data, el.getAttribute("dl--bind")); 127 | 128 | try { 129 | if (el.tagName === "INPUT" && ["radio", "checkbox"].includes(el.type)) { 130 | if (value.includes(el.value)) { 131 | el.checked = true; 132 | } 133 | } else { 134 | el.value = value; 135 | } 136 | } catch (e) { 137 | // 138 | } 139 | }); 140 | 141 | const mutationsObserver = new MutationObserver((mutations) => { 142 | [...mutations] 143 | .filter((m) => m.addedNodes.length > 0) 144 | .map((m2) => m2.target) 145 | .map((t) => mapEvents(t)); 146 | }); 147 | 148 | mutationsObserver.observe(selector, { 149 | attributes: true, 150 | childList: true, 151 | subtree: true, 152 | }); 153 | 154 | mapEvents(selector); 155 | 156 | return mutationsObserver; 157 | } 158 | -------------------------------------------------------------------------------- /docs/source/components/directives.md: -------------------------------------------------------------------------------- 1 | # Directives 2 | 3 | Directives are HTML element attributes that start with a `:`. They are shortcuts for logic in the template. 4 | 5 | ```html 6 | Show me 7 | ``` 8 | 9 | will get converted to the following JavaScript: 10 | 11 | ```js 12 | ${this.index === 5 ? `Show me` : ``} 13 | ``` 14 | 15 | ```{note} 16 | Directive values can be any JavaScript conditional. `${...}` or `{...}` are not necessary inside of the directive -- it should be written as a normal string. 17 | ``` 18 | 19 | ## if 20 | 21 | `:if` and `:else` can be used to conditionally add or remove the element. 22 | 23 | The element with the `:else` must immediately follow the element with the `:if` directive otherwise it will not be recognized. 24 | 25 | ```html 26 | 37 | 38 | 44 | ``` 45 | 46 | ## for 47 | 48 | `:for` iterates over a list of items. 49 | 50 | ```{note} 51 | Under the hood `for` converts the array into a `map`. 52 | ``` 53 | 54 | The `:for` directive requires a special syntax in the form of `item in items`, where `items` is the source data and `item` is the current object in the iteration. 55 | 56 | You can also use `item, index in items`, where `index` tracks the loop index. 57 | 58 | ```{note} 59 | It is recommended to provide a `:key` directive or `id` attribute with `:for` whenever possible. 60 | ``` 61 | 62 | ```html 63 | 80 | 81 | 86 | ``` 87 | 88 | ```html 89 | 119 | 120 | 134 | ``` 135 | 136 | ## class 137 | 138 | `:class` conditionally toggles class names. 139 | 140 | ```html 141 | 150 | 151 | 159 | 160 |
161 | - will have .red if count is 7 162 | - will have .blue if count is 10 163 |
164 | ``` 165 | 166 | ## style 167 | 168 | `:style` sets element styles. 169 | 170 | ```html 171 | 184 | 185 |
186 | 187 | // will get converted to 188 | 189 |
190 | ``` 191 | 192 | ## text 193 | 194 | `:text` sets the text of the element. 195 | 196 | ```html 197 | 206 | 207 |
208 | 209 | // will get converted to 210 | 211 |
this is some text
212 | ``` 213 | -------------------------------------------------------------------------------- /docs/source/components/index.md: -------------------------------------------------------------------------------- 1 | # Initialize 2 | 3 | `dlite` turns HTML into composable, fully compliant `Web Component`s. The easiest way to initialize `dlite` is via an `ES Module` import. 4 | 5 | ```{warning} 6 | Make sure to specify `module` for the `script` element's `type`. 7 | ``` 8 | 9 | ```html 10 | 16 | ``` 17 | 18 | or download [dlite.es.js](https://raw.githubusercontent.com/adamghill/dlite/main/dist/dlite.es.js) and serve it. 19 | 20 | ```html 21 | 22 | ``` 23 | 24 | ## Configuration 25 | 26 | `Dlite` initialization accepts one argument which is either a [configuration object](configuration.md) or an array of [configuration objects](configuration.md). 27 | 28 | ### Object 29 | 30 | ```js 31 | Dlite({ 32 | tagName: 'component-x', 33 | template: '...' 34 | }); 35 | ``` 36 | 37 | ### Array of objects 38 | 39 | Initializes multiple components at once. 40 | 41 | ```js 42 | const componentOne = { 43 | tagName: 'component-one', 44 | template: '...', 45 | }; 46 | const componentTwo = { 47 | tagName: 'component-two', 48 | template: '...', 49 | }; 50 | 51 | Dlite([componentOne, componentTwo]); 52 | ``` 53 | 54 | #### Shared configuration 55 | 56 | When using an array of configuration objects, a shared configuration object can also be passed-in. 57 | 58 | ```js 59 | const componentOne = { 60 | tagName: 'component-one', 61 | template: '...', 62 | }; 63 | const componentTwo = { 64 | tagName: 'component-two', 65 | template: '...', 66 | }; 67 | 68 | Dlite([ 69 | componentOne, 70 | componentTwo, 71 | ], { 72 | created() { 73 | console.log(`This will log when each component gets created`); 74 | }, 75 | }); 76 | ``` 77 | 78 | ## Custom element 79 | 80 | [`Custom Element`s](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) are created by a custom element tag which can be reused in multiple places. It also allows you to place your component in an external JavaScript file. 81 | 82 | Requires a `tagName` and `template` to be set in the configuration. 83 | 84 | ```html 85 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | ``` 103 | 104 | ## In-place element 105 | 106 | Use an existing DOM element and make it reactive. 107 | 108 | Requires `el`, a query selector, in the configuration to find an element in the HTML. 109 | 110 | ```html 111 | 121 | 122 | 126 | ``` 127 | 128 | ````{warning} 129 | Setting a `template` key with the `el` will always override the `innerHTML` of the selected element. 130 | 131 | ```html 132 | 143 | 144 | 148 | ``` 149 | ```` 150 | 151 | ### Prevent flickering 152 | 153 | `template` tags are not rendered by browsers normally, so they are useful to prevent the flickering effect when an element is initially rendered before it is merged with the data. 154 | 155 | If another element tag other than `template` must be used, either add a [`hidden` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/hidden) to the element or [`style="visibility: hidden"`](https://developer.mozilla.org/en-US/docs/Web/CSS/visibility). 156 | 157 | The `hidden` attribute will make the component invisible and will not include it in the layout. It is basically the same as doing a `display: none` style. 158 | 159 | ```html 160 | 161 | 164 | ``` 165 | 166 | The `visibility: hidden` style will make the component invisible, but will still affect the layout -- this is beneficial to prevent elements from shifting as much. This is what `dlite` does by default when rendering non in-place elements. 167 | 168 | ```html 169 | 170 | 173 | ``` 174 | -------------------------------------------------------------------------------- /src/directives.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @type {object} 3 | */ 4 | // The order is important, especially for :for and :if 5 | const DIRECTIVES_LIST = { 6 | $key: _key, 7 | $class: _class, 8 | $style: _style, 9 | $for: _for, 10 | $if: _if, 11 | $text: _text, 12 | }; 13 | 14 | const md = (dir) => { 15 | return `\:${dir}`; 16 | }; 17 | const has = (el, dir) => { 18 | return el.hasAttribute(md(dir)); 19 | }; 20 | const get = (el, dir) => { 21 | return el.getAttribute(md(dir)); 22 | }; 23 | const remove = (el, dir) => { 24 | return el.removeAttribute(md(dir)); 25 | }; 26 | // const query = (el, dir) => el.querySelector(`[\\${md(dir)}]`); 27 | const queryAll = (el, dir) => { 28 | return el.querySelectorAll(`[\\${md(dir)}]`); 29 | }; 30 | const beforeText = (el, text) => { 31 | return el.insertAdjacentText("beforebegin", text); 32 | }; 33 | const afterText = (el, text) => { 34 | return el.insertAdjacentText("afterend", text); 35 | }; 36 | const wrapAround = (el, before, after) => { 37 | beforeText(el, before); 38 | afterText(el, after); 39 | }; 40 | 41 | /** 42 | * Parse directives 43 | * @param {HTMLElement} el The element 44 | * @param {object} customDirectives Custom directives to expand functionalities 45 | * @return {HTMLElement} 46 | */ 47 | export function parseDirectives(el, customDirectives = {}) { 48 | const directives = { ...customDirectives, ...DIRECTIVES_LIST }; 49 | 50 | for (const $dir in directives) { 51 | const directive = $dir.replace("$", ""); 52 | 53 | for (const el2 of queryAll(el, directive)) { 54 | if (has(el2, directive)) { 55 | const value = get(el2, directive); 56 | 57 | directives[$dir](el2, value, directive); 58 | } 59 | } 60 | } 61 | 62 | return el; 63 | } 64 | 65 | /** 66 | * :if directive 67 | * @param {HTMLElement} el 68 | * @param {string} value 69 | * @param {string} directive 70 | * @returns {void} 71 | */ 72 | function _if(el, value, directive) { 73 | remove(el, directive); 74 | beforeText(el, `\${${value} ? `); 75 | 76 | const rElse = el.nextElementSibling; 77 | 78 | if (rElse && has(rElse, "else")) { 79 | wrapAround(el, `\``, `\``); 80 | remove(rElse, "else"); 81 | wrapAround(rElse, `:\``, `\`}`); 82 | } else { 83 | wrapAround(el, `\``, `\`:\`\`}`); 84 | } 85 | } 86 | 87 | /** 88 | * :for directive 89 | * @todo: add for else => :else for for, it's an if condition that test the length, 90 | * @param {HTMLElement} el 91 | * @param {string} value 92 | * @param {string} directive 93 | * @returns {void} 94 | */ 95 | function _for(el, value, directive) { 96 | const groups = /(.*)\s+(in)\s+(.*)$/.exec(value); 97 | 98 | if (groups.length === 4) { 99 | const sel = groups[1].replace("(", "").replace(")", ""); 100 | const query = groups[3]; 101 | 102 | wrapAround( 103 | el, 104 | `\${${query}.map(function(${sel}) { return \``, 105 | `\`}.bind(this)).join('')}` 106 | ); 107 | 108 | remove(el, directive); 109 | } 110 | } 111 | 112 | /** 113 | * :class directive 114 | *
115 | *
116 | * @param {HTMLElement} el 117 | * @param {string} value 118 | * @param {string} directive 119 | * @returns {void} 120 | */ 121 | function _class(el, value, directive) { 122 | const klass = value 123 | .split(";") 124 | .map((v) => v.split(":", 2).map((e) => e.trim())) 125 | .map((v) => `\${${v[1]} ? '${v[0]}': ''}`) 126 | .join(" "); 127 | const classList = (el.getAttribute("class") || "") + ` ${klass}`; 128 | el.setAttribute("class", classList); 129 | 130 | remove(el, directive); 131 | } 132 | 133 | /** 134 | * :key directive 135 | *
will be change to
136 | * to make sure iteration is rendered properly 137 | * @param {HTMLElement} el 138 | * @param {string} value 139 | * @param {string} directive 140 | * @returns {void} 141 | */ 142 | function _key(el, value, directive) { 143 | el.setAttribute("ref-key", value); 144 | 145 | remove(el, directive); 146 | } 147 | 148 | /** 149 | * :style directive 150 | *
151 | * @param {HTMLElement} el 152 | * @param {string} value 153 | * @param {string} directive 154 | * @returns {void} 155 | */ 156 | function _style(el, value, directive) { 157 | const oStyle = el.getAttribute("style") || ""; 158 | const style = `\${function() { return this.__$styleMap(${value});}.call(this)}`; 159 | 160 | el.setAttribute("style", (oStyle ? oStyle + "; " : "") + style); 161 | remove(el, directive); 162 | } 163 | 164 | /** 165 | * :text directive 166 | *
167 | *
{dataState value}
168 | * @param {HTMLElement} el 169 | * @param {string} value 170 | * @param {string} directive 171 | * @returns {void} 172 | */ 173 | function _text(el, value, directive) { 174 | el.textContent = value; 175 | 176 | remove(el, directive); 177 | } 178 | -------------------------------------------------------------------------------- /src/component-helpers.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Helpers for components 3 | */ 4 | import emerj from "./emerj.js"; 5 | import { tokenizeEvents } from "./events.js"; 6 | import { parseDirectives } from "./directives.js"; 7 | 8 | import { 9 | computeState, 10 | isObjKeyFn, 11 | parseLit, 12 | htmlToDom, 13 | set, 14 | get, 15 | toStrLit, 16 | decodeHTMLStringForDirective, 17 | } from "./utils.js"; 18 | 19 | /** 20 | * @type {array} 21 | */ 22 | const RESERVED_KEYS = [ 23 | "data", 24 | "el", 25 | "shadowDOM", 26 | "template", 27 | "created", 28 | "updated", 29 | "removed", 30 | "$store", 31 | "prop", 32 | "tagName", 33 | ]; 34 | 35 | /** 36 | * Filter all methods from the initial object 37 | * @param {object} obj 38 | * @returns {object} 39 | */ 40 | export const filterMethods = (obj) => { 41 | return Object.keys(obj) 42 | .filter((k) => !RESERVED_KEYS.includes(k)) 43 | .filter((k) => !k.startsWith("$")) 44 | .filter((k) => isObjKeyFn(obj, k)) 45 | .reduce((pV, cK) => ({ ...pV, [cK]: obj[cK] }), {}); 46 | }; 47 | 48 | /** 49 | * Filter initial state 50 | * @param {Object} obj 51 | * @return {object} initial state 52 | */ 53 | export const filterInitialState = (obj) => { 54 | return Object.keys(obj) 55 | .filter((k) => !isObjKeyFn(obj, k)) 56 | .reduce((pV, cK) => ({ ...pV, [cK]: obj[cK] }), {}); 57 | }; 58 | 59 | /** 60 | * Filter computed state 61 | * @param {object} obj 62 | * @returns {function[]} 63 | */ 64 | export const filterComputedState = (obj) => { 65 | return Object.keys(obj) 66 | .filter((k) => isObjKeyFn(obj, k)) 67 | .map((k) => computeState(k, obj[k])); 68 | }; 69 | 70 | /** 71 | * Filter all global objects with `$` prefix. 72 | * @param {object} obj 73 | * @returns {object} 74 | */ 75 | export const filterGlobal$Object = (obj) => { 76 | return Object.keys(obj) 77 | .filter((k) => k.startsWith("$")) 78 | .filter((k) => !RESERVED_KEYS.includes(k)) 79 | .reduce((pV, cK) => ({ ...pV, [cK]: obj[cK] }), {}); 80 | }; 81 | 82 | /** 83 | * @typedef {Object} StateManagementType - The state management definition 84 | * @property {function} getState - a function that returns the current state 85 | * @property {function} subscribe - function to subscribe that returns a function 86 | * 87 | * Connect to an external store to share state 88 | * @param {StateManagementType} store store Instance, must have getState() and subscribe() 89 | * @return {function} to create an instance of the store to react on the element 90 | */ 91 | export const storeConnector = (store) => (data) => { 92 | data.$store = store.getState(); 93 | 94 | return store.subscribe((x) => (data.$store = { ...store.getState() })); 95 | }; 96 | 97 | /** 98 | * Bind public methods to the element context 99 | * @param {object} context 100 | * @param {object} methods 101 | * @param {object} contextState 102 | * @returns {void} 103 | */ 104 | export const bindPublicMethodsToContext = (context, methods, contextState) => { 105 | Object.keys(methods) 106 | .filter((k) => !k.startsWith("_")) 107 | .map((k) => (context[k] = methods[k].bind(contextState))); 108 | }; 109 | 110 | /** 111 | * Receiving a template it 112 | * @param {string} template 113 | * @returns {{html: string, render: function }} 114 | */ 115 | export const domConnector = (template) => { 116 | const { head, body } = htmlToDom(template); 117 | 118 | parseDirectives(body); 119 | tokenizeEvents(body); 120 | 121 | let html = toStrLit(decodeHTMLStringForDirective(body.innerHTML)); 122 | 123 | // Add style + link elements 124 | head.querySelectorAll('style, link[rel="stylesheet"]').forEach((el) => { 125 | html += el.outerHTML; 126 | }); 127 | 128 | // TODO: Add script elements; however, they do not get executed 129 | 130 | const lit = parseLit(html); 131 | 132 | return { 133 | html, 134 | render: (target, state) => { 135 | const { head: newHead, body: newBody } = htmlToDom(lit(state)); 136 | 137 | // Add style + link elements 138 | newBody.append(...newHead.querySelectorAll("style, link")); 139 | 140 | // TODO: Add script elements; however, they do not get executed 141 | 142 | return !target.isEqualNode(newBody) ? emerj(target, newBody) : false; 143 | }, 144 | }; 145 | }; 146 | 147 | /** 148 | * For two-way data binding 149 | * This is an internal function to be used 150 | * @param {Event} e 151 | * @returns {void} 152 | */ 153 | export function __$bindInput(e) { 154 | /** @type {HTMLInputElement|any} el */ 155 | const el = e.target; 156 | const key = el.getAttribute("ld--bind"); 157 | 158 | if (el.type === "checkbox") { 159 | const obj = get(this.data, key) || []; 160 | 161 | set( 162 | this.data, 163 | key, 164 | el.checked ? obj.concat(el.value) : obj.filter((v) => v != el.value) 165 | ); 166 | } else if (el.options && el.multiple) { 167 | set( 168 | this.data, 169 | key, 170 | [].reduce.call(el, (v, o) => (o.selected ? v.concat(o.value) : v), []) 171 | ); 172 | } else { 173 | set(this.data, key, el.value); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /test/directives.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @vitest-environment jsdom 3 | */ 4 | 5 | import { test, expect } from "vitest"; 6 | import { parseDirectives } from "../src/directives.js"; 7 | import { decodeHTMLStringForDirective } from "../src/utils.js"; 8 | 9 | import { JSDOM } from "jsdom"; 10 | 11 | function createHTML(text) { 12 | return new JSDOM(text).window.document.body; 13 | } 14 | 15 | test("r-if", () => { 16 | const root = createHTML( 17 | `
Hello
` 18 | ); 19 | parseDirectives(root); 20 | const result = 21 | '
${this.a === b ? `Hello`:``}
'; 22 | expect(root.innerHTML).toBe(result); 23 | }); 24 | 25 | test("greater & lesser than", () => { 26 | const root = createHTML( 27 | `
Hello
` 28 | ); 29 | const rootB = createHTML( 30 | `
Hello
` 31 | ); 32 | const rootC = createHTML( 33 | `
Hello
` 34 | ); 35 | const rootD = createHTML( 36 | `
Hello
` 37 | ); 38 | parseDirectives(root); 39 | parseDirectives(rootB); 40 | parseDirectives(rootC); 41 | parseDirectives(rootD); 42 | 43 | expect(root.innerHTML).toBe( 44 | '
${this.a > b ? `Hello`:``}
' 45 | ); 46 | expect(rootB.innerHTML).toBe( 47 | '
${this.a < b ? `Hello`:``}
' 48 | ); 49 | expect(rootC.innerHTML).toBe( 50 | '
${this.a >= b ? `Hello`:``}
' 51 | ); 52 | expect(rootD.innerHTML).toBe( 53 | '
${this.a <= b ? `Hello`:``}
' 54 | ); 55 | }); 56 | 57 | test("greater & lesser than when decoded", () => { 58 | const root = createHTML( 59 | `
Hello
` 60 | ); 61 | const rootB = createHTML( 62 | `
Hello
` 63 | ); 64 | const rootC = createHTML( 65 | `
Hello
` 66 | ); 67 | const rootD = createHTML( 68 | `
Hello
` 69 | ); 70 | parseDirectives(root); 71 | parseDirectives(rootB); 72 | parseDirectives(rootC); 73 | parseDirectives(rootD); 74 | 75 | expect(decodeHTMLStringForDirective(root.innerHTML)).toBe( 76 | '
${this.a > b ? `Hello`:``}
' 77 | ); 78 | expect(decodeHTMLStringForDirective(rootB.innerHTML)).toBe( 79 | '
${this.a < b ? `Hello`:``}
' 80 | ); 81 | expect(decodeHTMLStringForDirective(rootC.innerHTML)).toBe( 82 | '
${this.a >= b ? `Hello`:``}
' 83 | ); 84 | expect(decodeHTMLStringForDirective(rootD.innerHTML)).toBe( 85 | '
${this.a <= b ? `Hello`:``}
' 86 | ); 87 | }); 88 | 89 | test("r-if else", () => { 90 | const root = createHTML( 91 | `
HelloWorld
` 92 | ); 93 | parseDirectives(root); 94 | const result = 95 | '
${this.a === b ? `Hello`:`World`}
'; 96 | expect(root.innerHTML).toBe(result); 97 | }); 98 | 99 | test("r-if no immidiate else", () => { 100 | const root = createHTML( 101 | `
HelloSomething ElseWorld
` 102 | ); 103 | parseDirectives(root); 104 | const result = 105 | '
${this.a === b ? `Hello`:``}Something ElseWorld
'; 106 | expect(root.innerHTML).toBe(result); 107 | }); 108 | 109 | test("for", () => { 110 | const root = createHTML( 111 | `
  • {item}
` 112 | ); 113 | parseDirectives(root); 114 | const result = 115 | "
    ${this.items.map(function(item) { return `
  • {item}
  • `}.bind(this)).join('')}
"; 116 | expect(root.innerHTML).toBe(result); 117 | }); 118 | 119 | test("inner for", () => { 120 | const root = createHTML( 121 | `
    • {item2}
` 122 | ); 123 | parseDirectives(root); 124 | const result = 125 | "
    ${this.items.map(function(item) { return `
    • ${item.map(function(item2) { return `
    • {item2}
    • `}.bind(this)).join('')}
  • `}.bind(this)).join('')}
"; 126 | expect(root.innerHTML).toBe(result); 127 | }); 128 | 129 | test("for with if", () => { 130 | const root = createHTML( 131 | `
  • {item} Yes
` 132 | ); 133 | parseDirectives(root); 134 | const result = 135 | "
    ${this.items.map(function(item) { return `
  • {item} ${x === y ? `Yes`:``}
  • `}.bind(this)).join('')}
"; 136 | expect(root.innerHTML).toBe(result); 137 | }); 138 | 139 | test("textContent with text", () => { 140 | const root = createHTML( 141 | `

` 142 | ); 143 | parseDirectives(root); 144 | const result = '

{this.a}

'; 145 | expect(root.innerHTML).toBe(result); 146 | }); 147 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import Component from "./component.js"; 2 | import { getAttrs } from "./utils.js"; 3 | 4 | /** 5 | * Fetches the url passed to it and returns the response based on content type. 6 | * 7 | * Raises an error for >=400 status codes. 8 | * 9 | * @param {string} url 10 | * @returns {Promise} 11 | */ 12 | export async function fetcher(url) { 13 | return await fetch(url) 14 | .then((res) => { 15 | if (res.status >= 400) { 16 | throw new Error(`${res.url} (${res.status})`); 17 | } 18 | 19 | return res; 20 | }) 21 | .then((res) => { 22 | if ((res.headers.get("content-type") || "").includes("json")) { 23 | return res.json(); 24 | } 25 | 26 | return res.text(); 27 | }); 28 | } 29 | 30 | /** 31 | * Creates a new element 32 | * @param {string} tagName The element's tag name 33 | * @param {object} attrs Attributes that should be set on the element 34 | * @param {string} html HTML to set on the el's innerHTML 35 | * @returns {HTMLElement} The custom element 36 | */ 37 | function createElement(tagName, attrs = {}, html = "") { 38 | const el = document.createElement(tagName); 39 | el.innerHTML = html; 40 | 41 | for (const [attrName, attrValue] of Object.entries(attrs)) { 42 | el.setAttribute(attrName, attrValue); 43 | } 44 | 45 | return el; 46 | } 47 | 48 | /** 49 | * dlite default function initializer 50 | * @param {object} options The configuration 51 | * @returns {HTMLElement} The custom element 52 | */ 53 | function Dlite(options) { 54 | const configuration = { 55 | /** 56 | * el 57 | * @type {HTMLElement | string} 58 | * Element or query selector where the in-place element will be rendered. 59 | * If $template is `null`, it will use the el#innerHTML as template. 60 | */ 61 | el: null, 62 | 63 | /** 64 | * template 65 | * @type {string} 66 | * Template string used to create the component. 67 | * If it is set with `el`, `el` will be the target, but `template` will override its `innerHTML`. 68 | */ 69 | template: null, 70 | 71 | /** 72 | * tagName 73 | * @type {string} 74 | * The tag name for the custom element. Not needed for in-place element. 75 | */ 76 | tagName: null, 77 | 78 | /** 79 | * shadowDOM 80 | * @type {boolean} 81 | * Attach the `Custom Element` to a `Shadow DOM`. Defaults to `true`. 82 | */ 83 | shadowDOM: true, 84 | 85 | /** 86 | * debug 87 | * @type {boolean} 88 | * Whether in debug mode or not. 89 | */ 90 | debug: false, 91 | 92 | ...options, 93 | }; 94 | 95 | const shouldCreateTag = !configuration.tagName; 96 | configuration.tagName = 97 | configuration.tagName || 98 | `dlite-${Math.random().toString(36).substring(2, 9).toLowerCase()}`; 99 | 100 | if (configuration.el) { 101 | if (typeof configuration.el === "string") { 102 | const el = document.querySelector(configuration.el); 103 | 104 | if (!el) { 105 | throw new Error(`'${configuration.el}' could not be found.`); 106 | } 107 | 108 | configuration.el = el; 109 | } 110 | 111 | // Set the `template` as the el's innerHTML if needed 112 | if (!configuration.template) { 113 | configuration.template = configuration.el.innerHTML; 114 | } 115 | 116 | // Clear out the innerHTML of the element 117 | configuration.el.innerHTML = ""; 118 | 119 | if (shouldCreateTag) { 120 | // Set attributes from the original element on the new element 121 | // Useful so that `id`, `style`, `class`, etc gets set on new `Custom Element` 122 | const createdEl = createElement( 123 | configuration.tagName, 124 | getAttrs(configuration.el) 125 | ); 126 | 127 | // Set attributes from the original element on the new element 128 | // Useful so that `id`, `style`, `class` gets set on new `Custom Element` 129 | for (const [attrName, attrValue] of Object.entries( 130 | getAttrs(configuration.el) 131 | )) { 132 | createdEl.setAttribute(attrName, attrValue); 133 | } 134 | 135 | configuration.el.parentNode.replaceChild(createdEl, configuration.el); 136 | } 137 | } else if (!configuration.el && shouldCreateTag) { 138 | throw new Error(`Missing either 'el' or 'tagName' setting.`); 139 | } 140 | 141 | if (!configuration.template) { 142 | throw new Error(`Missing 'template' setting.`); 143 | } 144 | 145 | return Component(configuration); 146 | } 147 | 148 | /** 149 | * 150 | * @param {object|array} configuration `dlite` configuration or array of configurations 151 | * @param {object} sharedConfiguration shared global configurations fo reach component; only used with array of configurations 152 | * @returns {Array} An array of custom elements for the configured components 153 | */ 154 | export default (configuration, sharedConfiguration = {}) => { 155 | if (!Array.isArray(configuration)) { 156 | configuration = [configuration]; 157 | } 158 | 159 | return configuration.map( 160 | (/** @type {object} */ config, /** @type {number} */ index) => { 161 | try { 162 | return Dlite({ ...sharedConfiguration, ...config }); 163 | } catch (e) { 164 | if (sharedConfiguration.debug || config.debug) { 165 | // Add error message to page 166 | const errorDiv = createElement( 167 | "div", 168 | { 169 | style: "background-color: red; color: white; padding: 10px;", 170 | }, 171 | ` 172 |

#${index + 1}

173 | ${e} 174 |
175 | Stacktrace 176 | ${e.stack.replaceAll("\n", "
")} 177 |
` 178 | ); 179 | document.body.prepend(errorDiv); 180 | 181 | console.error(e); 182 | } else { 183 | throw e; 184 | } 185 | } 186 | } 187 | ); 188 | }; 189 | -------------------------------------------------------------------------------- /src/emerj.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Emerj is a tiny JavaScript library to render live HTML/DOM updates 3 | * efficiently and non-destructively, by merging an updated DOM with 4 | * the live DOM, and only changing those elements that differ. 5 | * 6 | * https://github.com/bryhoyt/emerj/blob/f427476ec/src/emerj.js 7 | */ 8 | 9 | import { getAttrs } from "./utils.js"; 10 | 11 | export function nodesByKey(parent) { 12 | const map = {}; 13 | 14 | for (let j = 0; j < parent.childNodes.length; j++) { 15 | const key = parent.childNodes[j].id; 16 | 17 | if (key) { 18 | map[key] = parent.childNodes[j]; 19 | } 20 | } 21 | 22 | return map; 23 | } 24 | 25 | export default function merge(base, modified) { 26 | /* Merge any differences between base and modified back into base. 27 | * 28 | * Operates only the children nodes, and does not change the root node or its 29 | * attributes. 30 | * 31 | * Conceptually similar to React's reconciliation algorithm: 32 | * https://facebook.github.io/react/docs/reconciliation.html 33 | * 34 | * I haven't thoroughly tested performance to compare to naive DOM updates (i.e. 35 | * just updating the entire DOM from a string using .innerHTML), but some quick 36 | * tests on a basic DOMs were twice as fast -- so at least it's not slower in 37 | * a simple scenario -- and it's definitely "fast enough" for responsive UI and 38 | * even smooth animation. 39 | * 40 | * The real advantage for me is not so much performance, but that state & identity 41 | * of existing elements is preserved -- text typed into an , an open 42 | * 219 |
220 | 221 | 222 |
223 | 224 |
225 | 226 | ::: 227 | 228 | ## [Event handling](components/events.md) 229 | 230 | Associate element events to JavaScript methods. 231 | 232 | :::{code} html 233 | :force: true 234 | 250 | 251 |
252 |

{this.count}

253 | 254 |
255 | 256 | 257 |
258 |
259 | ::: 260 | 261 | ### Nested components 262 | 263 | Nest a component inside another component. 264 | 265 | ```html 266 | 295 | 296 | 306 | 307 |
308 | 312 | 313 |
314 | ``` 315 | -------------------------------------------------------------------------------- /docs/source/components/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | A configuration with examples for all possible settings. 4 | 5 | ```js 6 | { 7 | el: '#app', 8 | tagName: 'hello-world', 9 | template: `Hello {this.world} {this.prop.name}!`, 10 | shadowDOM: true, 11 | $store: Litestate(), 12 | data: { 13 | world: 'World', 14 | helloWorld(state) { 15 | // computed data 16 | return `Hello {this.data.world}!`; 17 | } 18 | }, 19 | created() { 20 | console.log(`Component created with ${this.prop}`); 21 | }, 22 | updated() { 23 | console.log(`Component updated with ${this.data}`); 24 | }, 25 | removed() { 26 | console.log(`Component removed: ${this.el}`); 27 | }, 28 | sayHello(name) { 29 | // custom method 30 | console.log(`Hello ${name}`); 31 | }, 32 | } 33 | ``` 34 | 35 | ## Settings 36 | 37 | ### `el` 38 | [`string` | `HTMLElement`] 39 | 40 | The element where the component will be created and rendered. 41 | 42 | This can be an HTML selector (e.g. `#someId`, `[some-data-attribute]`) or a DOM element (e.g. `document.querySelector('#myId')`). 43 | 44 | ```html 45 | 56 | 57 | 59 | ``` 60 | 61 | If `template` is not set, the `innerHTML` of the element will be used as the template as an [in-place element](components/index.md#in-place-element). 62 | 63 | ```html 64 | 74 | 75 | 78 | ``` 79 | 80 | ### `tagName` 81 | [`string`] 82 | 83 | Name for the [custom element](components/index.md#custom-element) where the component will be created and rendered. Required for [custom elements](components/index.md#custom-element). 84 | 85 | ```{warning} 86 | Custom element names **must** contain a hyphen. `my-counter` will be used as `` 87 | ``` 88 | 89 | ```html 90 | 101 | 102 | 103 | ``` 104 | 105 | ### `template` 106 | [`string`] 107 | 108 | Markup for the HTML of the component. 109 | 110 | Required for [custom elements](components/index.md#custom-element). If `template` and `el` are set, the `template` will override the `innerHTML` of the `el`. 111 | 112 | ```html 113 | 124 | 125 | 126 | ``` 127 | 128 | ### `data` 129 | [`object`] 130 | 131 | Reactive application state. Whenever a property is updated or removed it will trigger a DOM update (if necessary). 132 | 133 | ```{warning} 134 | Keys must be present at initialization to be reactive. Adding a new a key after initialization will _not_ trigger an update. 135 | ``` 136 | 137 | Values are expected to be a `string`, `number`, `object`, `array`, `boolean`, `null`, `undefined` or `function`. 138 | 139 | #### Computed data 140 | 141 | Computed data is defined as a `function` in the `data` object. It is reactive and created based on some other input. Whenever the component state is updated, the computed data will also be updated. 142 | 143 | Computed data functions accept the current state as the only argument and must return a value. The value will be assigned in the `data` with the function's name. 144 | 145 | ```js 146 | data: { 147 | firstName: 'Mardix', 148 | lastName: 'M.', 149 | 150 | // used in a template with `{this.fullName}` or 'this.data.fullName' in JavaScript 151 | fullName: (state) => `${state.firstName} ${state.lastName}`, 152 | 153 | // used in a template with '{this.totalChars}' or 'this.data.totalChars' in JavaScript 154 | totalChars: (state) => state.fullName.length 155 | } 156 | ``` 157 | 158 | ```{note} 159 | You cannot access the computed data as functions in your code. 160 | ``` 161 | 162 | ```{note} 163 | You cannot mutate the state or access custom methods in the computed data function. 164 | ``` 165 | 166 | ### [`$store`](store.md) 167 | 168 | A shared store manager, i.e. `reStated`, `Redux`, [Litestate](https://github.com/mardix/litestate). The store instance must have `getState` and `subscribe` functions. 169 | 170 | ### `shadowDOM` 171 | [`boolean`: `true`] 172 | 173 | Components are created as a [`Custom Element`](https://developer.mozilla.org/en-US/docs/Web/Web_Components) and by default it will be attached to a `Shadow DOM` to help encapsulate it from the rest of the page with [scoped CSS](template.md#scoped-css). Disable this behavior by setting `shadowDOM` to `false` so the component will be affected by the page's CSS styles. 174 | 175 | ### `debug` 176 | [`boolean`: `false`] 177 | 178 | Whether or not to output configuration error messages to the page. 179 | 180 | ![Error message](../img/debug-error.png) 181 | 182 | ## Methods 183 | 184 | ### Lifecycle methods 185 | 186 | #### `created` 187 | 188 | Gets called when the component is created. 189 | 190 | ```html 191 | 202 | 203 | 204 | ``` 205 | 206 | #### `updated` 207 | 208 | Gets called each time the [`data`](#data) or the [`$store`](#store) updates the component's state. 209 | 210 | ```html 211 | 222 | 223 | 224 | ``` 225 | 226 | #### `removed` 227 | 228 | Gets called when the component is removed. 229 | 230 | ```html 231 | 242 | 243 | 244 | ``` 245 | 246 | ### Custom methods 247 | 248 | Custom methods can be accessed via `this`. 249 | 250 | ```js 251 | Dlite({ 252 | ... 253 | created() { 254 | this.sayHello('dlite'); 255 | }, 256 | sayHello(name) { 257 | console.log(`Hello ${name}`) 258 | }, 259 | }); 260 | ``` 261 | 262 | ````{note} 263 | You can use async methods with `async` and `await`. 264 | 265 | ```js 266 | Dlite({ 267 | el: '#app', 268 | data: { 269 | status: 'unknown', 270 | apiData: {}, 271 | } 272 | async loadData() { 273 | this.data.status = 'loading...'; 274 | const res = await fetch('https://some-api.com/data.json'); 275 | 276 | this.data.apiData = await res.data; 277 | this.data.status = 'loading completed!'; 278 | }, 279 | async created(event) { 280 | await this.loadData(); 281 | }, 282 | }); 283 | ``` 284 | ```` 285 | 286 | ```{warning} 287 | Do not use arrow functions such as `created: () => this.sayHello()`. Arrow functions do not have a `this` so this will result in errors such as `Uncaught TypeError: Cannot read property of undefined` or `Uncaught TypeError: this.myMethod is not a function`. 288 | ``` 289 | 290 | ### Access to `this` 291 | 292 | Inside of lifecycle and custom methods, you have access to the following properties via `this`. 293 | 294 | #### `this.el` 295 | 296 | The instance root element of the component. 297 | 298 | #### `this.data` 299 | 300 | The reactive [`data`](#data) defined in the configuration. 301 | 302 | Whenever `data` is updated it will re-render the DOM if necessary. 303 | 304 | #### `this.prop` 305 | 306 | The attributes of the custom element. 307 | 308 | ```html 309 | 327 | 328 | 329 | ``` 330 | -------------------------------------------------------------------------------- /test/utils.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @vitest-environment jsdom 3 | */ 4 | 5 | import { describe, test, expect } from "vitest"; 6 | import { Fixture } from "./helpers.js"; 7 | 8 | import { 9 | isFn, 10 | isObjKeyFn, 11 | htmlToDom, 12 | parseLit, 13 | computeState, 14 | set, 15 | get, 16 | toStrLit, 17 | kebabCase, 18 | camelCase, 19 | styleMap, 20 | } from "../src/utils.js"; 21 | 22 | describe("isFn", () => { 23 | test("isFn", () => { 24 | const fn = () => {}; 25 | expect(isFn(fn)).toBe(true); 26 | }); 27 | }); 28 | 29 | describe("isObjKeyFn", () => { 30 | test("is a function", () => { 31 | const o = { 32 | fn: () => {}, 33 | }; 34 | expect(isObjKeyFn(o, "fn")).toBe(true); 35 | }); 36 | 37 | test("not a function", () => { 38 | const o = { 39 | fn: 1, 40 | }; 41 | expect(isObjKeyFn(o, "fn")).toBe(false); 42 | }); 43 | }); 44 | 45 | describe("parseLit", () => { 46 | test("to return a function", () => { 47 | expect(parseLit("Hello ${this.name}")).toBeInstanceOf(Function); 48 | }); 49 | test("Hello ${this.name} === Hello world", () => { 50 | expect(parseLit("Hello ${this.name}")({ name: "world" })).toBe( 51 | "Hello world" 52 | ); 53 | }); 54 | test("Template literal stuff", () => { 55 | expect(parseLit("${1 + 1}")()).toBe("2"); 56 | }); 57 | }); 58 | 59 | describe("htmlToDom", () => { 60 | test("div to HTMLElement", () => { 61 | const div = "
Hello World
"; 62 | const { _, body } = htmlToDom(div); 63 | 64 | expect(body).toBeInstanceOf(HTMLElement); 65 | }); 66 | 67 | test("div to return same outerHTML div", () => { 68 | const div = "
Hello World
"; 69 | const { _, body } = htmlToDom(div); 70 | 71 | expect(body.outerHTML).toBe(div); 72 | }); 73 | 74 | test("style to HTMLHeadElement", () => { 75 | const style = ""; 76 | const html = `${style}
Hello World
`; 77 | const { head, _ } = htmlToDom(html); 78 | 79 | expect(head).toBeInstanceOf(HTMLHeadElement); 80 | }); 81 | 82 | test("style to same innerHTML", () => { 83 | const style = ""; 84 | const html = `${style}
Hello World
`; 85 | const { head, _ } = htmlToDom(html); 86 | 87 | expect(head.innerHTML).toBe(style); 88 | }); 89 | }); 90 | 91 | describe("computeState", () => { 92 | test("instantialize must return a function", () => { 93 | expect(computeState("name", (state) => {})).toBeInstanceOf(Function); 94 | }); 95 | 96 | test("computeState should mutate the state via returned value", () => { 97 | const data = { 98 | name: "HTML", 99 | }; 100 | const m = computeState("value", (state) => { 101 | return `OK ${state.name}`; 102 | }); 103 | m(data); 104 | expect(data.value).toBe("OK HTML"); 105 | }); 106 | }); 107 | 108 | describe("SET", () => { 109 | test("Set simple key value", () => { 110 | const o = {}; 111 | set(o, "key", "value"); 112 | expect(o.key).toBe("value"); 113 | }); 114 | 115 | test("Set dot notation", () => { 116 | const o = {}; 117 | set(o, "key.key2.key3", 10); 118 | expect(o.key.key2.key3).toBe(10); 119 | }); 120 | 121 | test("Set dot notation to be object", () => { 122 | const o = {}; 123 | set(o, "key.key2.key3", 10); 124 | expect(o.key.key2).toBeInstanceOf(Object); 125 | }); 126 | }); 127 | 128 | describe("GET", () => { 129 | test("Get simple key value", () => { 130 | const o = { 131 | key: "value", 132 | }; 133 | expect(get(o, "key")).toBe("value"); 134 | }); 135 | 136 | test("Get dot notation", () => { 137 | const o = { 138 | key: { 139 | key2: { 140 | key3: 10, 141 | }, 142 | }, 143 | }; 144 | expect(get(o, "key.key2.key3")).toBe(10); 145 | }); 146 | 147 | test("Set dot notation to be object", () => { 148 | const o = { 149 | key: { 150 | key2: { 151 | key3: 10, 152 | }, 153 | }, 154 | }; 155 | expect(get(o, "key.key2")).toBeInstanceOf(Object); 156 | }); 157 | 158 | test("Set dot notation to be undefined", () => { 159 | const o = { 160 | key: { 161 | key2: { 162 | key3: 10, 163 | }, 164 | }, 165 | }; 166 | expect(get(o, "key.key2.k4")).toBe(undefined); 167 | }); 168 | }); 169 | 170 | describe("toStrLit", () => { 171 | [ 172 | new Fixture("hello world", "hello world", "string to string"), 173 | new Fixture( 174 | "hello {key}", 175 | "hello ${typeof key != 'undefined' ? key : ''}", 176 | "string with var" 177 | ), 178 | new Fixture( 179 | "hello {key.aFunction()}", 180 | "hello ${typeof key.aFunction() != 'undefined' ? key.aFunction() : ''}", 181 | "string with var with method inside" 182 | ), 183 | new Fixture( 184 | "hello {key.val.something }", 185 | "hello ${typeof key.val.something != 'undefined' ? key.val.something : ''}", 186 | "string with var with properties" 187 | ), 188 | new Fixture( 189 | "hello {key.val.something + y + z}", 190 | "hello ${typeof key.val.something + y + z != 'undefined' ? key.val.something + y + z : ''}", 191 | "string with var with operations" 192 | ), 193 | new Fixture( 194 | "hello {this.x ? y : z}", 195 | "hello ${typeof this.x ? y : z != 'undefined' ? this.x ? y : z : ''}", 196 | "string with var with ternary" 197 | ), 198 | new Fixture( 199 | "hello { key}", 200 | "hello ${typeof key != 'undefined' ? key : ''}", 201 | "string with var with inside space left" 202 | ), 203 | new Fixture( 204 | "hello
", 205 | "hello
", 206 | "string with var with inside space left" 207 | ), 208 | new Fixture( 209 | "hello { key }", 210 | "hello ${typeof key != 'undefined' ? key : ''}", 211 | "string with var with inside space right" 212 | ), 213 | new Fixture( 214 | "hello {key }", 215 | "hello ${typeof key != 'undefined' ? key : ''}", 216 | "string with var with inside space right 2" 217 | ), 218 | new Fixture( 219 | "hello {key}", 220 | "hello ${typeof key != 'undefined' ? key : ''}", 221 | "string with var with leading extra space" 222 | ), 223 | new Fixture( 224 | "hello {key} ", 225 | "hello ${typeof key != 'undefined' ? key : ''} ", 226 | "string with var with trailing extra space" 227 | ), 228 | new Fixture( 229 | "hello ${key}", 230 | "hello ${typeof key != 'undefined' ? key : ''}", 231 | "string with $" 232 | ), 233 | new Fixture( 234 | "hello $${key}", 235 | "hello $${typeof key != 'undefined' ? key : ''}", 236 | "string with $$" 237 | ), 238 | new Fixture( 239 | "hello ${key.aFunction()}", 240 | "hello ${typeof key.aFunction() != 'undefined' ? key.aFunction() : ''}", 241 | "string with $ with a method inside" 242 | ), 243 | new Fixture( 244 | "hello ${this.x ? y : z}", 245 | "hello ${typeof this.x ? y : z != 'undefined' ? this.x ? y : z : ''}", 246 | "string with $ with ternary" 247 | ), 248 | ].forEach((fixture) => { 249 | test(fixture.name, () => { 250 | expect(toStrLit(fixture.data)).toBe(fixture.expected); 251 | }); 252 | }); 253 | }); 254 | 255 | describe("kebabCase", () => { 256 | [ 257 | new Fixture("hello", "hello"), 258 | new Fixture("hello-world", "hello-world"), 259 | new Fixture("helloWorld", "hello-world"), 260 | new Fixture("helloworld", "helloworld"), 261 | new Fixture("helloAWorld", "hello-a-world"), 262 | new Fixture("HelloWorld", "hello-world"), 263 | new Fixture("HelloABCWorld", "hello-a-b-c-world"), 264 | ].forEach((fixture) => { 265 | test(fixture.name, () => { 266 | expect(kebabCase(fixture.data)).toBe(fixture.expected); 267 | }); 268 | }); 269 | }); 270 | 271 | describe("camelCase", () => { 272 | [ 273 | new Fixture("hello", "hello"), 274 | new Fixture("hello-world", "helloWorld"), 275 | new Fixture("hello_world", "helloWorld"), 276 | new Fixture("hello world", "helloWorld"), 277 | new Fixture("hello my world", "helloMyWorld"), 278 | ].forEach((fixture) => { 279 | test(fixture.name, () => { 280 | expect(camelCase(fixture.data)).toBe(fixture.expected); 281 | }); 282 | }); 283 | }); 284 | 285 | describe("styleMap", () => { 286 | test("stylemap", () => { 287 | const s = { 288 | color: "blue", 289 | topLine: "yellow", 290 | "bottom-dash": "red", 291 | "font-size": "12px", 292 | backgroundColor: "purple", 293 | textSize: "2em", 294 | margin: "0 20 40 30 !important", 295 | }; 296 | 297 | expect(styleMap(s)).toBe( 298 | "color: blue; top-line: yellow; bottom-dash: red; font-size: 12px; background-color: purple; text-size: 2em; margin: 0 20 40 30 !important;" 299 | ); 300 | }); 301 | }); 302 | -------------------------------------------------------------------------------- /src/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Turn camelCase to kebab-case 3 | * kebabCase('userId') => "user-id" 4 | * kebabCase('waitAMoment') => "wait-a-moment" 5 | * kebabCase('TurboPascal') => "turbo-pascal" 6 | * @param {string} s 7 | * @returns {string} 8 | */ 9 | export const kebabCase = (s) => { 10 | return s.replace(/([a-zA-Z])(?=[A-Z])/g, "$1-").toLowerCase(); 11 | }; 12 | 13 | /** 14 | * Convert string to camelCase 15 | * user-id => userId 16 | * wait-a-moment => waitAMoment 17 | * wait a moment => waitAMoment 18 | * wait_a_moment => waitAMoment 19 | * @param {string} s 20 | * @returns {string} 21 | */ 22 | export const camelCase = (s) => { 23 | return s.replace(/[-_\s+]([a-z])/g, (g) => g[1].toUpperCase()); 24 | }; 25 | 26 | /** 27 | * Turn an object into a stylemap to be used in inline css 28 | * { 29 | * "font-size": "12px", 30 | * backgroundColor: 'purple', 31 | * margin: '0 0 !important' 32 | * } => font-size: 12px; background-color: purple; margin: 0 0 !important 33 | * @param {object} o 34 | * @returns {string} 35 | */ 36 | export const styleMap = (o) => { 37 | return Object.keys(o) 38 | .map((k) => `${kebabCase(k)}: ${o[k]};`) 39 | .join(" "); 40 | }; 41 | 42 | /** 43 | * Set a value in an object via dot notation 44 | * @param {object} obj 45 | * @param {string} path 46 | * @param {any} value 47 | * @returns {void} 48 | */ 49 | export const set = (obj, path, value) => { 50 | let ref = obj; 51 | const keys = path.split("."); 52 | 53 | while (keys.length) { 54 | const key = keys.shift(); 55 | ref[key] = keys.length ? (ref[key] ? ref[key] : {}) : value; 56 | ref = ref[key]; 57 | } 58 | }; 59 | 60 | /** 61 | * Get a value in an object via dot notation 62 | * @param {object} obj 63 | * @param {string} path 64 | * @returns {any} 65 | */ 66 | export const get = (obj, path) => { 67 | return path.split(".").reduce((acc, part) => acc && acc[part], obj); 68 | }; 69 | 70 | /** 71 | * Check if an object is a function 72 | * @param {object} o 73 | * @returns {boolean} 74 | */ 75 | export const isFn = (o) => { 76 | return typeof o === "function"; 77 | }; 78 | 79 | /** 80 | * isObjKeyFn isFunction 81 | * @param {object} obj 82 | * @param {string} key 83 | * @returns {boolean} 84 | */ 85 | export const isObjKeyFn = (obj, key) => { 86 | return obj && isFn(obj[key]); 87 | }; 88 | 89 | /** 90 | * Turn an HTML string into HTMLElement 91 | * @param {string} html 92 | * @returns {{head: HTMLHeadElement, body: HTMLElement}} 93 | */ 94 | export const htmlToDom = (html) => { 95 | const parsedDOM = new DOMParser().parseFromString(html, "text/html"); 96 | 97 | return { head: parsedDOM.head, body: parsedDOM.body }; 98 | }; 99 | 100 | /** 101 | * Get a string and turn it into template literal 102 | * @param {string} tpl 103 | * @returns {function} 104 | * 105 | * x = parseLit(string) 106 | * x(state) // to update 107 | */ 108 | export const parseLit = (tpl) => (state) => { 109 | return new Function(`return \`${tpl}\``).call(state); 110 | }; 111 | 112 | /** 113 | * Convert a string that contains {...} to ${...} 114 | * @param {string} str 115 | * @returns {string} 116 | */ 117 | export const toStrLit = (str) => { 118 | return str.replace( 119 | /\$?\{([^\;\{]+)\}/g, 120 | (_, expression) => 121 | `\${typeof ${expression} != 'undefined' ? ${expression} : ''}` 122 | ); 123 | }; 124 | 125 | /** 126 | * Create a function that receive data to create computed state 127 | * @param {string} key 128 | * @param {function} fn 129 | * myCs = computeState(('fullName', (state) => return state.name) => ) 130 | * myCs({name: 'Mardix'}) 131 | * myCs.fullName -> Mardix 132 | */ 133 | export const computeState = (key, fn) => (state) => { 134 | return (state[key] = fn({ ...state })); 135 | }; 136 | 137 | /** 138 | * @type {array} 139 | */ 140 | const decodeHTMLList = [ 141 | ["<", "<"], 142 | [">", ">"], 143 | ["&", "&"], 144 | ]; 145 | 146 | /** 147 | * To decode html string for directive 148 | * change < + > to < + > respectively 149 | * @param {string} str 150 | * @returns {string} 151 | */ 152 | export const decodeHTMLStringForDirective = (str) => { 153 | return decodeHTMLList.reduce( 154 | (pV, cK) => pV.replace(new RegExp(cK[0], "g"), cK[1]), 155 | str 156 | ); 157 | }; 158 | 159 | /** 160 | * Returns all attributes into an object 161 | * @param {HTMLElement} el 162 | * @param {boolean} camelCaseIt 163 | * @returns {object} 164 | */ 165 | export const getAttrs = (el, camelCaseIt = false) => { 166 | return Object.freeze( 167 | Array.from(el.attributes) 168 | .map((e) => ({ [camelCaseIt ? camelCase(e.name) : e.name]: e.value })) 169 | .reduce((pV, cK) => ({ ...pV, ...cK }), {}) 170 | ); 171 | }; 172 | 173 | const proxyTarget = "#"; 174 | const isPrimitive = (value) => { 175 | return value === null || !["function", "object"].includes(typeof value); 176 | }; 177 | 178 | /** 179 | * objectOnChange 180 | * Observe an object change, and run onChange() 181 | * @param {*} object 182 | * @param {*} onChange 183 | */ 184 | export const objectOnChange = (object, onChange) => { 185 | let inApply = false; 186 | let changed = false; 187 | const propCache = new WeakMap(); 188 | 189 | const handleChange = () => { 190 | if (!inApply) { 191 | onChange(); 192 | } else if (!changed) { 193 | changed = true; 194 | } 195 | }; 196 | 197 | const getOwnPropertyDescriptor = (target, property) => { 198 | let props = propCache.get(target); 199 | 200 | if (props) { 201 | return props; 202 | } 203 | 204 | props = new Map(); 205 | propCache.set(target, props); 206 | 207 | let prop = props.get(property); 208 | 209 | if (!prop) { 210 | prop = Reflect.getOwnPropertyDescriptor(target, property); 211 | props.set(property, prop); 212 | } 213 | 214 | return prop; 215 | }; 216 | 217 | const handler = { 218 | get(target, property, receiver) { 219 | if (property === proxyTarget) { 220 | return target; 221 | } 222 | 223 | const value = Reflect.get(target, property, receiver); 224 | 225 | if (isPrimitive(value) || property === "constructor") { 226 | return value; 227 | } 228 | 229 | const descriptor = getOwnPropertyDescriptor(target, property); 230 | 231 | if (descriptor && !descriptor.configurable) { 232 | if (descriptor.set && !descriptor.get) { 233 | return undefined; 234 | } 235 | 236 | if (descriptor.writable === false) { 237 | return value; 238 | } 239 | } 240 | 241 | return new Proxy(value, handler); 242 | }, 243 | 244 | set(target, property, value, receiver) { 245 | if (value && value[proxyTarget] !== undefined) { 246 | value = value[proxyTarget]; 247 | } 248 | 249 | const previous = Reflect.get(target, property, receiver); 250 | const result = Reflect.set(target, property, value); 251 | 252 | if (previous !== value) { 253 | handleChange(); 254 | } 255 | 256 | return result; 257 | }, 258 | 259 | defineProperty(target, property, descriptor) { 260 | const result = Reflect.defineProperty(target, property, descriptor); 261 | handleChange(); 262 | 263 | return result; 264 | }, 265 | 266 | deleteProperty(target, property) { 267 | const result = Reflect.deleteProperty(target, property); 268 | handleChange(); 269 | 270 | return result; 271 | }, 272 | 273 | apply(target, thisArg, argumentsList) { 274 | if (!inApply) { 275 | inApply = true; 276 | const result = Reflect.apply(target, thisArg, argumentsList); 277 | 278 | if (changed) { 279 | onChange(); 280 | } 281 | 282 | inApply = changed = false; 283 | 284 | return result; 285 | } 286 | 287 | return Reflect.apply(target, thisArg, argumentsList); 288 | }, 289 | }; 290 | 291 | const proxy = new Proxy(object, handler); 292 | 293 | return proxy; 294 | }; 295 | 296 | /** 297 | * Deep copy object 298 | * @param {object} obj 299 | * @returns {object} 300 | */ 301 | function deepCopy(obj) { 302 | if (obj === null || typeof obj !== "object") { 303 | return obj; 304 | } 305 | 306 | var temp = obj.constructor(); 307 | 308 | for (const key in obj) { 309 | if (Object.prototype.hasOwnProperty.call(obj, key)) { 310 | temp[key] = freeze(obj[key]); 311 | } 312 | } 313 | 314 | return temp; 315 | } 316 | 317 | /** 318 | * Deep freezes object 319 | * @param {object} obj 320 | * @returns {object} 321 | */ 322 | function deepFreeze(obj) { 323 | if (obj === null || typeof obj !== "object") { 324 | return obj; 325 | } 326 | 327 | Object.keys(obj).forEach(function (name) { 328 | const prop = obj[name]; 329 | 330 | if (prop !== null && typeof prop === "object") { 331 | deepFreeze(prop); 332 | } 333 | }); 334 | 335 | return Object.freeze(obj); 336 | } 337 | 338 | /** 339 | * Creates a deep immutable object 340 | * @param {object} obj 341 | * @returns {object} 342 | */ 343 | export const freeze = (obj) => { 344 | return deepFreeze(deepCopy(obj)); 345 | }; 346 | -------------------------------------------------------------------------------- /dist/dlite.es.js: -------------------------------------------------------------------------------- 1 | /* dlite v0.19.0 */ 2 | const t=(t,e,n)=>{let r=t;const o=e.split(".");for(;o.length;){const t=o.shift();r[t]=o.length?r[t]?r[t]:{}:n,r=r[t]}},e=(t,e)=>e.split(".").reduce(((t,e)=>t&&t[e]),t),n=t=>"function"==typeof t,r=(t,e)=>t&&n(t[e]),o=t=>{const e=(new DOMParser).parseFromString(t,"text/html");return{head:e.head,body:e.body}},i=[["<","<"],[">",">"],["&","&"]],s=(t,e=!1)=>Object.freeze(Array.from(t.attributes).map((t=>{return{[e?(n=t.name,n.replace(/[-_\s+]([a-z])/g,(t=>t[1].toUpperCase()))):t.name]:t.value};var n})).reduce(((t,e)=>({...t,...e})),{})),l=(t,e)=>{let n=!1,r=!1;const o=new WeakMap,i=()=>{n?r||(r=!0):e()},s={get(t,e,n){if("#"===e)return t;const r=Reflect.get(t,e,n);if((t=>null===t||!["function","object"].includes(typeof t))(r)||"constructor"===e)return r;const i=((t,e)=>{let n=o.get(t);if(n)return n;n=new Map,o.set(t,n);let r=n.get(e);return r||(r=Reflect.getOwnPropertyDescriptor(t,e),n.set(e,r)),r})(t,e);if(i&&!i.configurable){if(i.set&&!i.get)return;if(!1===i.writable)return r}return new Proxy(r,s)},set(t,e,n,r){n&&void 0!==n["#"]&&(n=n["#"]);const o=Reflect.get(t,e,r),s=Reflect.set(t,e,n);return o!==n&&i(),s},defineProperty(t,e,n){const r=Reflect.defineProperty(t,e,n);return i(),r},deleteProperty(t,e){const n=Reflect.deleteProperty(t,e);return i(),n},apply(t,o,i){if(!n){n=!0;const s=Reflect.apply(t,o,i);return r&&e(),n=r=!1,s}return Reflect.apply(t,o,i)}};return new Proxy(t,s)};function c(t){return null===t||"object"!=typeof t?t:(Object.keys(t).forEach((function(e){const n=t[e];null!==n&&"object"==typeof n&&c(n)})),Object.freeze(t))}const a=t=>c(function(t){if(null===t||"object"!=typeof t)return t;var e=t.constructor();for(const n in t)Object.prototype.hasOwnProperty.call(t,n)&&(e[n]=a(t[n]));return e}(t)),u=[];for(const t in document){const e=null===document[t]||n(document[t]);t.startsWith("on")&&e&&u.push(t.substring(2))}const d="dl--elist",f=t=>`dl--on-${t}`;function h(t){const e={};for(let n=0;n=t.childNodes.length){t.appendChild(o);continue}let i=t.childNodes[r];const l=o.id;if(i.id||l){const e=l&&l in n.old?n.old[l]:o;e!==i&&(i=t.insertBefore(e,i))}if(i.nodeType!==o.nodeType||i.tagName!==o.tagName)t.replaceChild(o,i);else if([Node.TEXT_NODE,Node.COMMENT_NODE].indexOf(i.nodeType)>=0){if(i.textContent===o.textContent)continue;i.textContent=o.textContent}else if(i!==o){const t={base:s(i),new:s(o)};for(const e in t.base)e in t.new||i.removeAttribute(e);for(const e in t.new)e in t.base&&t.base[e]===t.new[e]||i.setAttribute(e,t.new[e]);p(i,o)}}for(;t.childNodes.length>r;)t.removeChild(t.lastChild);return!0}const b={$key:function(t,e,n){t.setAttribute("ref-key",e),$(t,n)},$class:function(t,e,n){const r=e.split(";").map((t=>t.split(":",2).map((t=>t.trim())))).map((t=>`\${${t[1]} ? '${t[0]}': ''}`)).join(" "),o=(t.getAttribute("class")||"")+` ${r}`;t.setAttribute("class",o),$(t,n)},$style:function(t,e,n){const r=t.getAttribute("style")||"",o=`\${function() { return this.__$styleMap(${e});}.call(this)}`;t.setAttribute("style",(r?r+"; ":"")+o),$(t,n)},$for:function(t,e,n){const r=/(.*)\s+(in)\s+(.*)$/.exec(e);if(4===r.length){const e=r[1].replace("(","").replace(")",""),o=r[3];w(t,`\${${o}.map(function(${e}) { return \``,"`}.bind(this)).join('')}"),$(t,n)}},$if:function(t,e,n){$(t,n),v(t,`\${${e} ? `);const r=t.nextElementSibling;r&&y(r,"else")?(w(t,"`","`"),$(r,"else"),w(r,":`","`}")):w(t,"`","`:``}")},$text:function(t,e,n){t.textContent=e,$(t,n)}},m=t=>`:${t}`,y=(t,e)=>t.hasAttribute(m(e)),g=(t,e)=>t.getAttribute(m(e)),$=(t,e)=>t.removeAttribute(m(e)),A=(t,e)=>t.querySelectorAll(`[\\${m(e)}]`),v=(t,e)=>t.insertAdjacentText("beforebegin",e),w=(t,e,n)=>{v(t,e),((t,e)=>{t.insertAdjacentText("afterend",e)})(t,n)};const x=["data","el","shadowDOM","template","created","updated","removed","$store","prop","tagName"],M=t=>Object.keys(t).filter((t=>!x.includes(t))).filter((t=>!t.startsWith("$"))).filter((e=>r(t,e))).reduce(((e,n)=>({...e,[n]:t[n]})),{}),N=t=>Object.keys(t).filter((e=>!r(t,e))).reduce(((e,n)=>({...e,[n]:t[n]})),{}),j=t=>Object.keys(t).filter((e=>r(t,e))).map((e=>{return n=e,r=t[e],t=>t[n]=r({...t});var n,r})),E=t=>Object.keys(t).filter((t=>t.startsWith("$"))).filter((t=>!x.includes(t))).reduce(((e,n)=>({...e,[n]:t[n]})),{}),O=t=>e=>(e.$store=t.getState(),t.subscribe((n=>e.$store={...t.getState()}))),T=(t,e,n)=>{Object.keys(e).filter((t=>!t.startsWith("_"))).map((r=>t[r]=e[r].bind(n)))},k=t=>{const{head:e,body:n}=o(t);!function(t,e={}){const n={...e,...b};for(const e in n){const r=e.replace("$","");for(const o of A(t,r))if(y(o,r)){const t=g(o,r);n[e](o,t,r)}}}(n),function(t){for(const e of t.querySelectorAll("[\\@call], [\\@bind]")){let t=e.getAttribute("@call");e.removeAttribute("@call"),e.hasAttribute("@bind")&&(e.setAttribute("dl--bind",e.getAttribute("@bind")),e.removeAttribute("@bind"),t="__$bindInput");let n=["click"];e instanceof HTMLInputElement||e instanceof HTMLTextAreaElement?n=["input","paste"]:e instanceof HTMLSelectElement?n=["change"]:e instanceof HTMLFormElement?n=["submit"]:e instanceof HTMLAnchorElement&&e.setAttribute("href","javascript:void(0);");let r=(e.getAttribute(d)||"").split(",").filter((t=>t));r=r.concat(n),e.setAttribute(d,r.join(","));for(const r of n)e.setAttribute(f(r),t)}for(const e of u)for(const n of t.querySelectorAll(`[\\@${e}]`)){const t=(n.getAttribute(d)||"").split(",").filter((t=>t));t.push(e),n.setAttribute(d,t.join(",")),n.setAttribute(f(e),n.getAttribute(`@${e}`)),n.removeAttribute(`@${e}`),n instanceof HTMLAnchorElement&&n.setAttribute("href","javascript:void(0);")}}(n);let r=(t=>t.replace(/\$?\{([^\;\{]+)\}/g,((t,e)=>`\${typeof ${e} != 'undefined' ? ${e} : ''}`)))((s=n.innerHTML,i.reduce(((t,e)=>t.replace(new RegExp(e[0],"g"),e[1])),s)));var s;e.querySelectorAll('style, link[rel="stylesheet"]').forEach((t=>{r+=t.outerHTML}));const l=(c=r,t=>new Function(`return \`${c}\``).call(t));var c;return{html:r,render:(t,e)=>{const{head:n,body:r}=o(l(e));return r.append(...n.querySelectorAll("style, link")),!t.isEqualNode(r)&&p(t,r)}}};function C(n){const r=n.target,o=r.getAttribute("ld--bind");if("checkbox"===r.type){const n=e(this.data,o)||[];t(this.data,o,r.checked?n.concat(r.value):n.filter((t=>t!=r.value)))}else r.options&&r.multiple?t(this.data,o,[].reduce.call(r,((t,e)=>e.selected?t.concat(e.value):t),[])):t(this.data,o,r.value)}const _={__$styleMap:t=>Object.keys(t).map((e=>{return`${n=e,n.replace(/([a-zA-Z])(?=[A-Z])/g,"$1-").toLowerCase()}: ${t[e]};`;var n})).join(" ")};function S(t={}){return function(t){let n=null;const r=O(t.$store),o=k(t.template),i=M(t),c=N(t.data),u=j(t.data),h=E(t),p=t=>u.forEach((e=>e(t)));return window.customElements.define(t.tagName.toLowerCase(),class extends HTMLElement{constructor(){super(),this.style.visibility=this.style.visibility?this.style.visibility:"hidden",this.$root=t.shadowDOM?this.attachShadow({mode:"open"}):this,n=this}render(e=!1){p(this._state),o.render(this.$root,{...this._state,..._})&&!e&&t.updated.call(this.context),"hidden"===this.style.visibility&&this.style.removeProperty("visibility")}connectedCallback(){if(!this.isConnected)return;this._state={...this._state,...c,prop:s(this,!0),...i};const n=l(this._state,this.render.bind(this));this.disconnectStore=r(n),this.$root.innerHTML=o.html,this.context={...i,...h,data:n,el:this.$root,prop:this._state.prop,$store:t.$store},function(t,n){function r(t){Array.from(t.querySelectorAll(`[${d}]`)).map((t=>{(t.getAttribute(d)||"").split(",").filter((t=>t)).map((e=>{t[`on${e}`]=r=>{r.preventDefault();const o=t.getAttribute(f(e));n[o].call(n,r)}}))}))}Array.from(t.querySelectorAll("[dl--bind]")).map((t=>{const r=e(n.data,t.getAttribute("dl--bind"));try{"INPUT"===t.tagName&&["radio","checkbox"].includes(t.type)?r.includes(t.value)&&(t.checked=!0):t.value=r}catch(t){}}));const o=new MutationObserver((t=>{[...t].filter((t=>t.addedNodes.length>0)).map((t=>t.target)).map((t=>r(t)))}));o.observe(t,{attributes:!0,childList:!0,subtree:!0}),r(t)}(this.$root,{...this.context,__$bindInput:C}),T(this,i,this.context),T(this._state,i,this.context),this.render(!0),t.created.call(this.context)}disconnectedCallback(){t.removed.call(this.context),this.disconnectStore()}get data(){return a(this._state)}}),n}({shadowDOM:!0,tagName:null,data:{},template:null,$store:{getState:()=>{},subscribe:()=>()=>{}},created(){},updated(){},removed(){},...t})}async function L(t){return await fetch(t).then((t=>{if(t.status>=400)throw new Error(`${t.url} (${t.status})`);return t})).then((t=>(t.headers.get("content-type")||"").includes("json")?t.json():t.text()))}function H(t,e={},n=""){const r=document.createElement(t);r.innerHTML=n;for(const[t,n]of Object.entries(e))r.setAttribute(t,n);return r}var P=(t,e={})=>(Array.isArray(t)||(t=[t]),t.map(((t,n)=>{try{return function(t){const e={el:null,template:null,tagName:null,shadowDOM:!0,debug:!1,...t},n=!e.tagName;if(e.tagName=e.tagName||`dlite-${Math.random().toString(36).substring(2,9).toLowerCase()}`,e.el){if("string"==typeof e.el){const t=document.querySelector(e.el);if(!t)throw new Error(`'${e.el}' could not be found.`);e.el=t}if(e.template||(e.template=e.el.innerHTML),e.el.innerHTML="",n){const t=H(e.tagName,s(e.el));for(const[n,r]of Object.entries(s(e.el)))t.setAttribute(n,r);e.el.parentNode.replaceChild(t,e.el)}}else if(!e.el&&n)throw new Error("Missing either 'el' or 'tagName' setting.");if(!e.template)throw new Error("Missing 'template' setting.");return S(e)}({...e,...t})}catch(r){if(!e.debug&&!t.debug)throw r;{const t=H("div",{style:"background-color: red; color: white; padding: 10px;"},`\n

#${n+1}

\n${r}\n
\n Stacktrace\n ${r.stack.replaceAll("\n","
")}\n
`);document.body.prepend(t),console.error(r)}}})));export{P as default,L as fetcher}; 3 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "alabaster" 5 | version = "0.7.13" 6 | description = "A configurable sidebar-enabled Sphinx theme" 7 | category = "main" 8 | optional = false 9 | python-versions = ">=3.6" 10 | files = [ 11 | {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, 12 | {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, 13 | ] 14 | 15 | [[package]] 16 | name = "attrs" 17 | version = "22.2.0" 18 | description = "Classes Without Boilerplate" 19 | category = "main" 20 | optional = false 21 | python-versions = ">=3.6" 22 | files = [ 23 | {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, 24 | {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, 25 | ] 26 | 27 | [package.extras] 28 | cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] 29 | dev = ["attrs[docs,tests]"] 30 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] 31 | tests = ["attrs[tests-no-zope]", "zope.interface"] 32 | tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] 33 | 34 | [[package]] 35 | name = "babel" 36 | version = "2.11.0" 37 | description = "Internationalization utilities" 38 | category = "main" 39 | optional = false 40 | python-versions = ">=3.6" 41 | files = [ 42 | {file = "Babel-2.11.0-py3-none-any.whl", hash = "sha256:1ad3eca1c885218f6dce2ab67291178944f810a10a9b5f3cb8382a5a232b64fe"}, 43 | {file = "Babel-2.11.0.tar.gz", hash = "sha256:5ef4b3226b0180dedded4229651c8b0e1a3a6a2837d45a073272f313e4cf97f6"}, 44 | ] 45 | 46 | [package.dependencies] 47 | pytz = ">=2015.7" 48 | 49 | [[package]] 50 | name = "beautifulsoup4" 51 | version = "4.11.1" 52 | description = "Screen-scraping library" 53 | category = "main" 54 | optional = false 55 | python-versions = ">=3.6.0" 56 | files = [ 57 | {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, 58 | {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, 59 | ] 60 | 61 | [package.dependencies] 62 | soupsieve = ">1.2" 63 | 64 | [package.extras] 65 | html5lib = ["html5lib"] 66 | lxml = ["lxml"] 67 | 68 | [[package]] 69 | name = "certifi" 70 | version = "2022.12.7" 71 | description = "Python package for providing Mozilla's CA Bundle." 72 | category = "main" 73 | optional = false 74 | python-versions = ">=3.6" 75 | files = [ 76 | {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, 77 | {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, 78 | ] 79 | 80 | [[package]] 81 | name = "charset-normalizer" 82 | version = "3.0.1" 83 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 84 | category = "main" 85 | optional = false 86 | python-versions = "*" 87 | files = [ 88 | {file = "charset-normalizer-3.0.1.tar.gz", hash = "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f"}, 89 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6"}, 90 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db"}, 91 | {file = "charset_normalizer-3.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539"}, 92 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d"}, 93 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"}, 94 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e"}, 95 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d"}, 96 | {file = "charset_normalizer-3.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783"}, 97 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555"}, 98 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639"}, 99 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76"}, 100 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6"}, 101 | {file = "charset_normalizer-3.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e"}, 102 | {file = "charset_normalizer-3.0.1-cp310-cp310-win32.whl", hash = "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b"}, 103 | {file = "charset_normalizer-3.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59"}, 104 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d"}, 105 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3"}, 106 | {file = "charset_normalizer-3.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c"}, 107 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b"}, 108 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1"}, 109 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317"}, 110 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603"}, 111 | {file = "charset_normalizer-3.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18"}, 112 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a"}, 113 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7"}, 114 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc"}, 115 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b"}, 116 | {file = "charset_normalizer-3.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6"}, 117 | {file = "charset_normalizer-3.0.1-cp311-cp311-win32.whl", hash = "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3"}, 118 | {file = "charset_normalizer-3.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c"}, 119 | {file = "charset_normalizer-3.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a"}, 120 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea"}, 121 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72"}, 122 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478"}, 123 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d"}, 124 | {file = "charset_normalizer-3.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837"}, 125 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6"}, 126 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc"}, 127 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a"}, 128 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c"}, 129 | {file = "charset_normalizer-3.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a"}, 130 | {file = "charset_normalizer-3.0.1-cp36-cp36m-win32.whl", hash = "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41"}, 131 | {file = "charset_normalizer-3.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602"}, 132 | {file = "charset_normalizer-3.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14"}, 133 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d"}, 134 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc"}, 135 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3"}, 136 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866"}, 137 | {file = "charset_normalizer-3.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b"}, 138 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087"}, 139 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8"}, 140 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b"}, 141 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918"}, 142 | {file = "charset_normalizer-3.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d"}, 143 | {file = "charset_normalizer-3.0.1-cp37-cp37m-win32.whl", hash = "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154"}, 144 | {file = "charset_normalizer-3.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5"}, 145 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42"}, 146 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c"}, 147 | {file = "charset_normalizer-3.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e"}, 148 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58"}, 149 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174"}, 150 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753"}, 151 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b"}, 152 | {file = "charset_normalizer-3.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1"}, 153 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a"}, 154 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed"}, 155 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479"}, 156 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291"}, 157 | {file = "charset_normalizer-3.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820"}, 158 | {file = "charset_normalizer-3.0.1-cp38-cp38-win32.whl", hash = "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e"}, 159 | {file = "charset_normalizer-3.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af"}, 160 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f"}, 161 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678"}, 162 | {file = "charset_normalizer-3.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be"}, 163 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b"}, 164 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca"}, 165 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e"}, 166 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3"}, 167 | {file = "charset_normalizer-3.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541"}, 168 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d"}, 169 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579"}, 170 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4"}, 171 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c"}, 172 | {file = "charset_normalizer-3.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786"}, 173 | {file = "charset_normalizer-3.0.1-cp39-cp39-win32.whl", hash = "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8"}, 174 | {file = "charset_normalizer-3.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59"}, 175 | {file = "charset_normalizer-3.0.1-py3-none-any.whl", hash = "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24"}, 176 | ] 177 | 178 | [[package]] 179 | name = "colorama" 180 | version = "0.4.6" 181 | description = "Cross-platform colored terminal text." 182 | category = "main" 183 | optional = false 184 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 185 | files = [ 186 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 187 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 188 | ] 189 | 190 | [[package]] 191 | name = "docutils" 192 | version = "0.19" 193 | description = "Docutils -- Python Documentation Utilities" 194 | category = "main" 195 | optional = false 196 | python-versions = ">=3.7" 197 | files = [ 198 | {file = "docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc"}, 199 | {file = "docutils-0.19.tar.gz", hash = "sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6"}, 200 | ] 201 | 202 | [[package]] 203 | name = "furo" 204 | version = "2022.12.7" 205 | description = "A clean customisable Sphinx documentation theme." 206 | category = "main" 207 | optional = false 208 | python-versions = ">=3.7" 209 | files = [ 210 | {file = "furo-2022.12.7-py3-none-any.whl", hash = "sha256:7cb76c12a25ef65db85ab0743df907573d03027a33631f17d267e598ebb191f7"}, 211 | {file = "furo-2022.12.7.tar.gz", hash = "sha256:d8008f8efbe7587a97ba533c8b2df1f9c21ee9b3e5cad0d27f61193d38b1a986"}, 212 | ] 213 | 214 | [package.dependencies] 215 | beautifulsoup4 = "*" 216 | pygments = ">=2.7" 217 | sphinx = ">=5.0,<7.0" 218 | sphinx-basic-ng = "*" 219 | 220 | [[package]] 221 | name = "idna" 222 | version = "3.4" 223 | description = "Internationalized Domain Names in Applications (IDNA)" 224 | category = "main" 225 | optional = false 226 | python-versions = ">=3.5" 227 | files = [ 228 | {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, 229 | {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, 230 | ] 231 | 232 | [[package]] 233 | name = "imagesize" 234 | version = "1.4.1" 235 | description = "Getting image size from png/jpeg/jpeg2000/gif file" 236 | category = "main" 237 | optional = false 238 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 239 | files = [ 240 | {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, 241 | {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, 242 | ] 243 | 244 | [[package]] 245 | name = "importlib-metadata" 246 | version = "6.0.0" 247 | description = "Read metadata from Python packages" 248 | category = "main" 249 | optional = false 250 | python-versions = ">=3.7" 251 | files = [ 252 | {file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"}, 253 | {file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"}, 254 | ] 255 | 256 | [package.dependencies] 257 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 258 | zipp = ">=0.5" 259 | 260 | [package.extras] 261 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 262 | perf = ["ipython"] 263 | 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)"] 264 | 265 | [[package]] 266 | name = "jinja2" 267 | version = "3.1.2" 268 | description = "A very fast and expressive template engine." 269 | category = "main" 270 | optional = false 271 | python-versions = ">=3.7" 272 | files = [ 273 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, 274 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, 275 | ] 276 | 277 | [package.dependencies] 278 | MarkupSafe = ">=2.0" 279 | 280 | [package.extras] 281 | i18n = ["Babel (>=2.7)"] 282 | 283 | [[package]] 284 | name = "linkify-it-py" 285 | version = "2.0.0" 286 | description = "Links recognition library with FULL unicode support." 287 | category = "main" 288 | optional = false 289 | python-versions = ">=3.6" 290 | files = [ 291 | {file = "linkify-it-py-2.0.0.tar.gz", hash = "sha256:476464480906bed8b2fa3813bf55566282e55214ad7e41b7d1c2b564666caf2f"}, 292 | {file = "linkify_it_py-2.0.0-py3-none-any.whl", hash = "sha256:1bff43823e24e507a099e328fc54696124423dd6320c75a9da45b4b754b748ad"}, 293 | ] 294 | 295 | [package.dependencies] 296 | uc-micro-py = "*" 297 | 298 | [package.extras] 299 | benchmark = ["pytest", "pytest-benchmark"] 300 | dev = ["black", "flake8", "isort", "pre-commit"] 301 | doc = ["myst-parser", "sphinx", "sphinx-book-theme"] 302 | test = ["coverage", "pytest", "pytest-cov"] 303 | 304 | [[package]] 305 | name = "livereload" 306 | version = "2.6.3" 307 | description = "Python LiveReload is an awesome tool for web developers" 308 | category = "main" 309 | optional = false 310 | python-versions = "*" 311 | files = [ 312 | {file = "livereload-2.6.3-py2.py3-none-any.whl", hash = "sha256:ad4ac6f53b2d62bb6ce1a5e6e96f1f00976a32348afedcb4b6d68df2a1d346e4"}, 313 | {file = "livereload-2.6.3.tar.gz", hash = "sha256:776f2f865e59fde56490a56bcc6773b6917366bce0c267c60ee8aaf1a0959869"}, 314 | ] 315 | 316 | [package.dependencies] 317 | six = "*" 318 | tornado = {version = "*", markers = "python_version > \"2.7\""} 319 | 320 | [[package]] 321 | name = "markdown-it-py" 322 | version = "2.1.0" 323 | description = "Python port of markdown-it. Markdown parsing, done right!" 324 | category = "main" 325 | optional = false 326 | python-versions = ">=3.7" 327 | files = [ 328 | {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, 329 | {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, 330 | ] 331 | 332 | [package.dependencies] 333 | mdurl = ">=0.1,<1.0" 334 | typing_extensions = {version = ">=3.7.4", markers = "python_version < \"3.8\""} 335 | 336 | [package.extras] 337 | benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] 338 | code-style = ["pre-commit (==2.6)"] 339 | compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] 340 | linkify = ["linkify-it-py (>=1.0,<2.0)"] 341 | plugins = ["mdit-py-plugins"] 342 | profiling = ["gprof2dot"] 343 | rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] 344 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 345 | 346 | [[package]] 347 | name = "markupsafe" 348 | version = "2.1.2" 349 | description = "Safely add untrusted strings to HTML/XML markup." 350 | category = "main" 351 | optional = false 352 | python-versions = ">=3.7" 353 | files = [ 354 | {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"}, 355 | {file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"}, 356 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"}, 357 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"}, 358 | {file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"}, 359 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"}, 360 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"}, 361 | {file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"}, 362 | {file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"}, 363 | {file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"}, 364 | {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"}, 365 | {file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"}, 366 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"}, 367 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"}, 368 | {file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"}, 369 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"}, 370 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"}, 371 | {file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"}, 372 | {file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"}, 373 | {file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"}, 374 | {file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"}, 375 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"}, 376 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"}, 377 | {file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"}, 378 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"}, 379 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"}, 380 | {file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"}, 381 | {file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"}, 382 | {file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"}, 383 | {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"}, 384 | {file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"}, 385 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"}, 386 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"}, 387 | {file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"}, 388 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"}, 389 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"}, 390 | {file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"}, 391 | {file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"}, 392 | {file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"}, 393 | {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"}, 394 | {file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"}, 395 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"}, 396 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"}, 397 | {file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"}, 398 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"}, 399 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"}, 400 | {file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"}, 401 | {file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"}, 402 | {file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"}, 403 | {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, 404 | ] 405 | 406 | [[package]] 407 | name = "mdit-py-plugins" 408 | version = "0.3.3" 409 | description = "Collection of plugins for markdown-it-py" 410 | category = "main" 411 | optional = false 412 | python-versions = ">=3.7" 413 | files = [ 414 | {file = "mdit-py-plugins-0.3.3.tar.gz", hash = "sha256:5cfd7e7ac582a594e23ba6546a2f406e94e42eb33ae596d0734781261c251260"}, 415 | {file = "mdit_py_plugins-0.3.3-py3-none-any.whl", hash = "sha256:36d08a29def19ec43acdcd8ba471d3ebab132e7879d442760d963f19913e04b9"}, 416 | ] 417 | 418 | [package.dependencies] 419 | markdown-it-py = ">=1.0.0,<3.0.0" 420 | 421 | [package.extras] 422 | code-style = ["pre-commit"] 423 | rtd = ["attrs", "myst-parser (>=0.16.1,<0.17.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"] 424 | testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] 425 | 426 | [[package]] 427 | name = "mdurl" 428 | version = "0.1.2" 429 | description = "Markdown URL utilities" 430 | category = "main" 431 | optional = false 432 | python-versions = ">=3.7" 433 | files = [ 434 | {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, 435 | {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, 436 | ] 437 | 438 | [[package]] 439 | name = "myst-parser" 440 | version = "0.18.1" 441 | description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." 442 | category = "main" 443 | optional = false 444 | python-versions = ">=3.7" 445 | files = [ 446 | {file = "myst-parser-0.18.1.tar.gz", hash = "sha256:79317f4bb2c13053dd6e64f9da1ba1da6cd9c40c8a430c447a7b146a594c246d"}, 447 | {file = "myst_parser-0.18.1-py3-none-any.whl", hash = "sha256:61b275b85d9f58aa327f370913ae1bec26ebad372cc99f3ab85c8ec3ee8d9fb8"}, 448 | ] 449 | 450 | [package.dependencies] 451 | docutils = ">=0.15,<0.20" 452 | jinja2 = "*" 453 | markdown-it-py = ">=1.0.0,<3.0.0" 454 | mdit-py-plugins = ">=0.3.1,<0.4.0" 455 | pyyaml = "*" 456 | sphinx = ">=4,<6" 457 | typing-extensions = "*" 458 | 459 | [package.extras] 460 | code-style = ["pre-commit (>=2.12,<3.0)"] 461 | linkify = ["linkify-it-py (>=1.0,<2.0)"] 462 | rtd = ["ipython", "sphinx-book-theme", "sphinx-design", "sphinxcontrib.mermaid (>=0.7.1,<0.8.0)", "sphinxext-opengraph (>=0.6.3,<0.7.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] 463 | testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx (<5.2)", "sphinx-pytest"] 464 | 465 | [[package]] 466 | name = "packaging" 467 | version = "23.0" 468 | description = "Core utilities for Python packages" 469 | category = "main" 470 | optional = false 471 | python-versions = ">=3.7" 472 | files = [ 473 | {file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"}, 474 | {file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"}, 475 | ] 476 | 477 | [[package]] 478 | name = "pygments" 479 | version = "2.14.0" 480 | description = "Pygments is a syntax highlighting package written in Python." 481 | category = "main" 482 | optional = false 483 | python-versions = ">=3.6" 484 | files = [ 485 | {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, 486 | {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, 487 | ] 488 | 489 | [package.extras] 490 | plugins = ["importlib-metadata"] 491 | 492 | [[package]] 493 | name = "pytz" 494 | version = "2022.7.1" 495 | description = "World timezone definitions, modern and historical" 496 | category = "main" 497 | optional = false 498 | python-versions = "*" 499 | files = [ 500 | {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"}, 501 | {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"}, 502 | ] 503 | 504 | [[package]] 505 | name = "pyyaml" 506 | version = "6.0" 507 | description = "YAML parser and emitter for Python" 508 | category = "main" 509 | optional = false 510 | python-versions = ">=3.6" 511 | files = [ 512 | {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, 513 | {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, 514 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, 515 | {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, 516 | {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"}, 517 | {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, 518 | {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, 519 | {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, 520 | {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, 521 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, 522 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, 523 | {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, 524 | {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, 525 | {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, 526 | {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, 527 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, 528 | {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, 529 | {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"}, 530 | {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, 531 | {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, 532 | {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, 533 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, 534 | {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, 535 | {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"}, 536 | {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, 537 | {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, 538 | {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, 539 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, 540 | {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, 541 | {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"}, 542 | {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, 543 | {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, 544 | {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, 545 | {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, 546 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, 547 | {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, 548 | {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"}, 549 | {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, 550 | {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, 551 | {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, 552 | ] 553 | 554 | [[package]] 555 | name = "requests" 556 | version = "2.28.2" 557 | description = "Python HTTP for Humans." 558 | category = "main" 559 | optional = false 560 | python-versions = ">=3.7, <4" 561 | files = [ 562 | {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"}, 563 | {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"}, 564 | ] 565 | 566 | [package.dependencies] 567 | certifi = ">=2017.4.17" 568 | charset-normalizer = ">=2,<4" 569 | idna = ">=2.5,<4" 570 | urllib3 = ">=1.21.1,<1.27" 571 | 572 | [package.extras] 573 | socks = ["PySocks (>=1.5.6,!=1.5.7)"] 574 | use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] 575 | 576 | [[package]] 577 | name = "six" 578 | version = "1.16.0" 579 | description = "Python 2 and 3 compatibility utilities" 580 | category = "main" 581 | optional = false 582 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 583 | files = [ 584 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 585 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 586 | ] 587 | 588 | [[package]] 589 | name = "snowballstemmer" 590 | version = "2.2.0" 591 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." 592 | category = "main" 593 | optional = false 594 | python-versions = "*" 595 | files = [ 596 | {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, 597 | {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, 598 | ] 599 | 600 | [[package]] 601 | name = "soupsieve" 602 | version = "2.3.2.post1" 603 | description = "A modern CSS selector implementation for Beautiful Soup." 604 | category = "main" 605 | optional = false 606 | python-versions = ">=3.6" 607 | files = [ 608 | {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, 609 | {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, 610 | ] 611 | 612 | [[package]] 613 | name = "sphinx" 614 | version = "5.3.0" 615 | description = "Python documentation generator" 616 | category = "main" 617 | optional = false 618 | python-versions = ">=3.6" 619 | files = [ 620 | {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, 621 | {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, 622 | ] 623 | 624 | [package.dependencies] 625 | alabaster = ">=0.7,<0.8" 626 | babel = ">=2.9" 627 | colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} 628 | docutils = ">=0.14,<0.20" 629 | imagesize = ">=1.3" 630 | importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} 631 | Jinja2 = ">=3.0" 632 | packaging = ">=21.0" 633 | Pygments = ">=2.12" 634 | requests = ">=2.5.0" 635 | snowballstemmer = ">=2.0" 636 | sphinxcontrib-applehelp = "*" 637 | sphinxcontrib-devhelp = "*" 638 | sphinxcontrib-htmlhelp = ">=2.0.0" 639 | sphinxcontrib-jsmath = "*" 640 | sphinxcontrib-qthelp = "*" 641 | sphinxcontrib-serializinghtml = ">=1.1.5" 642 | 643 | [package.extras] 644 | docs = ["sphinxcontrib-websupport"] 645 | lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] 646 | test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] 647 | 648 | [[package]] 649 | name = "sphinx-autobuild" 650 | version = "2021.3.14" 651 | description = "Rebuild Sphinx documentation on changes, with live-reload in the browser." 652 | category = "main" 653 | optional = false 654 | python-versions = ">=3.6" 655 | files = [ 656 | {file = "sphinx-autobuild-2021.3.14.tar.gz", hash = "sha256:de1ca3b66e271d2b5b5140c35034c89e47f263f2cd5db302c9217065f7443f05"}, 657 | {file = "sphinx_autobuild-2021.3.14-py3-none-any.whl", hash = "sha256:8fe8cbfdb75db04475232f05187c776f46f6e9e04cacf1e49ce81bdac649ccac"}, 658 | ] 659 | 660 | [package.dependencies] 661 | colorama = "*" 662 | livereload = "*" 663 | sphinx = "*" 664 | 665 | [package.extras] 666 | test = ["pytest", "pytest-cov"] 667 | 668 | [[package]] 669 | name = "sphinx-basic-ng" 670 | version = "1.0.0b1" 671 | description = "A modern skeleton for Sphinx themes." 672 | category = "main" 673 | optional = false 674 | python-versions = ">=3.7" 675 | files = [ 676 | {file = "sphinx_basic_ng-1.0.0b1-py3-none-any.whl", hash = "sha256:ade597a3029c7865b24ad0eda88318766bcc2f9f4cef60df7e28126fde94db2a"}, 677 | {file = "sphinx_basic_ng-1.0.0b1.tar.gz", hash = "sha256:89374bd3ccd9452a301786781e28c8718e99960f2d4f411845ea75fc7bb5a9b0"}, 678 | ] 679 | 680 | [package.dependencies] 681 | sphinx = ">=4.0" 682 | 683 | [package.extras] 684 | docs = ["furo", "ipython", "myst-parser", "sphinx-copybutton", "sphinx-inline-tabs"] 685 | 686 | [[package]] 687 | name = "sphinx-copybutton" 688 | version = "0.5.1" 689 | description = "Add a copy button to each of your code cells." 690 | category = "main" 691 | optional = false 692 | python-versions = ">=3.7" 693 | files = [ 694 | {file = "sphinx-copybutton-0.5.1.tar.gz", hash = "sha256:366251e28a6f6041514bfb5439425210418d6c750e98d3a695b73e56866a677a"}, 695 | {file = "sphinx_copybutton-0.5.1-py3-none-any.whl", hash = "sha256:0842851b5955087a7ec7fc870b622cb168618ad408dee42692e9a5c97d071da8"}, 696 | ] 697 | 698 | [package.dependencies] 699 | sphinx = ">=1.8" 700 | 701 | [package.extras] 702 | code-style = ["pre-commit (==2.12.1)"] 703 | rtd = ["ipython", "myst-nb", "sphinx", "sphinx-book-theme", "sphinx-examples"] 704 | 705 | [[package]] 706 | name = "sphinxcontrib-applehelp" 707 | version = "1.0.2" 708 | description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" 709 | category = "main" 710 | optional = false 711 | python-versions = ">=3.5" 712 | files = [ 713 | {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, 714 | {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, 715 | ] 716 | 717 | [package.extras] 718 | lint = ["docutils-stubs", "flake8", "mypy"] 719 | test = ["pytest"] 720 | 721 | [[package]] 722 | name = "sphinxcontrib-devhelp" 723 | version = "1.0.2" 724 | description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." 725 | category = "main" 726 | optional = false 727 | python-versions = ">=3.5" 728 | files = [ 729 | {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, 730 | {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, 731 | ] 732 | 733 | [package.extras] 734 | lint = ["docutils-stubs", "flake8", "mypy"] 735 | test = ["pytest"] 736 | 737 | [[package]] 738 | name = "sphinxcontrib-htmlhelp" 739 | version = "2.0.0" 740 | description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" 741 | category = "main" 742 | optional = false 743 | python-versions = ">=3.6" 744 | files = [ 745 | {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, 746 | {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, 747 | ] 748 | 749 | [package.extras] 750 | lint = ["docutils-stubs", "flake8", "mypy"] 751 | test = ["html5lib", "pytest"] 752 | 753 | [[package]] 754 | name = "sphinxcontrib-jsmath" 755 | version = "1.0.1" 756 | description = "A sphinx extension which renders display math in HTML via JavaScript" 757 | category = "main" 758 | optional = false 759 | python-versions = ">=3.5" 760 | files = [ 761 | {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, 762 | {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, 763 | ] 764 | 765 | [package.extras] 766 | test = ["flake8", "mypy", "pytest"] 767 | 768 | [[package]] 769 | name = "sphinxcontrib-qthelp" 770 | version = "1.0.3" 771 | description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." 772 | category = "main" 773 | optional = false 774 | python-versions = ">=3.5" 775 | files = [ 776 | {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, 777 | {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, 778 | ] 779 | 780 | [package.extras] 781 | lint = ["docutils-stubs", "flake8", "mypy"] 782 | test = ["pytest"] 783 | 784 | [[package]] 785 | name = "sphinxcontrib-serializinghtml" 786 | version = "1.1.5" 787 | description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." 788 | category = "main" 789 | optional = false 790 | python-versions = ">=3.5" 791 | files = [ 792 | {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, 793 | {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, 794 | ] 795 | 796 | [package.extras] 797 | lint = ["docutils-stubs", "flake8", "mypy"] 798 | test = ["pytest"] 799 | 800 | [[package]] 801 | name = "toml" 802 | version = "0.10.2" 803 | description = "Python Library for Tom's Obvious, Minimal Language" 804 | category = "main" 805 | optional = false 806 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 807 | files = [ 808 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 809 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 810 | ] 811 | 812 | [[package]] 813 | name = "tornado" 814 | version = "6.2" 815 | description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." 816 | category = "main" 817 | optional = false 818 | python-versions = ">= 3.7" 819 | files = [ 820 | {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, 821 | {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, 822 | {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, 823 | {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, 824 | {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, 825 | {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, 826 | {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, 827 | {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, 828 | {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, 829 | {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, 830 | {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, 831 | ] 832 | 833 | [[package]] 834 | name = "typing-extensions" 835 | version = "4.4.0" 836 | description = "Backported and Experimental Type Hints for Python 3.7+" 837 | category = "main" 838 | optional = false 839 | python-versions = ">=3.7" 840 | files = [ 841 | {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, 842 | {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, 843 | ] 844 | 845 | [[package]] 846 | name = "uc-micro-py" 847 | version = "1.0.1" 848 | description = "Micro subset of unicode data files for linkify-it-py projects." 849 | category = "main" 850 | optional = false 851 | python-versions = ">=3.6" 852 | files = [ 853 | {file = "uc-micro-py-1.0.1.tar.gz", hash = "sha256:b7cdf4ea79433043ddfe2c82210208f26f7962c0cfbe3bacb05ee879a7fdb596"}, 854 | {file = "uc_micro_py-1.0.1-py3-none-any.whl", hash = "sha256:316cfb8b6862a0f1d03540f0ae6e7b033ff1fa0ddbe60c12cbe0d4cec846a69f"}, 855 | ] 856 | 857 | [package.extras] 858 | test = ["coverage", "pytest", "pytest-cov"] 859 | 860 | [[package]] 861 | name = "urllib3" 862 | version = "1.26.14" 863 | description = "HTTP library with thread-safe connection pooling, file post, and more." 864 | category = "main" 865 | optional = false 866 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 867 | files = [ 868 | {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"}, 869 | {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"}, 870 | ] 871 | 872 | [package.extras] 873 | brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] 874 | secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] 875 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 876 | 877 | [[package]] 878 | name = "zipp" 879 | version = "3.11.0" 880 | description = "Backport of pathlib-compatible object wrapper for zip files" 881 | category = "main" 882 | optional = false 883 | python-versions = ">=3.7" 884 | files = [ 885 | {file = "zipp-3.11.0-py3-none-any.whl", hash = "sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa"}, 886 | {file = "zipp-3.11.0.tar.gz", hash = "sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766"}, 887 | ] 888 | 889 | [package.extras] 890 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] 891 | testing = ["flake8 (<5)", "func-timeout", "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)"] 892 | 893 | [metadata] 894 | lock-version = "2.0" 895 | python-versions = ">=3.7,<4.0" 896 | content-hash = "7814d077cdba9fc7757c50401530273d2affa5cd80f1af5e4dfbf01e973106e9" 897 | --------------------------------------------------------------------------------