Compare commits

...

19 Commits

Author SHA1 Message Date
Martin
999897df12 The all-new, pyscript.web (ignore the branch name :) ) (#2129)
* Minor cleanups: move all Element classes to bottom of module.

* Commenting.

* Commenting.

* Commenting.

* Group dunder methods.

* Don't cache the element's parent.

* Remove style type check until we decide whether or not to add for classes too.

* Add ability to register/unregister element classes.

* Implement __iter__ for container elements.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Minor renaming to make it clear when we have an Element instance vs an actual DOM element.

* remove duplication: added Element.get_tag_name

* Commenting.

* Allow Element.append to 1) use *args, 2) accept iterables

* Remove iterable check - inteferes with js proxies.

* Don't use *args, so it quacks more like a list ;)

* Element.append take 2 :)

* Remove unused code.

* Move to web.py with a page object!

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Added 'page.title' too :)

* Add __getitem__ as a shortcut for page.find

* Add Element.__getitem__ to be consistent

* Make __getitem__ consistent for Page, Element and ElementCollection.

* Docstringing.

* Docstringing.

* Docstringing/commenting.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* fix select.add (revert InnerHTML->html)

* Commenting.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Hand-edit some of the AI :)

* Rename ElementCollection.children -> ElementCollection.elements

* Remove unnecessary guard.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-08-01 10:36:57 +01:00
Andrea Giammarchi
d47fb58ede Update Pyodide to its 0.26.2 version (#2133) 2024-07-31 20:34:21 +02:00
Andrea Giammarchi
f316341e73 Updated Polyscript and added Panel worker test (#2130)
* Updated Polyscript and added Panel worker test

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-31 14:31:35 +02:00
Andrea Giammarchi
8c46fcabf7 Updating polyscript to its latest (#2128)
* Updating polyscript to its latest
2024-07-29 16:59:31 +02:00
Martin
e4ff4d8fab Controversial version where Element just delegates to the underlying DOM element. (#2127)
* Update elements.py

* remove grid which allows simpler class tag mapping.
2024-07-24 13:27:12 -05:00
Martin
f20a0003ed fix: broken methods video.snap and canvas.download (#2126)
* fix: broken methods video.snap and canvas.download

* Allow canvas.draw to use actual image width/height.
2024-07-23 15:35:07 -05:00
Martin
6c938dfe3b Override __getattr__ and __setattr__ on ElementCollection. (#2116)
* Override __getattr__ and __setattr__ on ElementCollection.

* fix: bug when using a string to query an ElementCollection.

* Use Element.find when indexing ElementCollection with a string.

* For consistency also have a find method on ElementCollection.

* ElementCollection.find now returns a collection of collections :)

* fix tests: for textContent

* Revert to extend for ElementCollection.find :)

* Make element_from_dom a classmethod Element.from_dom

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* rename: Element.from_dom -> Element.from_dom_element

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* PyCharm warning sweep.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Workaround for mp not allowing setting via __dict__

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-07-22 10:32:37 -05:00
Andrea Giammarchi
d884586a82 Updated Polyscript to its latest (#2124) 2024-07-19 12:21:04 +02:00
Fabio Pliger
f8f7ba89c1 Cleanup pyscript web elements (#2094)
* change pydom example to use new pyscript.web namespace

* change tests to use new pyscript.web namespace

* create new pyscript.web package and move pydom to pyscript.web.dom

* add __init__ to pyscript.web and expose the dom instance instead of the pyscript.web.dom module

* move elements from pyweb.ui to pyscript.web and temp fix pydom import

* moved of elements file completed

* moved media from pyweb to pyscript.web

* RIP pyweb

* move JSProperty from pyscript.web.dom to pyscript.web.elements

* move element classes from pyscript.web.dom to pyscript.web.elements

* first round of fixes while running tests

* fix test typo

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* restore right type type returned for Element.parent. ALL TESTS PASS LOCALLY NOW

* lint

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* clean up dom.py from dead commented code and osbolete comments

* bugfix: dom shouldn't return None when it can't find any element for a specific selector so it now returns an empty collection

* additional cleanup in tests

* lint

* initial cleaning up of unused modules

* change element.append to not consider types anymore and add tests for appending elements.Element or a JsProxy object

* add Element.append tests for append JS elements directly and appending nodeList as well

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Tag and create the correct subclass of Element.

* Move: Element.snap -> video.snap

* Move: Element.download and draw to canvas.download and draw.

* Minor cleanups.

* Commenting.

* Allow css classes to be passed to Element constructor.

* Commenting.

* Typo fix.

* Make html, id and text JSProperties.

* Commenting.

* Remove unnecessary selected attribute on BaseElement.

* Extract: BaseElement.from_js -> element_from_js

* Pass *args and **kwargs to element_from_js and remove BaseElement.create

* Move value attribute to specific Element subclasses.

* fix: wrapping of existing js elements.

* Add body and head elements so parent and children work everywhere.

* Revert order of HasOptions mixin for the select element.

* Comment out tests that are no longer relevant (see comment).

* Use correct super args in mixin.

* Have to use element_from_js when returning options from OptionsProxy.

* rename: StyleProxy -> Style, OptionsProxy -> Options and added Classes.

* Remove cached_property.

* Remove list-y methods from Classes collection.

* Allow explicit children or *args for containers.

* controversial: fix tests to use find rather than dom

* Add html element so (say) body.parent does what is expected.

* Collapse Element class hierarchy.

* rename: js_element -> dom_element

* rename: element_from_js -> element_from_dom

* replace: JS with DOM

* rename: _js -> _dom_element

* fix dom tests.

* Complete element list with void elements derived from Element.

* Added attributes to the newly added Element subclasses.

* remove dom module, replace with instance.

Also, remove media :)

* fix: typo in test for 'b' element.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Remove dom and media modules.

* fix up ts definitions.

* Added missing import (used in content property).

* Added TODO :)

* wip: Add ClassesCollection

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Attempt to ask black to leave class list alone.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Add classes attribute to ElementCollection

* wip: work on classes collection

* Extract code to get set of all class names in ClassesCollection.

* Update elements.py

* Polishing.

* Make children return an ElementCollection

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* wip: Add the ability to set multiple properties.

* Add __getitem__ back to the dom object.

* Put validation when setting DOM properties back in.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* All tests green.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Remove unnecessary comment.

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Martin <martin.chilvers@gmail.com>
2024-07-03 14:21:23 -07:00
Andrea Giammarchi
67d47511d5 Fix MicroPython terminal input when no REPL is used/needed (#2113)
* Fix terminal input when no REPL is used/needed
* Fix input backspace too
2024-07-03 13:03:31 +02:00
Andrea Giammarchi
6f49f18937 Updated Polyscript with its workers feature (#2104)
* Updated Polyscript with its workers feature
* Worked around the inconsistent behavior between Pyodide and MicroPython
* Fixed Pyodide greedy access to undesired Proxy fields
2024-06-26 14:01:22 +02:00
Andrea Giammarchi
7b8ef7ebe2 Fix #2109 - Allow inline JSON config attribute in PyEditor (#2110)
Fix #2109 - Allow inline JSON config attribute in PyEditor
2024-06-24 17:04:28 +02:00
Andrea Giammarchi
461ae38763 Updated reference code to grab latest (#2107) 2024-06-21 16:06:18 +02:00
Andrea Giammarchi
4b90ebdef5 Bring back pyweb as it was (#2105) 2024-06-21 14:49:20 +02:00
Andrea Giammarchi
15c19aa708 Updated Polyscript with latest MicroPython (#2103) 2024-06-19 17:56:22 +02:00
Andrea Giammarchi
d0406be84c A persistent IndexedDB store for PyScript (#2101)
A persistent IndexedDB store for PyScript
2024-06-19 14:11:57 +02:00
Andrea Giammarchi
aab015b9b8 Better py editor indentation (#2098)
Better PyEditor Indentation
2024-06-13 11:34:14 +02:00
Andrea Giammarchi
a1e5a05b49 PyEditor cumulative fixes & improvements (#2095)
* PyEditor fixes

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* PyEditor cumulative fixes & improvements

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-06-12 18:55:36 +02:00
Fabio Pliger
f1a787e031 move pydom and elements from pyweb to pyscript.web (#2092)
* change pydom example to use new pyscript.web namespace

* change tests to use new pyscript.web namespace

* create new pyscript.web package and move pydom to pyscript.web.dom

* add __init__ to pyscript.web and expose the dom instance instead of the pyscript.web.dom module

* move elements from pyweb.ui to pyscript.web and temp fix pydom import

* moved of elements file completed

* moved media from pyweb to pyscript.web

* RIP pyweb

* move JSProperty from pyscript.web.dom to pyscript.web.elements

* move element classes from pyscript.web.dom to pyscript.web.elements

* first round of fixes while running tests

* fix test typo

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* restore right type type returned for Element.parent. ALL TESTS PASS LOCALLY NOW

* lint

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* clean up dom.py from dead commented code and osbolete comments

* bugfix: dom shouldn't return None when it can't find any element for a specific selector so it now returns an empty collection

* additional cleanup in tests

* lint

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-06-06 15:42:14 +02:00
50 changed files with 2847 additions and 2149 deletions

View File

@@ -38,11 +38,11 @@ To try PyScript, import the appropriate pyscript files into the `<head>` tag of
<head> <head>
<link <link
rel="stylesheet" rel="stylesheet"
href="https://pyscript.net/releases/2024.5.2/core.css" href="https://pyscript.net/releases/2024.6.2/core.css"
/> />
<script <script
type="module" type="module"
src="https://pyscript.net/releases/2024.5.2/core.js" src="https://pyscript.net/releases/2024.6.2/core.js"
></script> ></script>
</head> </head>
<body> <body>

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@pyscript/core", "name": "@pyscript/core",
"version": "0.4.42", "version": "0.5.1",
"type": "module", "type": "module",
"description": "PyScript", "description": "PyScript",
"module": "./index.js", "module": "./index.js",
@@ -20,8 +20,9 @@
}, },
"scripts": { "scripts": {
"server": "npx static-handler --coi .", "server": "npx static-handler --coi .",
"build": "export ESLINT_USE_FLAT_CONFIG=true;npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && eslint src/ && npm run ts && npm run test:mpy", "build": "export ESLINT_USE_FLAT_CONFIG=true;npm run build:3rd-party && npm run build:stdlib && npm run build:plugins && npm run build:core && if [ -z \"$NO_MIN\" ]; then eslint src/ && npm run ts && npm run test:mpy; fi",
"build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/", "build:core": "rm -rf dist && rollup --config rollup/core.config.js && cp src/3rd-party/*.css dist/",
"build:flatted": "node rollup/flatted.cjs",
"build:plugins": "node rollup/plugins.cjs", "build:plugins": "node rollup/plugins.cjs",
"build:stdlib": "node rollup/stdlib.cjs", "build:stdlib": "node rollup/stdlib.cjs",
"build:3rd-party": "node rollup/3rd-party.cjs", "build:3rd-party": "node rollup/3rd-party.cjs",
@@ -43,7 +44,7 @@
"dependencies": { "dependencies": {
"@ungap/with-resolvers": "^0.1.0", "@ungap/with-resolvers": "^0.1.0",
"basic-devtools": "^0.1.6", "basic-devtools": "^0.1.6",
"polyscript": "^0.12.14", "polyscript": "^0.14.4",
"sticky-module": "^0.1.1", "sticky-module": "^0.1.1",
"to-json-callback": "^0.1.1", "to-json-callback": "^0.1.1",
"type-checked-collections": "^0.1.7" "type-checked-collections": "^0.1.7"
@@ -53,23 +54,24 @@
"@codemirror/lang-python": "^6.1.6", "@codemirror/lang-python": "^6.1.6",
"@codemirror/language": "^6.10.2", "@codemirror/language": "^6.10.2",
"@codemirror/state": "^6.4.1", "@codemirror/state": "^6.4.1",
"@codemirror/view": "^6.27.0", "@codemirror/view": "^6.29.1",
"@playwright/test": "^1.44.1", "@playwright/test": "^1.45.3",
"@rollup/plugin-commonjs": "^25.0.8", "@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@webreflection/toml-j0.4": "^1.1.3", "@webreflection/toml-j0.4": "^1.1.3",
"@xterm/addon-fit": "^0.10.0", "@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0", "@xterm/addon-web-links": "^0.11.0",
"bun": "^1.1.12", "bun": "^1.1.21",
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"eslint": "^9.4.0", "eslint": "^9.8.0",
"rollup": "^4.18.0", "flatted": "^3.3.1",
"rollup": "^4.19.1",
"rollup-plugin-postcss": "^4.0.2", "rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-string": "^3.0.0", "rollup-plugin-string": "^3.0.0",
"static-handler": "^0.4.3", "static-handler": "^0.4.3",
"typescript": "^5.4.5", "typescript": "^5.5.4",
"xterm": "^5.3.0", "xterm": "^5.3.0",
"xterm-readline": "^1.1.1" "xterm-readline": "^1.1.1"
}, },

View File

@@ -0,0 +1,17 @@
const { writeFileSync, readFileSync } = require("node:fs");
const { join } = require("node:path");
const flatted = "# https://www.npmjs.com/package/flatted\n\n";
const source = join(
__dirname,
"..",
"node_modules",
"flatted",
"python",
"flatted.py",
);
const dest = join(__dirname, "..", "src", "stdlib", "pyscript", "flatted.py");
const clear = (str) => String(str).replace(/^#.*/gm, "").trimStart();
writeFileSync(dest, flatted + clear(readFileSync(source)));

View File

@@ -45,6 +45,8 @@ const configDetails = async (config, type) => {
const conflictError = (reason) => new Error(`(${CONFLICTING_CODE}): ${reason}`); const conflictError = (reason) => new Error(`(${CONFLICTING_CODE}): ${reason}`);
const relative_url = (url, base = location.href) => new URL(url, base).href;
const syntaxError = (type, url, { message }) => { const syntaxError = (type, url, { message }) => {
let str = `(${BAD_CONFIG}): Invalid ${type}`; let str = `(${BAD_CONFIG}): Invalid ${type}`;
if (url) str += ` @ ${url}`; if (url) str += ` @ ${url}`;
@@ -108,7 +110,7 @@ for (const [TYPE] of TYPES) {
if (!error && config) { if (!error && config) {
try { try {
const { json, toml, text, url } = await configDetails(config, type); const { json, toml, text, url } = await configDetails(config, type);
if (url) configURL = new URL(url, location.href).href; if (url) configURL = relative_url(url);
config = text; config = text;
if (json || type === "json") { if (json || type === "json") {
try { try {
@@ -153,4 +155,4 @@ for (const [TYPE] of TYPES) {
configs.set(TYPE, { config: parsed, configURL, plugins, error }); configs.set(TYPE, { config: parsed, configURL, plugins, error });
} }
export default configs; export { configs, relative_url };

View File

@@ -19,7 +19,7 @@ import {
import "./all-done.js"; import "./all-done.js";
import TYPES from "./types.js"; import TYPES from "./types.js";
import configs from "./config.js"; import { configs, relative_url } from "./config.js";
import sync from "./sync.js"; import sync from "./sync.js";
import bootstrapNodeAndPlugins from "./plugins-helper.js"; import bootstrapNodeAndPlugins from "./plugins-helper.js";
import { ErrorCode } from "./exceptions.js"; import { ErrorCode } from "./exceptions.js";
@@ -84,6 +84,7 @@ const [
export { export {
TYPES, TYPES,
relative_url,
exportedPyWorker as PyWorker, exportedPyWorker as PyWorker,
exportedMPWorker as MPWorker, exportedMPWorker as MPWorker,
exportedHooks as hooks, exportedHooks as hooks,
@@ -92,7 +93,7 @@ export {
}; };
export const offline_interpreter = (config) => export const offline_interpreter = (config) =>
config?.interpreter && new URL(config.interpreter, location.href).href; config?.interpreter && relative_url(config.interpreter);
const hooked = new Map(); const hooked = new Map();

View File

@@ -1,6 +1,6 @@
// PyScript py-editor plugin // PyScript py-editor plugin
import { Hook, XWorker, dedent, defineProperties } from "polyscript/exports"; import { Hook, XWorker, dedent, defineProperties } from "polyscript/exports";
import { TYPES, offline_interpreter, stdlib } from "../core.js"; import { TYPES, offline_interpreter, relative_url, stdlib } from "../core.js";
const RUN_BUTTON = `<svg style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>`; const RUN_BUTTON = `<svg style="height:20px;width:20px;vertical-align:-.125em;transform-origin:center;overflow:visible;color:green" viewBox="0 0 384 512" aria-hidden="true" role="img" xmlns="http://www.w3.org/2000/svg"><g transform="translate(192 256)" transform-origin="96 0"><g transform="translate(0,0) scale(1,1)"><path d="M361 215C375.3 223.8 384 239.3 384 256C384 272.7 375.3 288.2 361 296.1L73.03 472.1C58.21 482 39.66 482.4 24.52 473.9C9.377 465.4 0 449.4 0 432V80C0 62.64 9.377 46.63 24.52 38.13C39.66 29.64 58.21 29.99 73.03 39.04L361 215z" fill="currentColor" transform="translate(-192 -256)"></path></g></g></svg>`;
@@ -34,14 +34,25 @@ async function execute({ currentTarget }) {
if (!envs.has(env)) { if (!envs.has(env)) {
const srcLink = URL.createObjectURL(new Blob([""])); const srcLink = URL.createObjectURL(new Blob([""]));
const details = { type: this.interpreter }; const details = {
type: this.interpreter,
serviceWorker: this.serviceWorker,
};
const { config } = this; const { config } = this;
if (config) { if (config) {
details.configURL = config; details.configURL = relative_url(config);
const { parse } = config.endsWith(".toml") if (config.endsWith(".toml")) {
? await import(/* webpackIgnore: true */ "../3rd-party/toml.js") const [{ parse }, toml] = await Promise.all([
: JSON; import(/* webpackIgnore: true */ "../3rd-party/toml.js"),
details.config = parse(await fetch(config).then((r) => r.text())); fetch(config).then((r) => r.text()),
]);
details.config = parse(toml);
} else if (config.endsWith(".json")) {
details.config = await fetch(config).then((r) => r.json());
} else {
details.configURL = relative_url("./config.txt");
details.config = JSON.parse(config);
}
details.version = offline_interpreter(details.config); details.version = offline_interpreter(details.config);
} else { } else {
details.config = {}; details.config = {};
@@ -86,21 +97,24 @@ async function execute({ currentTarget }) {
}); });
} }
const makeRunButton = (listener, type) => { const makeRunButton = (handler, type) => {
const runButton = document.createElement("button"); const runButton = document.createElement("button");
runButton.className = `absolute ${type}-editor-run-button`; runButton.className = `absolute ${type}-editor-run-button`;
runButton.innerHTML = RUN_BUTTON; runButton.innerHTML = RUN_BUTTON;
runButton.setAttribute("aria-label", "Python Script Run Button"); runButton.setAttribute("aria-label", "Python Script Run Button");
runButton.addEventListener("click", listener); runButton.addEventListener("click", async (event) => {
runButton.blur();
await handler.handleEvent(event);
});
return runButton; return runButton;
}; };
const makeEditorDiv = (listener, type) => { const makeEditorDiv = (handler, type) => {
const editorDiv = document.createElement("div"); const editorDiv = document.createElement("div");
editorDiv.className = `${type}-editor-input`; editorDiv.className = `${type}-editor-input`;
editorDiv.setAttribute("aria-label", "Python Script Area"); editorDiv.setAttribute("aria-label", "Python Script Area");
const runButton = makeRunButton(listener, type); const runButton = makeRunButton(handler, type);
const editorShadowContainer = document.createElement("div"); const editorShadowContainer = document.createElement("div");
// avoid outer elements intercepting key events (reveal as example) // avoid outer elements intercepting key events (reveal as example)
@@ -120,15 +134,15 @@ const makeOutDiv = (type) => {
return outDiv; return outDiv;
}; };
const makeBoxDiv = (listener, type) => { const makeBoxDiv = (handler, type) => {
const boxDiv = document.createElement("div"); const boxDiv = document.createElement("div");
boxDiv.className = `${type}-editor-box`; boxDiv.className = `${type}-editor-box`;
const editorDiv = makeEditorDiv(listener, type); const editorDiv = makeEditorDiv(handler, type);
const outDiv = makeOutDiv(type); const outDiv = makeOutDiv(type);
boxDiv.append(editorDiv, outDiv); boxDiv.append(editorDiv, outDiv);
return [boxDiv, outDiv]; return [boxDiv, outDiv, editorDiv.querySelector("button")];
}; };
const init = async (script, type, interpreter) => { const init = async (script, type, interpreter) => {
@@ -138,7 +152,7 @@ const init = async (script, type, interpreter) => {
{ python }, { python },
{ indentUnit }, { indentUnit },
{ keymap }, { keymap },
{ defaultKeymap }, { defaultKeymap, indentWithTab },
] = await Promise.all([ ] = await Promise.all([
import(/* webpackIgnore: true */ "../3rd-party/codemirror.js"), import(/* webpackIgnore: true */ "../3rd-party/codemirror.js"),
import(/* webpackIgnore: true */ "../3rd-party/codemirror_state.js"), import(/* webpackIgnore: true */ "../3rd-party/codemirror_state.js"),
@@ -152,8 +166,17 @@ const init = async (script, type, interpreter) => {
let isSetup = script.hasAttribute("setup"); let isSetup = script.hasAttribute("setup");
const hasConfig = script.hasAttribute("config"); const hasConfig = script.hasAttribute("config");
const serviceWorker = script.getAttribute("service-worker");
const env = `${interpreter}-${script.getAttribute("env") || getID(type)}`; const env = `${interpreter}-${script.getAttribute("env") || getID(type)}`;
// helps preventing too lazy ServiceWorker initialization on button run
if (serviceWorker) {
new XWorker("data:application/javascript,postMessage(0)", {
type: "dummy",
serviceWorker,
}).onmessage = ({ target }) => target.terminate();
}
if (hasConfig && configs.has(env)) { if (hasConfig && configs.has(env)) {
throw new SyntaxError( throw new SyntaxError(
configs.get(env) configs.get(env)
@@ -168,11 +191,12 @@ const init = async (script, type, interpreter) => {
? await fetch(script.src).then((b) => b.text()) ? await fetch(script.src).then((b) => b.text())
: script.textContent; : script.textContent;
const context = { const context = {
// allow the listener to be overridden at distance
handleEvent: execute,
serviceWorker,
interpreter, interpreter,
env, env,
config: config: hasConfig && script.getAttribute("config"),
hasConfig &&
new URL(script.getAttribute("config"), location.href).href,
get pySrc() { get pySrc() {
return isSetup ? source : editor.state.doc.toString(); return isSetup ? source : editor.state.doc.toString();
}, },
@@ -184,6 +208,29 @@ const init = async (script, type, interpreter) => {
let target; let target;
defineProperties(script, { defineProperties(script, {
target: { get: () => target }, target: { get: () => target },
handleEvent: {
get: () => context.handleEvent,
set: (callback) => {
// do not bother with logic if it was set back as its original handler
if (callback === execute) context.handleEvent = execute;
// in every other case be sure that if the listener override returned
// `false` nothing happens, otherwise keep doing what it always did
else {
context.handleEvent = async (event) => {
// trap the currentTarget ASAP (if any)
// otherwise it gets lost asynchronously
const { currentTarget } = event;
// augment a code snapshot before invoking the override
defineProperties(event, {
code: { value: context.pySrc },
});
// avoid executing the default handler if the override returned `false`
if ((await callback(event)) !== false)
await execute.call(context, { currentTarget });
};
}
},
},
code: { code: {
get: () => context.pySrc, get: () => context.pySrc,
set: (insert) => { set: (insert) => {
@@ -214,8 +261,8 @@ const init = async (script, type, interpreter) => {
isSetup = wasSetup; isSetup = wasSetup;
source = wasSource; source = wasSource;
}; };
return execute return context
.call(context, { currentTarget: null }) .handleEvent({ currentTarget: null })
.then(restore, restore); .then(restore, restore);
}, },
}, },
@@ -227,7 +274,7 @@ const init = async (script, type, interpreter) => {
}; };
if (isSetup) { if (isSetup) {
await execute.call(context, { currentTarget: null }); await context.handleEvent({ currentTarget: null });
notify(); notify();
return; return;
} }
@@ -250,8 +297,7 @@ const init = async (script, type, interpreter) => {
if (!target.hasAttribute("root")) target.setAttribute("root", target.id); if (!target.hasAttribute("root")) target.setAttribute("root", target.id);
// @see https://github.com/JeffersGlass/mkdocs-pyscript/blob/main/mkdocs_pyscript/js/makeblocks.js // @see https://github.com/JeffersGlass/mkdocs-pyscript/blob/main/mkdocs_pyscript/js/makeblocks.js
const listener = execute.bind(context); const [boxDiv, outDiv, runButton] = makeBoxDiv(context, type);
const [boxDiv, outDiv] = makeBoxDiv(listener, type);
boxDiv.dataset.env = script.hasAttribute("env") ? env : interpreter; boxDiv.dataset.env = script.hasAttribute("env") ? env : interpreter;
const inputChild = boxDiv.querySelector(`.${type}-editor-input > div`); const inputChild = boxDiv.querySelector(`.${type}-editor-input > div`);
@@ -264,8 +310,9 @@ const init = async (script, type, interpreter) => {
const doc = dedent(script.textContent).trim(); const doc = dedent(script.textContent).trim();
// preserve user indentation, if any // preserve user indentation, if any
const indentation = /^(\s+)/m.test(doc) ? RegExp.$1 : " "; const indentation = /^([ \t]+)/m.test(doc) ? RegExp.$1 : " ";
const listener = () => runButton.click();
const editor = new EditorView({ const editor = new EditorView({
extensions: [ extensions: [
indentUnit.of(indentation), indentUnit.of(indentation),
@@ -275,9 +322,13 @@ const init = async (script, type, interpreter) => {
{ key: "Ctrl-Enter", run: listener, preventDefault: true }, { key: "Ctrl-Enter", run: listener, preventDefault: true },
{ key: "Cmd-Enter", run: listener, preventDefault: true }, { key: "Cmd-Enter", run: listener, preventDefault: true },
{ key: "Shift-Enter", run: listener, preventDefault: true }, { key: "Shift-Enter", run: listener, preventDefault: true },
// @see https://codemirror.net/examples/tab/
indentWithTab,
]), ]),
basicSetup, basicSetup,
], ],
foldGutter: true,
gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
parent, parent,
doc, doc,
}); });

View File

@@ -1,5 +1,5 @@
// PyScript py-terminal plugin // PyScript py-terminal plugin
import { TYPES } from "../core.js"; import { TYPES, relative_url } from "../core.js";
import { notify } from "./error.js"; import { notify } from "./error.js";
import { customObserver } from "polyscript/exports"; import { customObserver } from "polyscript/exports";
@@ -35,7 +35,7 @@ for (const type of TYPES.keys()) {
document.head.append( document.head.append(
Object.assign(document.createElement("link"), { Object.assign(document.createElement("link"), {
rel: "stylesheet", rel: "stylesheet",
href: new URL("./xterm.css", import.meta.url), href: relative_url("./xterm.css", import.meta.url),
}), }),
); );
} }

View File

@@ -49,11 +49,12 @@ const workerReady = ({ interpreter, io, run, type }, { sync }) => {
const writer = encoder.writable.getWriter(); const writer = encoder.writable.getWriter();
sync.pyterminal_stream_write = (buffer) => writer.write(buffer); sync.pyterminal_stream_write = (buffer) => writer.write(buffer);
pyterminal_ready();
interpreter.replInit(); interpreter.replInit();
}, },
}); });
pyterminal_ready();
}; };
export default async (element) => { export default async (element) => {
@@ -163,13 +164,25 @@ export default async (element) => {
}; };
terminal.onData((buffer) => { terminal.onData((buffer) => {
if (promisedChunks) { if (promisedChunks) {
readChunks += buffer; // handle backspace on input
terminal.write(buffer); if (buffer === "\x7f") {
if (readChunks.endsWith("\r")) { // avoid over-greedy backspace
terminal.write("\n"); if (readChunks.length) {
promisedChunks.resolve(readChunks.slice(0, -1)); readChunks = readChunks.slice(0, -1);
promisedChunks = null; // override previous char position
readChunks = ""; // put an empty space to clear the char
// move back position again
buffer = "\b \b";
} else buffer = "";
} else readChunks += buffer;
if (buffer) {
terminal.write(buffer);
if (readChunks.endsWith("\r")) {
terminal.write("\n");
promisedChunks.resolve(readChunks.slice(0, -1));
promisedChunks = null;
readChunks = "";
}
} }
} else { } else {
stream.write(buffer); stream.write(buffer);

View File

@@ -43,8 +43,12 @@ from pyscript.magic_js import (
sync, sync,
window, window,
) )
from pyscript.storage import Storage, storage
from pyscript.websocket import WebSocket from pyscript.websocket import WebSocket
if not RUNNING_IN_WORKER:
from pyscript.workers import create_named_worker, workers
try: try:
from pyscript.event_handling import when from pyscript.event_handling import when
except: except:

View File

@@ -19,22 +19,23 @@ def when(event_type=None, selector=None):
""" """
def decorator(func): def decorator(func):
from pyscript.web import Element, ElementCollection
if isinstance(selector, str): if isinstance(selector, str):
elements = document.querySelectorAll(selector) elements = document.querySelectorAll(selector)
# TODO: This is a hack that will be removed when pyscript becomes a package
# and we can better manage the imports without circular dependencies
elif isinstance(selector, Element):
elements = [selector._dom_element]
elif isinstance(selector, ElementCollection):
elements = [el._dom_element for el in selector]
else: else:
# TODO: This is a hack that will be removed when pyscript becomes a package if isinstance(selector, list):
# and we can better manage the imports without circular dependencies elements = selector
from pyweb import pydom
if isinstance(selector, pydom.Element):
elements = [selector._js]
elif isinstance(selector, pydom.ElementCollection):
elements = [el._js for el in selector]
else: else:
raise ValueError( elements = [selector]
f"Invalid selector: {selector}. Selector must"
" be a string, a pydom.Element or a pydom.ElementCollection."
)
try: try:
sig = inspect.signature(func) sig = inspect.signature(func)
# Function doesn't receive events # Function doesn't receive events

View File

@@ -0,0 +1,148 @@
# https://www.npmjs.com/package/flatted
import json as _json
class _Known:
def __init__(self):
self.key = []
self.value = []
class _String:
def __init__(self, value):
self.value = value
def _array_keys(value):
keys = []
i = 0
for _ in value:
keys.append(i)
i += 1
return keys
def _object_keys(value):
keys = []
for key in value:
keys.append(key)
return keys
def _is_array(value):
return isinstance(value, list) or isinstance(value, tuple)
def _is_object(value):
return isinstance(value, dict)
def _is_string(value):
return isinstance(value, str)
def _index(known, input, value):
input.append(value)
index = str(len(input) - 1)
known.key.append(value)
known.value.append(index)
return index
def _loop(keys, input, known, output):
for key in keys:
value = output[key]
if isinstance(value, _String):
_ref(key, input[int(value.value)], input, known, output)
return output
def _ref(key, value, input, known, output):
if _is_array(value) and not value in known:
known.append(value)
value = _loop(_array_keys(value), input, known, value)
elif _is_object(value) and not value in known:
known.append(value)
value = _loop(_object_keys(value), input, known, value)
output[key] = value
def _relate(known, input, value):
if _is_string(value) or _is_array(value) or _is_object(value):
try:
return known.value[known.key.index(value)]
except:
return _index(known, input, value)
return value
def _transform(known, input, value):
if _is_array(value):
output = []
for val in value:
output.append(_relate(known, input, val))
return output
if _is_object(value):
obj = {}
for key in value:
obj[key] = _relate(known, input, value[key])
return obj
return value
def _wrap(value):
if _is_string(value):
return _String(value)
if _is_array(value):
i = 0
for val in value:
value[i] = _wrap(val)
i += 1
elif _is_object(value):
for key in value:
value[key] = _wrap(value[key])
return value
def parse(value, *args, **kwargs):
json = _json.loads(value, *args, **kwargs)
wrapped = []
for value in json:
wrapped.append(_wrap(value))
input = []
for value in wrapped:
if isinstance(value, _String):
input.append(value.value)
else:
input.append(value)
value = input[0]
if _is_array(value):
return _loop(_array_keys(value), input, [value], value)
if _is_object(value):
return _loop(_object_keys(value), input, [value], value)
return value
def stringify(value, *args, **kwargs):
known = _Known()
input = []
output = []
i = int(_index(known, input, value))
while i < len(input):
output.append(_transform(known, input, input[i]))
i += 1
return _json.dumps(output, *args, **kwargs)

View File

@@ -36,7 +36,6 @@ if RUNNING_IN_WORKER:
) )
try: try:
globalThis.SharedArrayBuffer.new(4)
import js import js
window = polyscript.xworker.window window = polyscript.xworker.window
@@ -47,17 +46,11 @@ if RUNNING_IN_WORKER:
"return (...urls) => Promise.all(urls.map((url) => import(url)))" "return (...urls) => Promise.all(urls.map((url) => import(url)))"
)() )()
except: except:
globalThis.console.debug("SharedArrayBuffer is not available") message = "Unable to use `window` or `document` -> https://docs.pyscript.net/latest/faq/#sharedarraybuffer"
# in this scenario none of the utilities would work globalThis.console.warn(message)
# as expected so we better export these as NotSupported window = NotSupported("pyscript.window", message)
window = NotSupported( document = NotSupported("pyscript.document", message)
"pyscript.window", js_import = None
"pyscript.window in workers works only via SharedArrayBuffer",
)
document = NotSupported(
"pyscript.document",
"pyscript.document in workers works only via SharedArrayBuffer",
)
sync = polyscript.xworker.sync sync = polyscript.xworker.sync

View File

@@ -0,0 +1,60 @@
from polyscript import storage as _storage
from pyscript.flatted import parse as _parse
from pyscript.flatted import stringify as _stringify
# convert a Python value into an IndexedDB compatible entry
def _to_idb(value):
if value is None:
return _stringify(["null", 0])
if isinstance(value, (bool, float, int, str, list, dict, tuple)):
return _stringify(["generic", value])
if isinstance(value, bytearray):
return _stringify(["bytearray", [v for v in value]])
if isinstance(value, memoryview):
return _stringify(["memoryview", [v for v in value]])
raise TypeError(f"Unexpected value: {value}")
# convert an IndexedDB compatible entry into a Python value
def _from_idb(value):
(
kind,
result,
) = _parse(value)
if kind == "null":
return None
if kind == "generic":
return result
if kind == "bytearray":
return bytearray(result)
if kind == "memoryview":
return memoryview(bytearray(result))
return value
class Storage(dict):
def __init__(self, store):
super().__init__({k: _from_idb(v) for k, v in store.entries()})
self.__store__ = store
def __delitem__(self, attr):
self.__store__.delete(attr)
super().__delitem__(attr)
def __setitem__(self, attr, value):
self.__store__.set(attr, _to_idb(value))
super().__setitem__(attr, value)
def clear(self):
self.__store__.clear()
super().clear()
async def sync(self):
await self.__store__.sync()
async def storage(name="", storage_class=Storage):
if not name:
raise ValueError("The storage name must be defined")
return storage_class(await _storage(f"@pyscript/{name}"))

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
import js as _js
from polyscript import workers as _workers
_get = _js.Reflect.get
def _set(script, name, value=""):
script.setAttribute(name, value)
# this solves an inconsistency between Pyodide and MicroPython
# @see https://github.com/pyscript/pyscript/issues/2106
class _ReadOnlyProxy:
def __getitem__(self, name):
return _get(_workers, name)
def __getattr__(self, name):
return _get(_workers, name)
workers = _ReadOnlyProxy()
async def create_named_worker(src="", name="", config=None, type="py"):
from json import dumps
if not src:
raise ValueError("Named workers require src")
if not name:
raise ValueError("Named workers require a name")
s = _js.document.createElement("script")
s.type = type
s.src = src
_set(s, "worker")
_set(s, "name", name)
if config:
_set(s, "config", isinstance(config, str) and config or dumps(config))
_js.document.body.append(s)
return await workers[name]

View File

@@ -1,2 +0,0 @@
from .pydom import JSProperty
from .pydom import dom as pydom

View File

@@ -1,95 +0,0 @@
from pyodide.ffi import to_js
from pyscript import window
class Device:
"""Device represents a media input or output device, such as a microphone,
camera, or headset.
"""
def __init__(self, device):
self._js = device
@property
def id(self):
return self._js.deviceId
@property
def group(self):
return self._js.groupId
@property
def kind(self):
return self._js.kind
@property
def label(self):
return self._js.label
def __getitem__(self, key):
return getattr(self, key)
@classmethod
async def load(cls, audio=False, video=True):
"""Load the device stream."""
options = window.Object.new()
options.audio = audio
if isinstance(video, bool):
options.video = video
else:
# TODO: Think this can be simplified but need to check it on the pyodide side
# TODO: this is pyodide specific. shouldn't be!
options.video = window.Object.new()
for k in video:
setattr(
options.video,
k,
to_js(video[k], dict_converter=window.Object.fromEntries),
)
stream = await window.navigator.mediaDevices.getUserMedia(options)
return stream
async def get_stream(self):
key = self.kind.replace("input", "").replace("output", "")
options = {key: {"deviceId": {"exact": self.id}}}
return await self.load(**options)
async def list_devices() -> list[dict]:
"""
Return the list of the currently available media input and output devices,
such as microphones, cameras, headsets, and so forth.
Output:
list(dict) - list of dictionaries representing the available media devices.
Each dictionary has the following keys:
* deviceId: a string that is an identifier for the represented device
that is persisted across sessions. It is un-guessable by other
applications and unique to the origin of the calling application.
It is reset when the user clears cookies (for Private Browsing, a
different identifier is used that is not persisted across sessions).
* groupId: a string that is a group identifier. Two devices have the same
group identifier if they belong to the same physical device — for
example a monitor with both a built-in camera and a microphone.
* kind: an enumerated value that is either "videoinput", "audioinput"
or "audiooutput".
* label: a string describing this device (for example "External USB
Webcam").
Note: the returned list will omit any devices that are blocked by the document
Permission Policy: microphone, camera, speaker-selection (for output devices),
and so on. Access to particular non-default devices is also gated by the
Permissions API, and the list will omit devices for which the user has not
granted explicit permission.
"""
# https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices
return [
Device(obj) for obj in await window.navigator.mediaDevices.enumerateDevices()
]

View File

@@ -1,569 +0,0 @@
import inspect
try:
from typing import Any
except ImportError:
Any = "Any"
try:
import warnings
except ImportError:
# TODO: For now it probably means we are in MicroPython. We should figure
# out the "right" way to handle this. For now we just ignore the warning
# and logging to console
class warnings:
@staticmethod
def warn(*args, **kwargs):
print("WARNING: ", *args, **kwargs)
try:
from functools import cached_property
except ImportError:
# TODO: same comment about micropython as above
cached_property = property
try:
from pyodide.ffi import JsProxy
except ImportError:
# TODO: same comment about micropython as above
def JsProxy(obj):
return obj
from pyscript import display, document, window
alert = window.alert
class JSProperty:
"""JS property descriptor that directly maps to the property with the same
name in the underlying JS component."""
def __init__(self, name: str, allow_nones: bool = False):
self.name = name
self.allow_nones = allow_nones
def __get__(self, obj, objtype=None):
return getattr(obj._js, self.name)
def __set__(self, obj, value):
if not self.allow_nones and value is None:
return
setattr(obj._js, self.name, value)
class BaseElement:
def __init__(self, js_element):
self._js = js_element
self._parent = None
self.style = StyleProxy(self)
self._proxies = {}
def __eq__(self, obj):
"""Check if the element is the same as the other element by comparing
the underlying JS element"""
return isinstance(obj, BaseElement) and obj._js == self._js
@property
def parent(self):
if self._parent:
return self._parent
if self._js.parentElement:
self._parent = self.__class__(self._js.parentElement)
return self._parent
@property
def __class(self):
return self.__class__ if self.__class__ != PyDom else Element
def create(self, type_, is_child=True, classes=None, html=None, label=None):
js_el = document.createElement(type_)
element = self.__class(js_el)
if classes:
for class_ in classes:
element.add_class(class_)
if html is not None:
element.html = html
if label is not None:
element.label = label
if is_child:
self.append(element)
return element
def find(self, selector):
"""Return an ElementCollection representing all the child elements that
match the specified selector.
Args:
selector (str): A string containing a selector expression
Returns:
ElementCollection: A collection of elements matching the selector
"""
elements = self._js.querySelectorAll(selector)
if not elements:
return None
return ElementCollection([Element(el) for el in elements])
class Element(BaseElement):
@property
def children(self):
return [self.__class__(el) for el in self._js.children]
def append(self, child):
# TODO: this is Pyodide specific for now!!!!!!
# if we get passed a JSProxy Element directly we just map it to the
# higher level Python element
if inspect.isclass(JsProxy) and isinstance(child, JsProxy):
return self.append(Element(child))
elif isinstance(child, Element):
self._js.appendChild(child._js)
return child
elif isinstance(child, ElementCollection):
for el in child:
self.append(el)
# -------- Pythonic Interface to Element -------- #
@property
def html(self):
return self._js.innerHTML
@html.setter
def html(self, value):
self._js.innerHTML = value
@property
def text(self):
return self._js.textContent
@text.setter
def text(self, value):
self._js.textContent = value
@property
def content(self):
# TODO: This breaks with with standard template elements. Define how to best
# handle this specifica use case. Just not support for now?
if self._js.tagName == "TEMPLATE":
warnings.warn(
"Content attribute not supported for template elements.", stacklevel=2
)
return None
return self._js.innerHTML
@content.setter
def content(self, value):
# TODO: (same comment as above)
if self._js.tagName == "TEMPLATE":
warnings.warn(
"Content attribute not supported for template elements.", stacklevel=2
)
return
display(value, target=self.id)
@property
def id(self):
return self._js.id
@id.setter
def id(self, value):
self._js.id = value
@property
def options(self):
if "options" in self._proxies:
return self._proxies["options"]
if not self._js.tagName.lower() in {"select", "datalist", "optgroup"}:
raise AttributeError(
f"Element {self._js.tagName} has no options attribute."
)
self._proxies["options"] = OptionsProxy(self)
return self._proxies["options"]
@property
def value(self):
return self._js.value
@value.setter
def value(self, value):
# in order to avoid confusion to the user, we don't allow setting the
# value of elements that don't have a value attribute
if not hasattr(self._js, "value"):
raise AttributeError(
f"Element {self._js.tagName} has no value attribute. If you want to "
"force a value attribute, set it directly using the `_js.value = <value>` "
"javascript API attribute instead."
)
self._js.value = value
@property
def selected(self):
return self._js.selected
@selected.setter
def selected(self, value):
# in order to avoid confusion to the user, we don't allow setting the
# value of elements that don't have a value attribute
if not hasattr(self._js, "selected"):
raise AttributeError(
f"Element {self._js.tagName} has no value attribute. If you want to "
"force a value attribute, set it directly using the `_js.value = <value>` "
"javascript API attribute instead."
)
self._js.selected = value
def clone(self, new_id=None):
clone = Element(self._js.cloneNode(True))
clone.id = new_id
return clone
def remove_class(self, classname):
classList = self._js.classList
if isinstance(classname, list):
classList.remove(*classname)
else:
classList.remove(classname)
return self
def add_class(self, classname):
classList = self._js.classList
if isinstance(classname, list):
classList.add(*classname)
else:
self._js.classList.add(classname)
return self
@property
def classes(self):
classes = self._js.classList.values()
return [x for x in classes]
def show_me(self):
self._js.scrollIntoView()
def snap(
self,
to: BaseElement | str = None,
width: int | None = None,
height: int | None = None,
):
"""
Captures a snapshot of a video element. (Only available for video elements)
Inputs:
* to: element where to save the snapshot of the video frame to
* width: width of the image
* height: height of the image
Output:
(Element) canvas element where the video frame snapshot was drawn into
"""
if self._js.tagName != "VIDEO":
raise AttributeError("Snap method is only available for video Elements")
if to is None:
canvas = self.create("canvas")
if width is None:
width = self._js.width
if height is None:
height = self._js.height
canvas._js.width = width
canvas._js.height = height
elif isinstance(to, Element):
if to._js.tagName != "CANVAS":
raise TypeError("Element to snap to must a canvas.")
canvas = to
elif getattr(to, "tagName", "") == "CANVAS":
canvas = Element(to)
elif isinstance(to, str):
canvas = pydom[to][0]
if canvas._js.tagName != "CANVAS":
raise TypeError("Element to snap to must a be canvas.")
canvas.draw(self, width, height)
return canvas
def download(self, filename: str = "snapped.png") -> None:
"""Download the current element (only available for canvas elements) with the filename
provided in input.
Inputs:
* filename (str): name of the file being downloaded
Output:
None
"""
if self._js.tagName != "CANVAS":
raise AttributeError(
"The download method is only available for canvas Elements"
)
link = self.create("a")
link._js.download = filename
link._js.href = self._js.toDataURL()
link._js.click()
def draw(self, what, width, height):
"""Draw `what` on the current element (only available for canvas elements).
Inputs:
* what (canvas image source): An element to draw into the context. The specification permits any canvas
image source, specifically, an HTMLImageElement, an SVGImageElement, an HTMLVideoElement,
an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a VideoFrame.
"""
if self._js.tagName != "CANVAS":
raise AttributeError(
"The draw method is only available for canvas Elements"
)
if isinstance(what, Element):
what = what._js
# https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
self._js.getContext("2d").drawImage(what, 0, 0, width, height)
class OptionsProxy:
"""This class represents the options of a select element. It
allows to access to add and remove options by using the `add` and `remove` methods.
"""
def __init__(self, element: Element) -> None:
self._element = element
if self._element._js.tagName.lower() != "select":
raise AttributeError(
f"Element {self._element._js.tagName} has no options attribute."
)
def add(
self,
value: Any = None,
html: str = None,
text: str = None,
before: Element | int = None,
**kws,
) -> None:
"""Add a new option to the select element"""
# create the option element and set the attributes
option = document.createElement("option")
if value is not None:
kws["value"] = value
if html is not None:
option.innerHTML = html
if text is not None:
kws["text"] = text
for key, value in kws.items():
option.setAttribute(key, value)
if before:
if isinstance(before, Element):
before = before._js
self._element._js.add(option, before)
def remove(self, item: int) -> None:
"""Remove the option at the specified index"""
self._element._js.remove(item)
def clear(self) -> None:
"""Remove all the options"""
for i in range(len(self)):
self.remove(0)
@property
def options(self):
"""Return the list of options"""
return [Element(opt) for opt in self._element._js.options]
@property
def selected(self):
"""Return the selected option"""
return self.options[self._element._js.selectedIndex]
def __iter__(self):
yield from self.options
def __len__(self):
return len(self.options)
def __repr__(self):
return f"{self.__class__.__name__} (length: {len(self)}) {self.options}"
def __getitem__(self, key):
return self.options[key]
class StyleProxy: # (dict):
def __init__(self, element: Element) -> None:
self._element = element
@cached_property
def _style(self):
return self._element._js.style
def __getitem__(self, key):
return self._style.getPropertyValue(key)
def __setitem__(self, key, value):
self._style.setProperty(key, value)
def remove(self, key):
self._style.removeProperty(key)
def set(self, **kws):
for k, v in kws.items():
self._element._js.style.setProperty(k, v)
# CSS Properties
# Reference: https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L3799C1-L5005C2
# Following prperties automatically generated from the above reference using
# tools/codegen_css_proxy.py
@property
def visible(self):
return self._element._js.style.visibility
@visible.setter
def visible(self, value):
self._element._js.style.visibility = value
class StyleCollection:
def __init__(self, collection: "ElementCollection") -> None:
self._collection = collection
def __get__(self, obj, objtype=None):
return obj._get_attribute("style")
def __getitem__(self, key):
return self._collection._get_attribute("style")[key]
def __setitem__(self, key, value):
for element in self._collection._elements:
element.style[key] = value
def remove(self, key):
for element in self._collection._elements:
element.style.remove(key)
class ElementCollection:
def __init__(self, elements: [Element]) -> None:
self._elements = elements
self.style = StyleCollection(self)
def __getitem__(self, key):
# If it's an integer we use it to access the elements in the collection
if isinstance(key, int):
return self._elements[key]
# If it's a slice we use it to support slice operations over the elements
# in the collection
elif isinstance(key, slice):
return ElementCollection(self._elements[key])
# If it's anything else (basically a string) we use it as a selector
# TODO: Write tests!
elements = self._element.querySelectorAll(key)
return ElementCollection([Element(el) for el in elements])
def __len__(self):
return len(self._elements)
def __eq__(self, obj):
"""Check if the element is the same as the other element by comparing
the underlying JS element"""
return isinstance(obj, ElementCollection) and obj._elements == self._elements
def _get_attribute(self, attr, index=None):
if index is None:
return [getattr(el, attr) for el in self._elements]
# As JQuery, when getting an attr, only return it for the first element
return getattr(self._elements[index], attr)
def _set_attribute(self, attr, value):
for el in self._elements:
setattr(el, attr, value)
@property
def html(self):
return self._get_attribute("html")
@html.setter
def html(self, value):
self._set_attribute("html", value)
@property
def value(self):
return self._get_attribute("value")
@value.setter
def value(self, value):
self._set_attribute("value", value)
@property
def children(self):
return self._elements
def __iter__(self):
yield from self._elements
def __repr__(self):
return f"{self.__class__.__name__} (length: {len(self._elements)}) {self._elements}"
class DomScope:
def __getattr__(self, __name: str):
element = document[f"#{__name}"]
if element:
return element[0]
class PyDom(BaseElement):
# Add objects we want to expose to the DOM namespace since this class instance is being
# remapped as "the module" itself
BaseElement = BaseElement
Element = Element
ElementCollection = ElementCollection
def __init__(self):
# PyDom is a special case of BaseElement where we don't want to create a new JS element
# and it really doesn't have a need for styleproxy or parent to to call to __init__
# (which actually fails in MP for some reason)
self._js = document
self._parent = None
self._proxies = {}
self.ids = DomScope()
self.body = Element(document.body)
self.head = Element(document.head)
def create(self, type_, classes=None, html=None):
return super().create(type_, is_child=False, classes=classes, html=html)
def __getitem__(self, key):
elements = self._js.querySelectorAll(key)
if not elements:
return None
return ElementCollection([Element(el) for el in elements])
dom = PyDom()

View File

@@ -1 +0,0 @@
from . import elements

View File

@@ -1,947 +0,0 @@
import inspect
import sys
from pyscript import document, when, window
from pyweb import JSProperty, pydom
#: A flag to show if MicroPython is the current Python interpreter.
is_micropython = "MicroPython" in sys.version
def getmembers_static(cls):
"""Cross-interpreter implementation of inspect.getmembers_static."""
if is_micropython: # pragma: no cover
return [(name, getattr(cls, name)) for name, _ in inspect.getmembers(cls)]
return inspect.getmembers_static(cls)
class ElementBase(pydom.Element):
tag = "div"
# GLOBAL ATTRIBUTES
# These are attribute that all elements have (this list is a subset of the official one)
# We are trying to capture the most used ones
accesskey = JSProperty("accesskey")
autofocus = JSProperty("autofocus")
autocapitalize = JSProperty("autocapitalize")
className = JSProperty("className")
contenteditable = JSProperty("contenteditable")
draggable = JSProperty("draggable")
enterkeyhint = JSProperty("enterkeyhint")
hidden = JSProperty("hidden")
id = JSProperty("id")
lang = JSProperty("lang")
nonce = JSProperty("nonce")
part = JSProperty("part")
popover = JSProperty("popover")
slot = JSProperty("slot")
spellcheck = JSProperty("spellcheck")
tabindex = JSProperty("tabindex")
title = JSProperty("title")
translate = JSProperty("translate")
virtualkeyboardpolicy = JSProperty("virtualkeyboardpolicy")
def __init__(self, style=None, **kwargs):
super().__init__(document.createElement(self.tag))
# set all the style properties provided in input
if isinstance(style, dict):
for key, value in style.items():
self.style[key] = value
elif style is None:
pass
else:
raise ValueError(
f"Style should be a dictionary, received {style} (type {type(style)}) instead."
)
# IMPORTANT!!! This is used to auto-harvest all input arguments and set them as properties
self._init_properties(**kwargs)
def _init_properties(self, **kwargs):
"""Set all the properties (of type JSProperties) provided in input as properties
of the class instance.
Args:
**kwargs: The properties to set
"""
# Look at all the properties of the class and see if they were provided in kwargs
for attr_name, attr in getmembers_static(self.__class__):
# For each one, actually check if it is a property of the class and set it
if isinstance(attr, JSProperty) and attr_name in kwargs:
try:
setattr(self, attr_name, kwargs[attr_name])
except Exception as e:
print(f"Error setting {attr_name} to {kwargs[attr_name]}: {e}")
raise
class TextElementBase(ElementBase):
def __init__(self, content=None, style=None, **kwargs):
super().__init__(style=style, **kwargs)
# If it's an element, append the element
if isinstance(content, pydom.Element):
self.append(content)
# If it's a list of elements
elif isinstance(content, list):
for item in content:
self.append(item)
# If the content wasn't set just ignore
elif content is None:
pass
else:
# Otherwise, set content as the html of the element
self.html = content
# IMPORTANT: For all HTML components defined below, we are not mapping all
# available attributes, just the global and the most common ones.
# If you need to access a specific attribute, you can always use the `_js.<attribute>`
class a(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a"""
tag = "a"
download = JSProperty("download")
href = JSProperty("href")
referrerpolicy = JSProperty("referrerpolicy")
rel = JSProperty("rel")
target = JSProperty("target")
type = JSProperty("type")
class abbr(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/abbr"""
tag = "abbr"
class address(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/address"""
tag = "address"
class area(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/area"""
tag = "area"
alt = JSProperty("alt")
coords = JSProperty("coords")
download = JSProperty("download")
href = JSProperty("href")
ping = JSProperty("ping")
referrerpolicy = JSProperty("referrerpolicy")
rel = JSProperty("rel")
shape = JSProperty("shape")
target = JSProperty("target")
class article(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/article"""
tag = "article"
class aside(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/aside"""
tag = "aside"
class audio(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio"""
tag = "audio"
autoplay = JSProperty("autoplay")
controls = JSProperty("controls")
controlslist = JSProperty("controlslist")
crossorigin = JSProperty("crossorigin")
disableremoteplayback = JSProperty("disableremoteplayback")
loop = JSProperty("loop")
muted = JSProperty("muted")
preload = JSProperty("preload")
src = JSProperty("src")
class b(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/b"""
tag = "b"
class blockquote(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/blockquote"""
tag = "blockquote"
cite = JSProperty("cite")
class br(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/br"""
tag = "br"
class button(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button"""
tag = "button"
autofocus = JSProperty("autofocus")
disabled = JSProperty("disabled")
form = JSProperty("form")
formaction = JSProperty("formaction")
formenctype = JSProperty("formenctype")
formmethod = JSProperty("formmethod")
formnovalidate = JSProperty("formnovalidate")
formtarget = JSProperty("formtarget")
name = JSProperty("name")
type = JSProperty("type")
value = JSProperty("value")
class canvas(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas"""
tag = "canvas"
height = JSProperty("height")
width = JSProperty("width")
class caption(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/caption"""
tag = "caption"
class cite(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/cite"""
tag = "cite"
class code(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/code"""
tag = "code"
class data(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/data"""
tag = "data"
value = JSProperty("value")
class datalist(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/datalist"""
tag = "datalist"
class dd(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dd"""
tag = "dd"
class del_(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/del"""
tag = "del"
cite = JSProperty("cite")
datetime = JSProperty("datetime")
class details(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details"""
tag = "details"
open = JSProperty("open")
class dialog(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog"""
tag = "dialog"
open = JSProperty("open")
class div(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div"""
tag = "div"
class dl(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dl"""
tag = "dl"
value = JSProperty("value")
class dt(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dt"""
tag = "dt"
class em(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em"""
tag = "em"
class embed(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/embed"""
tag = "embed"
height = JSProperty("height")
src = JSProperty("src")
type = JSProperty("type")
width = JSProperty("width")
class fieldset(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/fieldset"""
tag = "fieldset"
disabled = JSProperty("disabled")
form = JSProperty("form")
name = JSProperty("name")
class figcaption(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figcaption"""
tag = "figcaption"
class figure(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/figure"""
tag = "figure"
class footer(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/footer"""
tag = "footer"
class form(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form"""
tag = "form"
accept_charset = JSProperty("accept-charset")
action = JSProperty("action")
autocapitalize = JSProperty("autocapitalize")
autocomplete = JSProperty("autocomplete")
enctype = JSProperty("enctype")
name = JSProperty("name")
method = JSProperty("method")
nonvalidate = JSProperty("nonvalidate")
rel = JSProperty("rel")
target = JSProperty("target")
class h1(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h1"""
tag = "h1"
class h2(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h2"""
tag = "h2"
class h3(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h3"""
tag = "h3"
class h4(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h4"""
tag = "h4"
class h5(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h5"""
tag = "h5"
class h6(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/h6"""
tag = "h6"
class header(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/header"""
tag = "header"
class hgroup(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hgroup"""
tag = "hgroup"
class hr(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/hr"""
tag = "hr"
class i(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/i"""
tag = "i"
class iframe(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe"""
tag = "iframe"
allow = JSProperty("allow")
allowfullscreen = JSProperty("allowfullscreen")
height = JSProperty("height")
loading = JSProperty("loading")
name = JSProperty("name")
referrerpolicy = JSProperty("referrerpolicy")
sandbox = JSProperty("sandbox")
src = JSProperty("src")
srcdoc = JSProperty("srcdoc")
width = JSProperty("width")
class img(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img"""
tag = "img"
alt = JSProperty("alt")
crossorigin = JSProperty("crossorigin")
decoding = JSProperty("decoding")
fetchpriority = JSProperty("fetchpriority")
height = JSProperty("height")
ismap = JSProperty("ismap")
loading = JSProperty("loading")
referrerpolicy = JSProperty("referrerpolicy")
sizes = JSProperty("sizes")
src = JSProperty("src")
width = JSProperty("width")
# NOTE: Input is a reserved keyword in Python, so we use input_ instead
class input_(ElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input"""
tag = "input"
accept = JSProperty("accept")
alt = JSProperty("alt")
autofocus = JSProperty("autofocus")
capture = JSProperty("capture")
checked = JSProperty("checked")
dirname = JSProperty("dirname")
disabled = JSProperty("disabled")
form = JSProperty("form")
formaction = JSProperty("formaction")
formenctype = JSProperty("formenctype")
formmethod = JSProperty("formmethod")
formnovalidate = JSProperty("formnovalidate")
formtarget = JSProperty("formtarget")
height = JSProperty("height")
list = JSProperty("list")
max = JSProperty("max")
maxlength = JSProperty("maxlength")
min = JSProperty("min")
minlength = JSProperty("minlength")
multiple = JSProperty("multiple")
name = JSProperty("name")
pattern = JSProperty("pattern")
placeholder = JSProperty("placeholder")
popovertarget = JSProperty("popovertarget")
popovertargetaction = JSProperty("popovertargetaction")
readonly = JSProperty("readonly")
required = JSProperty("required")
size = JSProperty("size")
src = JSProperty("src")
step = JSProperty("step")
type = JSProperty("type")
value = JSProperty("value")
width = JSProperty("width")
class ins(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ins"""
tag = "ins"
cite = JSProperty("cite")
datetime = JSProperty("datetime")
class kbd(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/kbd"""
tag = "kbd"
class label(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/label"""
tag = "label"
for_ = JSProperty("for")
class legend(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/legend"""
tag = "legend"
class li(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li"""
tag = "li"
value = JSProperty("value")
class link(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link"""
tag = "link"
as_ = JSProperty("as")
crossorigin = JSProperty("crossorigin")
disabled = JSProperty("disabled")
fetchpriority = JSProperty("fetchpriority")
href = JSProperty("href")
imagesizes = JSProperty("imagesizes")
imagesrcset = JSProperty("imagesrcset")
integrity = JSProperty("integrity")
media = JSProperty("media")
rel = JSProperty("rel")
referrerpolicy = JSProperty("referrerpolicy")
sizes = JSProperty("sizes")
title = JSProperty("title")
type = JSProperty("type")
class main(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/main"""
tag = "main"
class map_(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/map"""
tag = "map"
name = JSProperty("name")
class mark(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/mark"""
tag = "mark"
class menu(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/menu"""
tag = "menu"
class meter(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meter"""
tag = "meter"
form = JSProperty("form")
high = JSProperty("high")
low = JSProperty("low")
max = JSProperty("max")
min = JSProperty("min")
optimum = JSProperty("optimum")
value = JSProperty("value")
class nav(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/nav"""
tag = "nav"
class object_(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/object"""
tag = "object"
data = JSProperty("data")
form = JSProperty("form")
height = JSProperty("height")
name = JSProperty("name")
type = JSProperty("type")
usemap = JSProperty("usemap")
width = JSProperty("width")
class ol(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ol"""
tag = "ol"
reversed = JSProperty("reversed")
start = JSProperty("start")
type = JSProperty("type")
class optgroup(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/optgroup"""
tag = "optgroup"
disabled = JSProperty("disabled")
label = JSProperty("label")
class option(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/option"""
tag = "option"
disabled = JSProperty("value")
label = JSProperty("label")
selected = JSProperty("selected")
value = JSProperty("value")
class output(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/output"""
tag = "output"
for_ = JSProperty("for")
form = JSProperty("form")
name = JSProperty("name")
class p(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/p"""
tag = "p"
class picture(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture"""
tag = "picture"
class pre(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/pre"""
tag = "pre"
class progress(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/progress"""
tag = "progress"
max = JSProperty("max")
value = JSProperty("value")
class q(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/q"""
tag = "q"
cite = JSProperty("cite")
class s(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/s"""
tag = "s"
class script(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script"""
tag = "script"
# Let's add async manually since it's a reserved keyword in Python
async_ = JSProperty("async")
blocking = JSProperty("blocking")
crossorigin = JSProperty("crossorigin")
defer = JSProperty("defer")
fetchpriority = JSProperty("fetchpriority")
integrity = JSProperty("integrity")
nomodule = JSProperty("nomodule")
nonce = JSProperty("nonce")
referrerpolicy = JSProperty("referrerpolicy")
src = JSProperty("src")
type = JSProperty("type")
class section(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/section"""
tag = "section"
class select(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select"""
tag = "select"
class small(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/small"""
tag = "small"
class source(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/source"""
tag = "source"
media = JSProperty("media")
sizes = JSProperty("sizes")
src = JSProperty("src")
srcset = JSProperty("srcset")
type = JSProperty("type")
class span(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/span"""
tag = "span"
class strong(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/strong"""
tag = "strong"
class style(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/style"""
tag = "style"
blocking = JSProperty("blocking")
media = JSProperty("media")
nonce = JSProperty("nonce")
title = JSProperty("title")
class sub(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sub"""
tag = "sub"
class summary(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/summary"""
tag = "summary"
class sup(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/sup"""
tag = "sup"
class table(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table"""
tag = "table"
class tbody(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tbody"""
tag = "tbody"
class td(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/td"""
tag = "td"
colspan = JSProperty("colspan")
headers = JSProperty("headers")
rowspan = JSProperty("rowspan")
class template(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template"""
tag = "template"
shadowrootmode = JSProperty("shadowrootmode")
class textarea(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea"""
tag = "textarea"
autocapitalize = JSProperty("autocapitalize")
autocomplete = JSProperty("autocomplete")
autofocus = JSProperty("autofocus")
cols = JSProperty("cols")
dirname = JSProperty("dirname")
disabled = JSProperty("disabled")
form = JSProperty("form")
maxlength = JSProperty("maxlength")
minlength = JSProperty("minlength")
name = JSProperty("name")
placeholder = JSProperty("placeholder")
readonly = JSProperty("readonly")
required = JSProperty("required")
rows = JSProperty("rows")
spellcheck = JSProperty("spellcheck")
wrap = JSProperty("wrap")
class tfoot(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tfoot"""
tag = "tfoot"
class th(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/th"""
tag = "th"
class thead(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/thead"""
tag = "thead"
class time(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/time"""
tag = "time"
datetime = JSProperty("datetime")
class title(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/title"""
tag = "title"
class tr(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/tr"""
tag = "tr"
abbr = JSProperty("abbr")
colspan = JSProperty("colspan")
headers = JSProperty("headers")
rowspan = JSProperty("rowspan")
scope = JSProperty("scope")
class track(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track"""
tag = "track"
default = JSProperty("default")
kind = JSProperty("kind")
label = JSProperty("label")
src = JSProperty("src")
srclang = JSProperty("srclang")
class u(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/u"""
tag = "u"
class ul(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/ul"""
tag = "ul"
class var(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/var"""
tag = "var"
class video(TextElementBase):
"""Ref: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video"""
tag = "video"
autoplay = JSProperty("autoplay")
controls = JSProperty("controls")
crossorigin = JSProperty("crossorigin")
disablepictureinpicture = JSProperty("disablepictureinpicture")
disableremoteplayback = JSProperty("disableremoteplayback")
height = JSProperty("height")
loop = JSProperty("loop")
muted = JSProperty("muted")
playsinline = JSProperty("playsinline")
poster = JSProperty("poster")
preload = JSProperty("preload")
src = JSProperty("src")
width = JSProperty("width")
# Custom Elements
class grid(TextElementBase):
tag = "div"
def __init__(self, layout, content=None, gap=None, **kwargs):
super().__init__(content, **kwargs)
self.style["display"] = "grid"
self.style["grid-template-columns"] = layout
# TODO: This should be a property
if not gap is None:
self.style["gap"] = gap

View File

@@ -0,0 +1,4 @@
packages = [
"https://cdn.holoviz.org/panel/wheels/bokeh-3.5.0-py3-none-any.whl",
"https://cdn.holoviz.org/panel/1.5.0-b.2/dist/wheels/panel-1.5.0b2-py3-none-any.whl"
]

View File

@@ -0,0 +1,17 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../../dist/core.css">
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-3.5.0.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.5.0.min.js"></script>
<script src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.5.0.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@holoviz/panel@1.5.0-b.2/dist/panel.min.js"></script>
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="py" src="main.py" config="config.toml" worker></script>
<div id="simple_app"></div>
</body>
</html>

View File

@@ -0,0 +1,12 @@
import panel as pn
pn.extension(sizing_mode="stretch_width")
slider = pn.widgets.FloatSlider(start=0, end=10, name="amplitude")
def callback(new):
return f"Amplitude is: {new}"
pn.Row(slider, pn.bind(callback, slider)).servable(target="simple_app")

View File

@@ -88,3 +88,13 @@ test('MicroPython + Pyodide ffi', async ({ page }) => {
await page.goto('http://localhost:8080/test/ffi.html'); await page.goto('http://localhost:8080/test/ffi.html');
await page.waitForSelector('html.mpy.py'); await page.waitForSelector('html.mpy.py');
}); });
test('MicroPython + Storage', async ({ page }) => {
await page.goto('http://localhost:8080/test/storage.html');
await page.waitForSelector('html.ok');
});
test('MicroPython + workers', async ({ page }) => {
await page.goto('http://localhost:8080/test/workers/index.html');
await page.waitForSelector('html.mpy.py');
});

View File

@@ -29,7 +29,7 @@
a = 1 a = 1
</script> </script>
<!-- a share-nothing micropython editor --> <!-- a share-nothing micropython editor -->
<script type="mpy-editor" config="./config.toml"> <script type="mpy-editor" config='{"js_modules":{"worker":{"https://cdn.jsdelivr.net/npm/html-escaper/+esm":"html_escaper"}}}'>
from pyscript.js_modules.html_escaper import escape, unescape from pyscript.js_modules.html_escaper import escape, unescape
print(unescape(escape("<OK>"))) print(unescape(escape("<OK>")))
b = 2 b = 2

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy-editor" service-worker="./sw.js">
from pyscript import document
document.body.append("OK")
</script>
</body>
</html>

View File

@@ -0,0 +1 @@
const{isArray:e}=Array,t=new Map,s=e=>{e.stopImmediatePropagation(),e.preventDefault()};var n=Object.freeze({__proto__:null,activate:e=>e.waitUntil(clients.claim()),fetch:e=>{const{request:n}=e;"POST"===n.method&&n.url===`${location.href}?sabayon`&&(s(e),e.respondWith(n.json().then((async e=>{const{promise:s,resolve:o}=Promise.withResolvers(),a=e.join(",");t.set(a,o);for(const t of await clients.matchAll())t.postMessage(e);return s.then((e=>new Response(`[${e.join(",")}]`,n.headers)))}))))},install:()=>skipWaiting(),message:n=>{const{data:o}=n;if(e(o)&&4===o.length){const[e,a,i,r]=o,l=[e,a,i].join(",");t.has(l)&&(s(n),t.get(l)(r),t.delete(l))}}});for(const e in n)addEventListener(e,n[e]);

View File

@@ -9,7 +9,7 @@
<style>.xterm { padding: .5rem; }</style> <style>.xterm { padding: .5rem; }</style>
</head> </head>
<body> <body>
<script type="py" worker terminal> <script type="mpy" worker terminal>
from pyscript import document from pyscript import document
document.documentElement.classList.add("first") document.documentElement.classList.add("first")

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<ul>
<li>
<a href="./no-repl.html">Prompt: NO REPL</a>
</li>
<li>
<a href="./repl.html">Prompt: REPL</a>
</li>
</ul>
</body>
</html>

View File

@@ -0,0 +1,20 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyTerminal Prompt: NO REPL</title>
<script type="module" src="../../dist/core.js"></script>
<style>.xterm { padding: .5rem; }</style>
</head>
<body>
<script type="mpy" worker terminal>
prompt = input("Say something: ")
print("You said, ", prompt)
</script>
<script type="py" worker terminal>
prompt = input("Say something: ")
print("You said, ", prompt)
</script>
</body>
</html>

View File

@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PyTerminal Prompt: REPL</title>
<script type="module" src="../../dist/core.js"></script>
<style>.xterm { padding: .5rem; }</style>
</head>
<body>
<script type="mpy" worker terminal>
import code
code.interact()
prompt = input("Say something: ")
print("You said, ", prompt)
</script>
<script type="py" worker terminal>
import code
code.interact()
# Pyodide won't execute this ... ever
# this should be tested manually
prompt = input("Say something: ")
print("You said, ", prompt)
</script>
</body>
</html>

View File

@@ -10,6 +10,8 @@
<body> <body>
<script type="mpy" src="pydom.py"></script> <script type="mpy" src="pydom.py"></script>
<div id="system-info"></div>
<button id="just-a-button">Click For Time</button> <button id="just-a-button">Click For Time</button>
<button id="color-button">Click For Color</button> <button id="color-button">Click For Color</button>
<button id="color-reset-button">Reset Color</button> <button id="color-reset-button">Reset Color</button>

View File

@@ -4,7 +4,7 @@ import time
from datetime import datetime as dt from datetime import datetime as dt
from pyscript import display, when from pyscript import display, when
from pyweb import pydom from pyscript.web import dom
display(sys.version, target="system-info") display(sys.version, target="system-info")
@@ -19,18 +19,15 @@ def on_click():
tstr = "{:02d}/{:02d}/{:04d} {:02d}:{:02d}:{:02d}" tstr = "{:02d}/{:02d}/{:04d} {:02d}:{:02d}:{:02d}"
timenow = tstr.format(tnow[2], tnow[1], tnow[0], *tnow[2:]) timenow = tstr.format(tnow[2], tnow[1], tnow[0], *tnow[2:])
display(f"Hello from PyScript, time is: {timenow}", append=False, target="result") display(f"Hello from PyScript, time is: {timenow}", append=False, target="#result")
@when("click", "#color-button") @when("click", "#color-button")
def on_color_click(event): def on_color_click(event):
btn = pydom["#result"] btn = dom["#result"]
btn.style["background-color"] = f"#{random.randrange(0x1000000):06x}" btn.style["background-color"] = f"#{random.randrange(0x1000000):06x}"
@when("click", "#color-reset-button") @when("click", "#color-reset-button")
def reset_color(*args, **kwargs): def reset_color(*args, **kwargs):
pydom["#result"].style["background-color"] = "white" dom["#result"].style["background-color"] = "white"
# btn_reset = pydom["#color-reset-button"][0].when('click', reset_color)

View File

@@ -32,7 +32,7 @@
</style> </style>
</head> </head>
<body> <body>
<script type="py" src="./run_tests.py" config="./tests.toml"></script> <script type="py" src="/test/pyscript_dom/run_tests.py" config="/test/pyscript_dom/tests.toml"></script>
<h1>pyscript.dom Tests</h1> <h1>pyscript.dom Tests</h1>
<p>You can pass test parameters to this test suite by passing them as query params on the url. <p>You can pass test parameters to this test suite by passing them as query params on the url.

View File

@@ -1,23 +1,11 @@
from unittest import mock
import pytest
from pyscript import document, when from pyscript import document, when
from pyweb import pydom from pyscript.web import Element, ElementCollection, div, p, page
class TestDocument: class TestDocument:
def test__element(self): def test__element(self):
assert pydom._js == document assert page.body._dom_element == document.body
assert page.head._dom_element == document.head
def test_no_parent(self):
assert pydom.parent is None
def test_create_element(self):
new_el = pydom.create("div")
assert isinstance(new_el, pydom.BaseElement)
assert new_el._js.tagName == "DIV"
# EXPECT the new element to be associated with the document
assert new_el.parent == None
def test_getitem_by_id(): def test_getitem_by_id():
@@ -26,14 +14,14 @@ def test_getitem_by_id():
txt = "You found test_id_selector" txt = "You found test_id_selector"
selector = f"#{id_}" selector = f"#{id_}"
# EXPECT the element to be found by id # EXPECT the element to be found by id
result = pydom[selector] result = page.find(selector)
div = result[0] div = result[0]
# EXPECT the element text value to match what we expect and what # EXPECT the element text value to match what we expect and what
# the JS document.querySelector API would return # the JS document.querySelector API would return
assert document.querySelector(selector).innerHTML == div.html == txt assert document.querySelector(selector).innerHTML == div.innerHTML == txt
# EXPECT the results to be of the right types # EXPECT the results to be of the right types
assert isinstance(div, pydom.BaseElement) assert isinstance(div, Element)
assert isinstance(result, pydom.ElementCollection) assert isinstance(result, ElementCollection)
def test_getitem_by_class(): def test_getitem_by_class():
@@ -43,8 +31,7 @@ def test_getitem_by_class():
"test_selector_w_children_child_1", "test_selector_w_children_child_1",
] ]
expected_class = "a-test-class" expected_class = "a-test-class"
result = pydom[f".{expected_class}"] result = page.find(f".{expected_class}")
div = result[0]
# EXPECT to find exact number of elements with the class in the page (== 3) # EXPECT to find exact number of elements with the class in the page (== 3)
assert len(result) == 3 assert len(result) == 3
@@ -54,40 +41,40 @@ def test_getitem_by_class():
def test_read_n_write_collection_elements(): def test_read_n_write_collection_elements():
elements = pydom[".multi-elems"] elements = page.find(".multi-elems")
for element in elements: for element in elements:
assert element.html == f"Content {element.id.replace('#', '')}" assert element.innerHTML == f"Content {element.id.replace('#', '')}"
new_content = "New Content" new_content = "New Content"
elements.html = new_content elements.innerHTML = new_content
for element in elements: for element in elements:
assert element.html == new_content assert element.innerHTML == new_content
class TestElement: class TestElement:
def test_query(self): def test_query(self):
# GIVEN an existing element on the page, with at least 1 child element # GIVEN an existing element on the page, with at least 1 child element
id_ = "test_selector_w_children" id_ = "test_selector_w_children"
parent_div = pydom[f"#{id_}"][0] parent_div = page.find(f"#{id_}")[0]
# EXPECT it to be able to query for the first child element # EXPECT it to be able to query for the first child element
div = parent_div.find("div")[0] div = parent_div.find("div")[0]
# EXPECT the new element to be associated with the parent # EXPECT the new element to be associated with the parent
assert div.parent == parent_div assert div.parent == parent_div
# EXPECT the new element to be a BaseElement # EXPECT the new element to be an Element
assert isinstance(div, pydom.BaseElement) assert isinstance(div, Element)
# EXPECT the div attributes to be == to how they are configured in the page # EXPECT the div attributes to be == to how they are configured in the page
assert div.html == "Child 1" assert div.innerHTML == "Child 1"
assert div.id == "test_selector_w_children_child_1" assert div.id == "test_selector_w_children_child_1"
def test_equality(self): def test_equality(self):
# GIVEN 2 different Elements pointing to the same underlying element # GIVEN 2 different Elements pointing to the same underlying element
id_ = "test_id_selector" id_ = "test_id_selector"
selector = f"#{id_}" selector = f"#{id_}"
div = pydom[selector][0] div = page.find(selector)[0]
div2 = pydom[selector][0] div2 = page.find(selector)[0]
# EXPECT them to be equal # EXPECT them to be equal
assert div == div2 assert div == div2
@@ -95,34 +82,34 @@ class TestElement:
assert div is not div2 assert div is not div2
# EXPECT their value to always be equal # EXPECT their value to always be equal
assert div.html == div2.html assert div.innerHTML == div2.innerHTML
div.html = "some value" div.innerHTML = "some value"
assert div.html == div2.html == "some value" assert div.innerHTML == div2.innerHTML == "some value"
def test_append_element(self): def test_append_element(self):
id_ = "element-append-tests" id_ = "element-append-tests"
div = pydom[f"#{id_}"][0] div = page.find(f"#{id_}")[0]
len_children_before = len(div.children) len_children_before = len(div.children)
new_el = div.create("p") new_el = p("new element")
div.append(new_el) div.append(new_el)
assert len(div.children) == len_children_before + 1 assert len(div.children) == len_children_before + 1
assert div.children[-1] == new_el assert div.children[-1] == new_el
def test_append_js_element(self): def test_append_dom_element_element(self):
id_ = "element-append-tests" id_ = "element-append-tests"
div = pydom[f"#{id_}"][0] div = page.find(f"#{id_}")[0]
len_children_before = len(div.children) len_children_before = len(div.children)
new_el = div.create("p") new_el = p("new element")
div.append(new_el._js) div.append(new_el._dom_element)
assert len(div.children) == len_children_before + 1 assert len(div.children) == len_children_before + 1
assert div.children[-1] == new_el assert div.children[-1] == new_el
def test_append_collection(self): def test_append_collection(self):
id_ = "element-append-tests" id_ = "element-append-tests"
div = pydom[f"#{id_}"][0] div = page.find(f"#{id_}")[0]
len_children_before = len(div.children) len_children_before = len(div.children)
collection = pydom[".collection"] collection = page.find(".collection")
div.append(collection) div.append(collection)
assert len(div.children) == len_children_before + len(collection) assert len(div.children) == len_children_before + len(collection)
@@ -132,24 +119,24 @@ class TestElement:
def test_read_classes(self): def test_read_classes(self):
id_ = "test_class_selector" id_ = "test_class_selector"
expected_class = "a-test-class" expected_class = "a-test-class"
div = pydom[f"#{id_}"][0] div = page.find(f"#{id_}")[0]
assert div.classes == [expected_class] assert div.classes == [expected_class]
def test_add_remove_class(self): def test_add_remove_class(self):
id_ = "div-no-classes" id_ = "div-no-classes"
classname = "tester-class" classname = "tester-class"
div = pydom[f"#{id_}"][0] div = page.find(f"#{id_}")[0]
assert not div.classes assert not div.classes
div.add_class(classname) div.classes.add(classname)
same_div = pydom[f"#{id_}"][0] same_div = page.find(f"#{id_}")[0]
assert div.classes == [classname] == same_div.classes assert div.classes == [classname] == same_div.classes
div.remove_class(classname) div.classes.remove(classname)
assert div.classes == [] == same_div.classes assert div.classes == [] == same_div.classes
def test_when_decorator(self): def test_when_decorator(self):
called = False called = False
just_a_button = pydom["#a-test-button"][0] just_a_button = page.find("#a-test-button")[0]
@when("click", just_a_button) @when("click", just_a_button)
def on_click(event): def on_click(event):
@@ -157,45 +144,49 @@ class TestElement:
called = True called = True
# Now let's simulate a click on the button (using the low level JS API) # Now let's simulate a click on the button (using the low level JS API)
# so we don't risk pydom getting in the way # so we don't risk dom getting in the way
assert not called assert not called
just_a_button._js.click() just_a_button._dom_element.click()
assert called assert called
def test_html_attribute(self): def test_inner_html_attribute(self):
# GIVEN an existing element on the page with a known empty text content # GIVEN an existing element on the page with a known empty text content
div = pydom["#element_attribute_tests"][0] div = page.find("#element_attribute_tests")[0]
# WHEN we set the html attribute # WHEN we set the html attribute
div.html = "<b>New Content</b>" div.innerHTML = "<b>New Content</b>"
# EXPECT the element html and underlying JS Element innerHTML property # EXPECT the element html and underlying JS Element innerHTML property
# to match what we expect and what # to match what we expect and what
assert div.html == div._js.innerHTML == "<b>New Content</b>" assert div.innerHTML == div._dom_element.innerHTML == "<b>New Content</b>"
assert div.text == div._js.textContent == "New Content" assert div.textContent == div._dom_element.textContent == "New Content"
def test_text_attribute(self): def test_text_attribute(self):
# GIVEN an existing element on the page with a known empty text content # GIVEN an existing element on the page with a known empty text content
div = pydom["#element_attribute_tests"][0] div = page.find("#element_attribute_tests")[0]
# WHEN we set the html attribute # WHEN we set the html attribute
div.text = "<b>New Content</b>" div.textContent = "<b>New Content</b>"
# EXPECT the element html and underlying JS Element innerHTML property # EXPECT the element html and underlying JS Element innerHTML property
# to match what we expect and what # to match what we expect and what
assert div.html == div._js.innerHTML == "&lt;b&gt;New Content&lt;/b&gt;" assert (
assert div.text == div._js.textContent == "<b>New Content</b>" div.innerHTML
== div._dom_element.innerHTML
== "&lt;b&gt;New Content&lt;/b&gt;"
)
assert div.textContent == div._dom_element.textContent == "<b>New Content</b>"
class TestCollection: class TestCollection:
def test_iter_eq_children(self): def test_iter_eq_children(self):
elements = pydom[".multi-elems"] elements = page.find(".multi-elems")
assert [el for el in elements] == [el for el in elements.children] assert [el for el in elements] == [el for el in elements.elements]
assert len(elements) == 3 assert len(elements) == 3
def test_slices(self): def test_slices(self):
elements = pydom[".multi-elems"] elements = page.find(".multi-elems")
assert elements[0] assert elements[0]
_slice = elements[:2] _slice = elements[:2]
assert len(_slice) == 2 assert len(_slice) == 2
@@ -205,26 +196,26 @@ class TestCollection:
def test_style_rule(self): def test_style_rule(self):
selector = ".multi-elems" selector = ".multi-elems"
elements = pydom[selector] elements = page.find(selector)
for el in elements: for el in elements:
assert el.style["background-color"] != "red" assert el.style["background-color"] != "red"
elements.style["background-color"] = "red" elements.style["background-color"] = "red"
for i, el in enumerate(pydom[selector]): for i, el in enumerate(page.find(selector)):
assert elements[i].style["background-color"] == "red" assert elements[i].style["background-color"] == "red"
assert el.style["background-color"] == "red" assert el.style["background-color"] == "red"
elements.style.remove("background-color") elements.style.remove("background-color")
for i, el in enumerate(pydom[selector]): for i, el in enumerate(page.find(selector)):
assert el.style["background-color"] != "red" assert el.style["background-color"] != "red"
assert elements[i].style["background-color"] != "red" assert elements[i].style["background-color"] != "red"
def test_when_decorator(self): def test_when_decorator(self):
called = False called = False
buttons_collection = pydom["button"] buttons_collection = page.find("button")
@when("click", buttons_collection) @when("click", buttons_collection)
def on_click(event): def on_click(event):
@@ -232,42 +223,43 @@ class TestCollection:
called = True called = True
# Now let's simulate a click on the button (using the low level JS API) # Now let's simulate a click on the button (using the low level JS API)
# so we don't risk pydom getting in the way # so we don't risk dom getting in the way
assert not called assert not called
for button in buttons_collection: for button in buttons_collection:
button._js.click() button._dom_element.click()
assert called assert called
called = False called = False
class TestCreation: class TestCreation:
def test_create_document_element(self): def test_create_document_element(self):
new_el = pydom.create("div") # TODO: This test should probably be removed since it's testing the elements
# module.
new_el = div("new element")
new_el.id = "new_el_id" new_el.id = "new_el_id"
assert isinstance(new_el, pydom.BaseElement) assert isinstance(new_el, Element)
assert new_el._js.tagName == "DIV" assert new_el._dom_element.tagName == "DIV"
# EXPECT the new element to be associated with the document # EXPECT the new element to be associated with the document
assert new_el.parent == None assert new_el.parent is None
pydom.body.append(new_el) page.body.append(new_el)
assert pydom["#new_el_id"][0].parent == pydom.body assert page.find("#new_el_id")[0].parent == page.body
def test_create_element_child(self): def test_create_element_child(self):
selector = "#element-creation-test" selector = "#element-creation-test"
parent_div = pydom[selector][0] parent_div = page.find(selector)[0]
# Creating an element from another element automatically creates that element # Creating an element from another element automatically creates that element
# as a child of the original element # as a child of the original element
new_el = parent_div.create( new_el = p("a div", classes=["code-description"], innerHTML="Ciao PyScripters!")
"p", classes=["code-description"], html="Ciao PyScripters!" parent_div.append(new_el)
)
assert isinstance(new_el, Element)
assert new_el._dom_element.tagName == "P"
assert isinstance(new_el, pydom.BaseElement)
assert new_el._js.tagName == "P"
# EXPECT the new element to be associated with the document # EXPECT the new element to be associated with the document
assert new_el.parent == parent_div assert new_el.parent == parent_div
assert page.find(selector)[0].children[0] == new_el
assert pydom[selector][0].children[0] == new_el
class TestInput: class TestInput:
@@ -281,10 +273,10 @@ class TestInput:
def test_value(self): def test_value(self):
for id_ in self.input_ids: for id_ in self.input_ids:
expected_type = id_.split("_")[-1] expected_type = id_.split("_")[-1]
result = pydom[f"#{id_}"] result = page.find(f"#{id_}")
input_el = result[0] input_el = result[0]
assert input_el._js.type == expected_type assert input_el._dom_element.type == expected_type
assert input_el.value == f"Content {id_}" == input_el._js.value assert input_el.value == f"Content {id_}" == input_el._dom_element.value
# Check that we can set the value # Check that we can set the value
new_value = f"New Value {expected_type}" new_value = f"New Value {expected_type}"
@@ -299,7 +291,7 @@ class TestInput:
def test_set_value_collection(self): def test_set_value_collection(self):
for id_ in self.input_ids: for id_ in self.input_ids:
input_el = pydom[f"#{id_}"] input_el = page.find(f"#{id_}")
assert input_el.value[0] == f"Content {id_}" == input_el[0].value assert input_el.value[0] == f"Content {id_}" == input_el[0].value
@@ -307,36 +299,35 @@ class TestInput:
input_el.value = new_value input_el.value = new_value
assert input_el.value[0] == new_value == input_el[0].value assert input_el.value[0] == new_value == input_el[0].value
def test_element_without_value(self): # TODO: We only attach attributes to the classes that have them now which means we
result = pydom[f"#tests-terminal"][0] # would have to have some other way to help users if using attributes that aren't
with pytest.raises(AttributeError): # actually on the class. Maybe a job for __setattr__?
result.value = "some value" #
# def test_element_without_value(self):
def test_element_without_collection(self): # result = page.find(f"#tests-terminal"][0]
result = pydom[f"#tests-terminal"] # with pytest.raises(AttributeError):
with pytest.raises(AttributeError): # result.value = "some value"
result.value = "some value" #
# def test_element_without_value_via_collection(self):
def test_element_without_collection(self): # result = page.find(f"#tests-terminal"]
result = pydom[f"#tests-terminal"] # with pytest.raises(AttributeError):
with pytest.raises(AttributeError): # result.value = "some value"
result.value = "some value"
class TestSelect: class TestSelect:
def test_select_options_iter(self): def test_select_options_iter(self):
select = pydom[f"#test_select_element_w_options"][0] select = page.find(f"#test_select_element_w_options")[0]
for i, option in enumerate(select.options, 1): for i, option in enumerate(select.options, 1):
assert option.value == f"{i}" assert option.value == f"{i}"
assert option.html == f"Option {i}" assert option.innerHTML == f"Option {i}"
def test_select_options_len(self): def test_select_options_len(self):
select = pydom[f"#test_select_element_w_options"][0] select = page.find(f"#test_select_element_w_options")[0]
assert len(select.options) == 2 assert len(select.options) == 2
def test_select_options_clear(self): def test_select_options_clear(self):
select = pydom[f"#test_select_element_to_clear"][0] select = page.find(f"#test_select_element_to_clear")[0]
assert len(select.options) == 3 assert len(select.options) == 3
select.options.clear() select.options.clear()
@@ -345,7 +336,7 @@ class TestSelect:
def test_select_element_add(self): def test_select_element_add(self):
# GIVEN the existing select element with no options # GIVEN the existing select element with no options
select = pydom[f"#test_select_element"][0] select = page.find(f"#test_select_element")[0]
# EXPECT the select element to have no options # EXPECT the select element to have no options
assert len(select.options) == 0 assert len(select.options) == 0
@@ -357,7 +348,7 @@ class TestSelect:
# we passed in # we passed in
assert len(select.options) == 1 assert len(select.options) == 1
assert select.options[0].value == "1" assert select.options[0].value == "1"
assert select.options[0].html == "Option 1" assert select.options[0].innerHTML == "Option 1"
# WHEN we add another option (blank this time) # WHEN we add another option (blank this time)
select.options.add("") select.options.add("")
@@ -367,7 +358,7 @@ class TestSelect:
# EXPECT the last option to have an empty value and html # EXPECT the last option to have an empty value and html
assert select.options[1].value == "" assert select.options[1].value == ""
assert select.options[1].html == "" assert select.options[1].innerHTML == ""
# WHEN we add another option (this time adding it in between the other 2 # WHEN we add another option (this time adding it in between the other 2
# options by using an integer index) # options by using an integer index)
@@ -378,11 +369,11 @@ class TestSelect:
# EXPECT the middle option to have the value and html we passed in # EXPECT the middle option to have the value and html we passed in
assert select.options[0].value == "1" assert select.options[0].value == "1"
assert select.options[0].html == "Option 1" assert select.options[0].innerHTML == "Option 1"
assert select.options[1].value == "2" assert select.options[1].value == "2"
assert select.options[1].html == "Option 2" assert select.options[1].innerHTML == "Option 2"
assert select.options[2].value == "" assert select.options[2].value == ""
assert select.options[2].html == "" assert select.options[2].innerHTML == ""
# WHEN we add another option (this time adding it in between the other 2 # WHEN we add another option (this time adding it in between the other 2
# options but using the option itself) # options but using the option itself)
@@ -395,38 +386,48 @@ class TestSelect:
# EXPECT the middle option to have the value and html we passed in # EXPECT the middle option to have the value and html we passed in
assert select.options[0].value == "1" assert select.options[0].value == "1"
assert select.options[0].html == "Option 1" assert select.options[0].innerHTML == "Option 1"
assert select.options[0].selected == select.options[0]._js.selected == False assert (
select.options[0].selected
== select.options[0]._dom_element.selected
== False
)
assert select.options[1].value == "2" assert select.options[1].value == "2"
assert select.options[1].html == "Option 2" assert select.options[1].innerHTML == "Option 2"
assert select.options[2].value == "3" assert select.options[2].value == "3"
assert select.options[2].html == "Option 3" assert select.options[2].innerHTML == "Option 3"
assert select.options[2].selected == select.options[2]._js.selected == True assert (
select.options[2].selected
== select.options[2]._dom_element.selected
== True
)
assert select.options[3].value == "" assert select.options[3].value == ""
assert select.options[3].html == "" assert select.options[3].innerHTML == ""
# WHEN we add another option (this time adding it in between the other 2 # WHEN we add another option (this time adding it in between the other 2
# options but using the JS element of the option itself) # options but using the JS element of the option itself)
select.options.add(value="2a", html="Option 2a", before=select.options[2]._js) select.options.add(
value="2a", html="Option 2a", before=select.options[2]._dom_element
)
# EXPECT the select element to have 3 options # EXPECT the select element to have 3 options
assert len(select.options) == 5 assert len(select.options) == 5
# EXPECT the middle option to have the value and html we passed in # EXPECT the middle option to have the value and html we passed in
assert select.options[0].value == "1" assert select.options[0].value == "1"
assert select.options[0].html == "Option 1" assert select.options[0].innerHTML == "Option 1"
assert select.options[1].value == "2" assert select.options[1].value == "2"
assert select.options[1].html == "Option 2" assert select.options[1].innerHTML == "Option 2"
assert select.options[2].value == "2a" assert select.options[2].value == "2a"
assert select.options[2].html == "Option 2a" assert select.options[2].innerHTML == "Option 2a"
assert select.options[3].value == "3" assert select.options[3].value == "3"
assert select.options[3].html == "Option 3" assert select.options[3].innerHTML == "Option 3"
assert select.options[4].value == "" assert select.options[4].value == ""
assert select.options[4].html == "" assert select.options[4].innerHTML == ""
def test_select_options_remove(self): def test_select_options_remove(self):
# GIVEN the existing select element with 3 options # GIVEN the existing select element with 3 options
select = pydom[f"#test_select_element_to_remove"][0] select = page.find(f"#test_select_element_to_remove")[0]
# EXPECT the select element to have 3 options # EXPECT the select element to have 3 options
assert len(select.options) == 4 assert len(select.options) == 4
@@ -448,12 +449,12 @@ class TestSelect:
def test_select_get_selected_option(self): def test_select_get_selected_option(self):
# GIVEN the existing select element with one selected option # GIVEN the existing select element with one selected option
select = pydom[f"#test_select_element_w_options"][0] select = page.find(f"#test_select_element_w_options")[0]
# WHEN we get the selected option # WHEN we get the selected option
selected_option = select.options.selected selected_option = select.options.selected
# EXPECT the selected option to be correct # EXPECT the selected option to be correct
assert selected_option.value == "2" assert selected_option.value == "2"
assert selected_option.html == "Option 2" assert selected_option.innerHTML == "Option 2"
assert selected_option.selected == selected_option._js.selected == True assert selected_option.selected == selected_option._dom_element.selected == True

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Service Worker</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="../../dist/core.css">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy" service-worker="./sabayon.js" worker>
from pyscript import document
document.body.append('OK')
</script>
</body>
</html>

View File

@@ -0,0 +1,28 @@
/*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */
/*! mini-coi - Andrea Giammarchi and contributors, licensed under MIT */
(({ document: d, navigator: { serviceWorker: s } }) => {
if (d) {
const { currentScript: c } = d;
s.register(c.src, { scope: c.getAttribute('scope') || '.' }).then(r => {
r.addEventListener('updatefound', () => location.reload());
if (r.active && !s.controller) location.reload();
});
}
else {
addEventListener('install', () => skipWaiting());
addEventListener('activate', e => e.waitUntil(clients.claim()));
addEventListener('fetch', e => {
const { request: r } = e;
if (r.cache === 'only-if-cached' && r.mode !== 'same-origin') return;
e.respondWith(fetch(r).then(r => {
const { body, status, statusText } = r;
if (!status || status > 399) return r;
const h = new Headers(r.headers);
h.set('Cross-Origin-Opener-Policy', 'same-origin');
h.set('Cross-Origin-Embedder-Policy', 'require-corp');
h.set('Cross-Origin-Resource-Policy', 'cross-origin');
return new Response(body, { status, statusText, headers: h });
}));
});
}
})(self);

View File

@@ -0,0 +1 @@
const{isArray:e}=Array,t=new Map,s=e=>{e.stopImmediatePropagation(),e.preventDefault()};var n=Object.freeze({__proto__:null,activate:e=>e.waitUntil(clients.claim()),fetch:e=>{const{request:n}=e;"POST"===n.method&&n.url===`${location.href}?sabayon`&&(s(e),e.respondWith(n.json().then((async e=>{const{promise:s,resolve:o}=Promise.withResolvers(),a=e.join(",");t.set(a,o);for(const t of await clients.matchAll())t.postMessage(e);return s.then((e=>new Response(`[${e.join(",")}]`,n.headers)))}))))},install:()=>skipWaiting(),message:n=>{const{data:o}=n;if(e(o)&&4===o.length){const[e,a,i,r]=o,l=[e,a,i].join(",");t.has(l)&&(s(n),t.get(l)(r),t.delete(l))}}});for(const e in n)addEventListener(e,n[e]);

View File

@@ -0,0 +1,46 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@pyscript/core storage</title>
<link rel="stylesheet" href="../dist/core.css">
<script type="module" src="../dist/core.js"></script>
</head>
<body>
<script type="mpy" async>
from random import random
from pyscript import storage
store = await storage(name="test")
print("before", len(store))
for k in store:
if isinstance(store[k], memoryview):
print(f" {k}: {store[k].hex()} as hex()")
else:
print(f" {k}: {store[k]}")
store["ba"] = bytearray([0, 1, 2, 3, 4])
store["mv"] = memoryview(bytearray([5, 6, 7, 8, 9]))
store["random"] = ("some", random(), True)
store["key"] = "value"
print("now", len(store))
for k in store:
print(f" {k}: {store[k]}")
del store["key"]
# store.clear()
print("after", len(store))
for k in store:
print(f" {k}: {store[k]}")
await store.sync()
import js
js.document.documentElement.classList.add("ok")
</script>
</body>
</html>

View File

@@ -0,0 +1,2 @@
[files]
"./test.py" = "./test.py"

View File

@@ -0,0 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy" async>
from pyscript import create_named_worker
await create_named_worker("./worker.py", name="micropython_version", type="mpy")
</script>
<script type="mpy" config="./config.toml" async>
from test import test
await test("mpy")
</script>
<script type="py" config="./config.toml" async>
from test import test
await test("py")
</script>
<script type="py" name="pyodide_version" worker>
def pyodide_version():
import sys
return sys.version
__export__ = ['pyodide_version']
</script>
</body>
</html>

View File

@@ -0,0 +1,29 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>named workers</title>
<script type="module" src="../../dist/core.js"></script>
</head>
<body>
<script type="mpy" async>
from pyscript import workers
await (await workers["mpy"]).greetings()
await (await workers["py"]).greetings()
</script>
<script type="mpy" worker name="mpy">
def greetings():
print("micropython")
__export__ = ['greetings']
</script>
<script type="py" worker name="py">
def greetings():
print("pyodide")
__export__ = ['greetings']
</script>
</body>
</html>

View File

@@ -0,0 +1,19 @@
from pyscript import document, workers
async def test(interpreter):
# accessed as item
named = await workers.micropython_version
version = await named.micropython_version()
document.body.append(version)
document.body.append(document.createElement("hr"))
# accessed as attribute
named = await workers["pyodide_version"]
version = await named.pyodide_version()
document.body.append(version)
document.body.append(document.createElement("hr"))
document.documentElement.classList.add(interpreter)

View File

@@ -0,0 +1,7 @@
def micropython_version():
import sys
return sys.version
__export__ = ["micropython_version"]

View File

@@ -101,10 +101,9 @@ class TestElements(PyScriptTest):
code_ = f""" code_ = f"""
from pyscript import when from pyscript import when
<script type="{interpreter}"> <script type="{interpreter}">
from pyweb import pydom from pyscript.web import page, {el_type}
from pyweb.ui.elements import {el_type}
el = {el_type}({attributes}) el = {el_type}({attributes})
pydom.body.append(el) page.body.append(el)
</script> </script>
""" """
self.pyscript_run(code_) self.pyscript_run(code_)
@@ -178,7 +177,7 @@ class TestElements(PyScriptTest):
) )
def test_b(self, interpreter): def test_b(self, interpreter):
self._create_el_and_basic_asserts("aside", "some text", interpreter) self._create_el_and_basic_asserts("b", "some text", interpreter)
def test_blockquote(self, interpreter): def test_blockquote(self, interpreter):
self._create_el_and_basic_asserts("blockquote", "some text", interpreter) self._create_el_and_basic_asserts("blockquote", "some text", interpreter)
@@ -603,3 +602,203 @@ class TestElements(PyScriptTest):
properties=properties, properties=properties,
expected_errors=self.expected_missing_file_errors, expected_errors=self.expected_missing_file_errors,
) )
def test_append_py_element(self, interpreter):
# Let's make sure the body of the page is clean first
body = self.page.locator("body")
assert body.inner_html() == ""
# Let's make sure the element is not in the page
element = self.page.locator("div")
assert not element.count()
div_text_content = "Luke, I am your father"
p_text_content = "noooooooooo!"
# Let's create the element
code_ = f"""
from pyscript import when
<script type="{interpreter}">
from pyscript.web import page, div, p
el = div("{div_text_content}")
child = p('{p_text_content}')
el.append(child)
page.body.append(el)
</script>
"""
self.pyscript_run(code_)
# Let's keep the tag in 2 variables, one for the selector and another to
# check the return tag from the selector
el = self.page.locator("div")
tag = el.evaluate("node => node.tagName")
assert tag == "DIV"
assert el.text_content() == f"{div_text_content}{p_text_content}"
assert (
el.evaluate("node => node.children.length") == 1
), "There should be only 1 child"
assert el.evaluate("node => node.children[0].tagName") == "P"
assert (
el.evaluate("node => node.children[0].parentNode.textContent")
== f"{div_text_content}{p_text_content}"
)
assert el.evaluate("node => node.children[0].textContent") == p_text_content
def test_append_proxy_element(self, interpreter):
# Let's make sure the body of the page is clean first
body = self.page.locator("body")
assert body.inner_html() == ""
# Let's make sure the element is not in the page
element = self.page.locator("div")
assert not element.count()
div_text_content = "Luke, I am your father"
p_text_content = "noooooooooo!"
# Let's create the element
code_ = f"""
from pyscript import when
<script type="{interpreter}">
from pyscript import document
from pyscript.web import page, div, p
el = div("{div_text_content}")
child = document.createElement('P')
child.textContent = '{p_text_content}'
el.append(child)
page.body.append(el)
</script>
"""
self.pyscript_run(code_)
# Let's keep the tag in 2 variables, one for the selector and another to
# check the return tag from the selector
el = self.page.locator("div")
tag = el.evaluate("node => node.tagName")
assert tag == "DIV"
assert el.text_content() == f"{div_text_content}{p_text_content}"
assert (
el.evaluate("node => node.children.length") == 1
), "There should be only 1 child"
assert el.evaluate("node => node.children[0].tagName") == "P"
assert (
el.evaluate("node => node.children[0].parentNode.textContent")
== f"{div_text_content}{p_text_content}"
)
assert el.evaluate("node => node.children[0].textContent") == p_text_content
def test_append_py_elementcollection(self, interpreter):
# Let's make sure the body of the page is clean first
body = self.page.locator("body")
assert body.inner_html() == ""
# Let's make sure the element is not in the page
element = self.page.locator("div")
assert not element.count()
div_text_content = "Luke, I am your father"
p_text_content = "noooooooooo!"
p2_text_content = "not me!"
# Let's create the element
code_ = f"""
from pyscript import when
<script type="{interpreter}">
from pyscript.web import page, div, p, ElementCollection
el = div("{div_text_content}")
child1 = p('{p_text_content}')
child2 = p('{p2_text_content}', id='child2')
collection = ElementCollection([child1, child2])
el.append(collection)
page.body.append(el)
</script>
"""
self.pyscript_run(code_)
# Let's keep the tag in 2 variables, one for the selector and another to
# check the return tag from the selector
el = self.page.locator("div")
tag = el.evaluate("node => node.tagName")
assert tag == "DIV"
parent_full_content = f"{div_text_content}{p_text_content}{p2_text_content}"
assert el.text_content() == parent_full_content
assert (
el.evaluate("node => node.children.length") == 2
), "There should be only 1 child"
assert el.evaluate("node => node.children[0].tagName") == "P"
assert (
el.evaluate("node => node.children[0].parentNode.textContent")
== parent_full_content
)
assert el.evaluate("node => node.children[0].textContent") == p_text_content
assert el.evaluate("node => node.children[1].tagName") == "P"
assert el.evaluate("node => node.children[1].id") == "child2"
assert (
el.evaluate("node => node.children[1].parentNode.textContent")
== parent_full_content
)
assert el.evaluate("node => node.children[1].textContent") == p2_text_content
def test_append_js_element_nodelist(self, interpreter):
# Let's make sure the body of the page is clean first
body = self.page.locator("body")
assert body.inner_html() == ""
# Let's make sure the element is not in the page
element = self.page.locator("div")
assert not element.count()
div_text_content = "Luke, I am your father"
p_text_content = "noooooooooo!"
p2_text_content = "not me!"
# Let's create the element
code_ = f"""
from pyscript import when
<script type="{interpreter}">
from pyscript import document
from pyscript.web import page, div, p, ElementCollection
el = div("{div_text_content}")
child1 = p('{p_text_content}')
child2 = p('{p2_text_content}', id='child2')
page.body.append(child1)
page.body.append(child2)
nodes = document.querySelectorAll('p')
el.append(nodes)
page.body.append(el)
</script>
"""
self.pyscript_run(code_)
# Let's keep the tag in 2 variables, one for the selector and another to
# check the return tag from the selector
el = self.page.locator("div")
tag = el.evaluate("node => node.tagName")
assert tag == "DIV"
parent_full_content = f"{div_text_content}{p_text_content}{p2_text_content}"
assert el.text_content() == parent_full_content
assert (
el.evaluate("node => node.children.length") == 2
), "There should be only 1 child"
assert el.evaluate("node => node.children[0].tagName") == "P"
assert (
el.evaluate("node => node.children[0].parentNode.textContent")
== parent_full_content
)
assert el.evaluate("node => node.children[0].textContent") == p_text_content
assert el.evaluate("node => node.children[1].tagName") == "P"
assert el.evaluate("node => node.children[1].id") == "child2"
assert (
el.evaluate("node => node.children[1].parentNode.textContent")
== parent_full_content
)
assert el.evaluate("node => node.children[1].textContent") == p2_text_content

View File

@@ -1,2 +1,2 @@
export default configs; export const configs: Map<any, any>;
declare const configs: Map<any, any>; export function relative_url(url: any, base?: string): string;

View File

@@ -3,6 +3,7 @@ import { stdlib } from "./stdlib.js";
import { optional } from "./stdlib.js"; import { optional } from "./stdlib.js";
import { inputFailure } from "./hooks.js"; import { inputFailure } from "./hooks.js";
import TYPES from "./types.js"; import TYPES from "./types.js";
import { relative_url } from "./config.js";
/** /**
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module. * A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
* @param {string} file the python file to run ina worker. * @param {string} file the python file to run ina worker.
@@ -53,5 +54,5 @@ declare const exportedHooks: {
}; };
}; };
declare const exportedConfig: {}; declare const exportedConfig: {};
declare const exportedWhenDefined: (type: string) => Promise<any>; declare const exportedWhenDefined: (type: string) => Promise<object>;
export { stdlib, optional, inputFailure, TYPES, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined }; export { stdlib, optional, inputFailure, TYPES, relative_url, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };

View File

@@ -53,19 +53,4 @@ export class InstallError extends UserError {
/** /**
* Keys of the ErrorCode object * Keys of the ErrorCode object
*/ */
export type ErrorCodes = keyof { export type ErrorCodes = "GENERIC" | "CONFLICTING_CODE" | "BAD_CONFIG" | "MICROPIP_INSTALL_ERROR" | "BAD_PLUGIN_FILE_EXTENSION" | "NO_DEFAULT_EXPORT" | "TOP_LEVEL_AWAIT" | "FETCH_ERROR" | "FETCH_NAME_ERROR" | "FETCH_UNAUTHORIZED_ERROR" | "FETCH_FORBIDDEN_ERROR" | "FETCH_NOT_FOUND_ERROR" | "FETCH_SERVER_ERROR" | "FETCH_UNAVAILABLE_ERROR";
GENERIC: string;
CONFLICTING_CODE: string;
BAD_CONFIG: string;
MICROPIP_INSTALL_ERROR: string;
BAD_PLUGIN_FILE_EXTENSION: string;
NO_DEFAULT_EXPORT: string;
TOP_LEVEL_AWAIT: string;
FETCH_ERROR: string;
FETCH_NAME_ERROR: string;
FETCH_UNAUTHORIZED_ERROR: string;
FETCH_FORBIDDEN_ERROR: string;
FETCH_NOT_FOUND_ERROR: string;
FETCH_SERVER_ERROR: string;
FETCH_UNAVAILABLE_ERROR: string;
};

View File

@@ -5,18 +5,13 @@ declare namespace _default {
"event_handling.py": string; "event_handling.py": string;
"fetch.py": string; "fetch.py": string;
"ffi.py": string; "ffi.py": string;
"flatted.py": string;
"magic_js.py": string; "magic_js.py": string;
"storage.py": string;
"util.py": string; "util.py": string;
"web.py": string;
"websocket.py": string; "websocket.py": string;
}; "workers.py": string;
let pyweb: {
"__init__.py": string;
"media.py": string;
"pydom.py": string;
ui: {
"__init__.py": string;
"elements.py": string;
};
}; };
} }
export default _default; export default _default;