@pyscript/core 0.4.33 → 0.4.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{codemirror-Dr2Hgejs.js → codemirror-zS6ccqby.js} +2 -2
- package/dist/codemirror-zS6ccqby.js.map +1 -0
- package/dist/{codemirror_commands-MgxtVkrD.js → codemirror_commands-4671n0xX.js} +2 -2
- package/dist/{codemirror_commands-MgxtVkrD.js.map → codemirror_commands-4671n0xX.js.map} +1 -1
- package/dist/codemirror_lang-python-D74-Kgzp.js +2 -0
- package/dist/codemirror_lang-python-D74-Kgzp.js.map +1 -0
- package/dist/{codemirror_language-_XiX6II0.js → codemirror_language-BheMcNfw.js} +2 -2
- package/dist/{codemirror_language-_XiX6II0.js.map → codemirror_language-BheMcNfw.js.map} +1 -1
- package/dist/codemirror_state-D1qTXrff.js +2 -0
- package/dist/{codemirror_state-BKbyfKsm.js.map → codemirror_state-D1qTXrff.js.map} +1 -1
- package/dist/codemirror_view-C3_bb6sY.js +2 -0
- package/dist/codemirror_view-C3_bb6sY.js.map +1 -0
- package/dist/core-tc5wDVRu.js +3 -0
- package/dist/core-tc5wDVRu.js.map +1 -0
- package/dist/core.js +1 -1
- package/dist/{deprecations-manager-C4b5dQv_.js → deprecations-manager-CAgIm-Gn.js} +2 -2
- package/dist/{deprecations-manager-C4b5dQv_.js.map → deprecations-manager-CAgIm-Gn.js.map} +1 -1
- package/dist/{error-LGdr10Zu.js → error-CtG6l7bX.js} +2 -2
- package/dist/{error-LGdr10Zu.js.map → error-CtG6l7bX.js.map} +1 -1
- package/dist/index-O4achdbT.js +2 -0
- package/dist/index-O4achdbT.js.map +1 -0
- package/dist/mpy-CP-h-QIF.js +2 -0
- package/dist/mpy-CP-h-QIF.js.map +1 -0
- package/dist/py-C9FGLVGP.js +2 -0
- package/dist/py-C9FGLVGP.js.map +1 -0
- package/dist/{py-editor-BqaFoNyj.js → py-editor-djBi0dCK.js} +2 -2
- package/dist/{py-editor-BqaFoNyj.js.map → py-editor-djBi0dCK.js.map} +1 -1
- package/dist/py-terminal-H_VJVofp.js +2 -0
- package/dist/py-terminal-H_VJVofp.js.map +1 -0
- package/dist/xterm-BY7uk_OU.js +2 -0
- package/dist/{xterm-DqawCVsv.js.map → xterm-BY7uk_OU.js.map} +1 -1
- package/dist/zip-CKUyfu9C.js +2 -0
- package/dist/zip-CKUyfu9C.js.map +1 -0
- package/package.json +8 -8
- package/src/core.js +9 -2
- package/src/hooks.js +1 -1
- package/src/plugins/py-terminal/mpy.js +231 -0
- package/src/plugins/py-terminal/py.js +176 -0
- package/src/plugins/py-terminal.js +23 -260
- package/src/stdlib/pyscript.js +1 -1
- package/src/stdlib/pyweb/pydom.py +1 -1
- package/types/core.d.ts +2 -1
- package/types/hooks.d.ts +1 -0
- package/types/plugins/py-terminal/mpy.d.ts +2 -0
- package/types/plugins/py-terminal/py.d.ts +2 -0
- package/dist/codemirror-Dr2Hgejs.js.map +0 -1
- package/dist/codemirror_lang-python-Cxoc-ydj.js +0 -2
- package/dist/codemirror_lang-python-Cxoc-ydj.js.map +0 -1
- package/dist/codemirror_state-BKbyfKsm.js +0 -2
- package/dist/codemirror_view-C0PMO2z_.js +0 -2
- package/dist/codemirror_view-C0PMO2z_.js.map +0 -1
- package/dist/core-DbDOh4To.js +0 -3
- package/dist/core-DbDOh4To.js.map +0 -1
- package/dist/index-CTWZX_TW.js +0 -2
- package/dist/index-CTWZX_TW.js.map +0 -1
- package/dist/py-terminal-D1xo7BxU.js +0 -2
- package/dist/py-terminal-D1xo7BxU.js.map +0 -1
- package/dist/xterm-DqawCVsv.js +0 -2
- package/dist/zip-D2yvzXKD.js +0 -2
- package/dist/zip-D2yvzXKD.js.map +0 -1
- package/dist.zip +0 -0
@@ -1,11 +1,14 @@
|
|
1
1
|
// PyScript py-terminal plugin
|
2
|
-
import { TYPES
|
2
|
+
import { TYPES } from "../core.js";
|
3
3
|
import { notify } from "./error.js";
|
4
|
-
import { customObserver
|
4
|
+
import { customObserver } from "polyscript/exports";
|
5
5
|
|
6
6
|
// will contain all valid selectors
|
7
7
|
const SELECTORS = [];
|
8
8
|
|
9
|
+
// avoid processing same elements twice
|
10
|
+
const processed = new WeakSet();
|
11
|
+
|
9
12
|
// show the error on main and
|
10
13
|
// stops the module from keep executing
|
11
14
|
const notifyAndThrow = (message) => {
|
@@ -15,265 +18,10 @@ const notifyAndThrow = (message) => {
|
|
15
18
|
|
16
19
|
const onceOnMain = ({ attributes: { worker } }) => !worker;
|
17
20
|
|
18
|
-
const bootstrapped = new WeakSet();
|
19
|
-
|
20
21
|
let addStyle = true;
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
// only once thanks to the `sync.is_pyterminal()` check.
|
25
|
-
const workerReady = ({ interpreter, io, run, type }, { sync }) => {
|
26
|
-
if (!sync.is_pyterminal()) return;
|
27
|
-
|
28
|
-
// in workers it's always safe to grab the polyscript currentScript
|
29
|
-
// the ugly `_` dance is due MicroPython not able to import via:
|
30
|
-
// `from polyscript.currentScript import terminal as __terminal__`
|
31
|
-
run(
|
32
|
-
"from polyscript import currentScript as _; __terminal__ = _.terminal; del _",
|
33
|
-
);
|
34
|
-
|
35
|
-
let data = "";
|
36
|
-
const { pyterminal_read, pyterminal_write } = sync;
|
37
|
-
const decoder = new TextDecoder();
|
38
|
-
const generic = {
|
39
|
-
isatty: false,
|
40
|
-
write(buffer) {
|
41
|
-
data = decoder.decode(buffer);
|
42
|
-
pyterminal_write(data);
|
43
|
-
return buffer.length;
|
44
|
-
},
|
45
|
-
};
|
46
|
-
|
47
|
-
// This part works already in both Pyodide and MicroPython
|
48
|
-
io.stderr = (error) => {
|
49
|
-
pyterminal_write(String(error.message || error));
|
50
|
-
};
|
51
|
-
|
52
|
-
// MicroPython has no code or code.interact()
|
53
|
-
// This part patches it in a way that simulates
|
54
|
-
// the code.interact() module in Pyodide.
|
55
|
-
if (type === "mpy") {
|
56
|
-
// monkey patch global input otherwise broken in MicroPython
|
57
|
-
interpreter.registerJsModule("_pyscript_input", {
|
58
|
-
input: pyterminal_read,
|
59
|
-
});
|
60
|
-
run("from _pyscript_input import input");
|
61
|
-
|
62
|
-
// this is needed to avoid truncated unicode in MicroPython
|
63
|
-
// the reason is that `linebuffer` false just send one byte
|
64
|
-
// per time and readline here doesn't like it much.
|
65
|
-
// MicroPython also has issues with code-points and
|
66
|
-
// replProcessChar(byte) but that function accepts only
|
67
|
-
// one byte per time so ... we have an issue!
|
68
|
-
// @see https://github.com/pyscript/pyscript/pull/2018
|
69
|
-
// @see https://github.com/WebReflection/buffer-points
|
70
|
-
const bufferPoints = (stdio) => {
|
71
|
-
const bytes = [];
|
72
|
-
let needed = 0;
|
73
|
-
return (buffer) => {
|
74
|
-
let written = 0;
|
75
|
-
for (const byte of buffer) {
|
76
|
-
bytes.push(byte);
|
77
|
-
// @see https://encoding.spec.whatwg.org/#utf-8-bytes-needed
|
78
|
-
if (needed) needed--;
|
79
|
-
else if (0xc2 <= byte && byte <= 0xdf) needed = 1;
|
80
|
-
else if (0xe0 <= byte && byte <= 0xef) needed = 2;
|
81
|
-
else if (0xf0 <= byte && byte <= 0xf4) needed = 3;
|
82
|
-
if (!needed) {
|
83
|
-
written += bytes.length;
|
84
|
-
stdio(new Uint8Array(bytes.splice(0)));
|
85
|
-
}
|
86
|
-
}
|
87
|
-
return written;
|
88
|
-
};
|
89
|
-
};
|
90
|
-
|
91
|
-
io.stdout = bufferPoints(generic.write);
|
92
|
-
|
93
|
-
// tiny shim of the code module with only interact
|
94
|
-
// to bootstrap a REPL like environment
|
95
|
-
interpreter.registerJsModule("code", {
|
96
|
-
interact() {
|
97
|
-
let input = "";
|
98
|
-
let length = 1;
|
99
|
-
|
100
|
-
const encoder = new TextEncoder();
|
101
|
-
const acc = [];
|
102
|
-
const handlePoints = bufferPoints((buffer) => {
|
103
|
-
acc.push(...buffer);
|
104
|
-
pyterminal_write(decoder.decode(buffer));
|
105
|
-
});
|
106
|
-
|
107
|
-
// avoid duplicating the output produced by the input
|
108
|
-
io.stdout = (buffer) =>
|
109
|
-
length++ > input.length ? handlePoints(buffer) : 0;
|
110
|
-
|
111
|
-
interpreter.replInit();
|
112
|
-
|
113
|
-
// loop forever waiting for user inputs
|
114
|
-
(function repl() {
|
115
|
-
const out = decoder.decode(new Uint8Array(acc.splice(0)));
|
116
|
-
// print in current line only the last line produced by the REPL
|
117
|
-
const data = `${pyterminal_read(out.split("\n").at(-1))}\r`;
|
118
|
-
length = 0;
|
119
|
-
input = encoder.encode(data);
|
120
|
-
for (const c of input) interpreter.replProcessChar(c);
|
121
|
-
repl();
|
122
|
-
})();
|
123
|
-
},
|
124
|
-
});
|
125
|
-
} else {
|
126
|
-
interpreter.setStdout(generic);
|
127
|
-
interpreter.setStderr(generic);
|
128
|
-
interpreter.setStdin({
|
129
|
-
isatty: false,
|
130
|
-
stdin: () => pyterminal_read(data),
|
131
|
-
});
|
132
|
-
}
|
133
|
-
};
|
134
|
-
|
135
|
-
const pyTerminal = async (element) => {
|
136
|
-
// lazy load these only when a valid terminal is found
|
137
|
-
const [{ Terminal }, { Readline }, { FitAddon }, { WebLinksAddon }] =
|
138
|
-
await Promise.all([
|
139
|
-
import(/* webpackIgnore: true */ "../3rd-party/xterm.js"),
|
140
|
-
import(/* webpackIgnore: true */ "../3rd-party/xterm-readline.js"),
|
141
|
-
import(/* webpackIgnore: true */ "../3rd-party/xterm_addon-fit.js"),
|
142
|
-
import(
|
143
|
-
/* webpackIgnore: true */ "../3rd-party/xterm_addon-web-links.js"
|
144
|
-
),
|
145
|
-
]);
|
146
|
-
|
147
|
-
const readline = new Readline();
|
148
|
-
|
149
|
-
// common main thread initialization for both worker
|
150
|
-
// or main case, bootstrapping the terminal on its target
|
151
|
-
const init = (options) => {
|
152
|
-
let target = element;
|
153
|
-
const selector = element.getAttribute("target");
|
154
|
-
if (selector) {
|
155
|
-
target =
|
156
|
-
document.getElementById(selector) ||
|
157
|
-
document.querySelector(selector);
|
158
|
-
if (!target) throw new Error(`Unknown target ${selector}`);
|
159
|
-
} else {
|
160
|
-
target = document.createElement("py-terminal");
|
161
|
-
target.style.display = "block";
|
162
|
-
element.after(target);
|
163
|
-
}
|
164
|
-
const terminal = new Terminal({
|
165
|
-
theme: {
|
166
|
-
background: "#191A19",
|
167
|
-
foreground: "#F5F2E7",
|
168
|
-
},
|
169
|
-
...options,
|
170
|
-
});
|
171
|
-
const fitAddon = new FitAddon();
|
172
|
-
terminal.loadAddon(fitAddon);
|
173
|
-
terminal.loadAddon(readline);
|
174
|
-
terminal.loadAddon(new WebLinksAddon());
|
175
|
-
terminal.open(target);
|
176
|
-
fitAddon.fit();
|
177
|
-
terminal.focus();
|
178
|
-
defineProperties(element, {
|
179
|
-
terminal: { value: terminal },
|
180
|
-
process: {
|
181
|
-
value: async (code) => {
|
182
|
-
// this loop is the only way I could find to actually simulate
|
183
|
-
// the user input char after char in a way that works in both
|
184
|
-
// MicroPython and Pyodide
|
185
|
-
for (const line of code.split(/(?:\r|\n|\r\n)/)) {
|
186
|
-
terminal.paste(`${line}\n`);
|
187
|
-
do {
|
188
|
-
await new Promise((resolve) =>
|
189
|
-
setTimeout(resolve, 0),
|
190
|
-
);
|
191
|
-
} while (!readline.activeRead?.resolve);
|
192
|
-
readline.activeRead.resolve(line);
|
193
|
-
}
|
194
|
-
},
|
195
|
-
},
|
196
|
-
});
|
197
|
-
return terminal;
|
198
|
-
};
|
199
|
-
|
200
|
-
// branch logic for the worker
|
201
|
-
if (element.hasAttribute("worker")) {
|
202
|
-
// add a hook on the main thread to setup all sync helpers
|
203
|
-
// also bootstrapping the XTerm target on main *BUT* ...
|
204
|
-
hooks.main.onWorker.add(function worker(_, xworker) {
|
205
|
-
// ... as multiple workers will add multiple callbacks
|
206
|
-
// be sure no xworker is ever initialized twice!
|
207
|
-
if (bootstrapped.has(xworker)) return;
|
208
|
-
bootstrapped.add(xworker);
|
209
|
-
|
210
|
-
// still cleanup this callback for future scripts/workers
|
211
|
-
hooks.main.onWorker.delete(worker);
|
212
|
-
|
213
|
-
init({
|
214
|
-
disableStdin: false,
|
215
|
-
cursorBlink: true,
|
216
|
-
cursorStyle: "block",
|
217
|
-
});
|
218
|
-
|
219
|
-
xworker.sync.is_pyterminal = () => true;
|
220
|
-
xworker.sync.pyterminal_read = readline.read.bind(readline);
|
221
|
-
xworker.sync.pyterminal_write = readline.write.bind(readline);
|
222
|
-
});
|
223
|
-
|
224
|
-
// setup remote thread JS/Python code for whenever the
|
225
|
-
// worker is ready to become a terminal
|
226
|
-
hooks.worker.onReady.add(workerReady);
|
227
|
-
} else {
|
228
|
-
// in the main case, just bootstrap XTerm without
|
229
|
-
// allowing any input as that's not possible / awkward
|
230
|
-
hooks.main.onReady.add(function main({ interpreter, io, run, type }) {
|
231
|
-
console.warn("py-terminal is read only on main thread");
|
232
|
-
hooks.main.onReady.delete(main);
|
233
|
-
|
234
|
-
// on main, it's easy to trash and clean the current terminal
|
235
|
-
globalThis.__py_terminal__ = init({
|
236
|
-
disableStdin: true,
|
237
|
-
cursorBlink: false,
|
238
|
-
cursorStyle: "underline",
|
239
|
-
});
|
240
|
-
run("from js import __py_terminal__ as __terminal__");
|
241
|
-
delete globalThis.__py_terminal__;
|
242
|
-
|
243
|
-
io.stderr = (error) => {
|
244
|
-
readline.write(String(error.message || error));
|
245
|
-
};
|
246
|
-
|
247
|
-
if (type === "mpy") {
|
248
|
-
interpreter.setStdin = Object; // as no-op
|
249
|
-
interpreter.setStderr = Object; // as no-op
|
250
|
-
interpreter.setStdout = ({ write }) => {
|
251
|
-
io.stdout = write;
|
252
|
-
};
|
253
|
-
}
|
254
|
-
|
255
|
-
let data = "";
|
256
|
-
const decoder = new TextDecoder();
|
257
|
-
const generic = {
|
258
|
-
isatty: false,
|
259
|
-
write(buffer) {
|
260
|
-
data = decoder.decode(buffer);
|
261
|
-
readline.write(data);
|
262
|
-
return buffer.length;
|
263
|
-
},
|
264
|
-
};
|
265
|
-
interpreter.setStdout(generic);
|
266
|
-
interpreter.setStderr(generic);
|
267
|
-
interpreter.setStdin({
|
268
|
-
isatty: false,
|
269
|
-
stdin: () => readline.read(data),
|
270
|
-
});
|
271
|
-
});
|
272
|
-
}
|
273
|
-
};
|
274
|
-
|
275
|
-
for (const key of TYPES.keys()) {
|
276
|
-
const selector = `script[type="${key}"][terminal],${key}-script[terminal]`;
|
23
|
+
for (const type of TYPES.keys()) {
|
24
|
+
const selector = `script[type="${type}"][terminal],${type}-script[terminal]`;
|
277
25
|
SELECTORS.push(selector);
|
278
26
|
customObserver.set(selector, async (element) => {
|
279
27
|
// we currently support only one terminal on main as in "classic"
|
@@ -292,6 +40,21 @@ for (const key of TYPES.keys()) {
|
|
292
40
|
);
|
293
41
|
}
|
294
42
|
|
295
|
-
|
43
|
+
if (processed.has(element)) return;
|
44
|
+
processed.add(element);
|
45
|
+
|
46
|
+
const bootstrap = (module) => module.default(element);
|
47
|
+
|
48
|
+
// we can't be smart with template literals for the dynamic import
|
49
|
+
// or bundlers are incapable of producing multiple files around
|
50
|
+
if (type === "mpy") {
|
51
|
+
await import(/* webpackIgnore: true */ "./py-terminal/mpy.js").then(
|
52
|
+
bootstrap,
|
53
|
+
);
|
54
|
+
} else {
|
55
|
+
await import(/* webpackIgnore: true */ "./py-terminal/py.js").then(
|
56
|
+
bootstrap,
|
57
|
+
);
|
58
|
+
}
|
296
59
|
});
|
297
60
|
}
|
package/src/stdlib/pyscript.js
CHANGED
@@ -13,6 +13,6 @@ export default {
|
|
13
13
|
"pyweb": {
|
14
14
|
"__init__.py": "from .pydom import dom as pydom\n",
|
15
15
|
"media.py": "from pyodide.ffi import to_js\nfrom pyscript import window\n\n\nclass Device:\n \"\"\"Device represents a media input or output device, such as a microphone,\n camera, or headset.\n \"\"\"\n\n def __init__(self, device):\n self._js = device\n\n @property\n def id(self):\n return self._js.deviceId\n\n @property\n def group(self):\n return self._js.groupId\n\n @property\n def kind(self):\n return self._js.kind\n\n @property\n def label(self):\n return self._js.label\n\n def __getitem__(self, key):\n return getattr(self, key)\n\n @classmethod\n async def load(cls, audio=False, video=True):\n \"\"\"Load the device stream.\"\"\"\n options = window.Object.new()\n options.audio = audio\n if isinstance(video, bool):\n options.video = video\n else:\n # TODO: Think this can be simplified but need to check it on the pyodide side\n\n # TODO: this is pyodide specific. shouldn't be!\n options.video = window.Object.new()\n for k in video:\n setattr(\n options.video,\n k,\n to_js(video[k], dict_converter=window.Object.fromEntries),\n )\n\n stream = await window.navigator.mediaDevices.getUserMedia(options)\n return stream\n\n async def get_stream(self):\n key = self.kind.replace(\"input\", \"\").replace(\"output\", \"\")\n options = {key: {\"deviceId\": {\"exact\": self.id}}}\n\n return await self.load(**options)\n\n\nasync def list_devices() -> list[dict]:\n \"\"\"\n Return the list of the currently available media input and output devices,\n such as microphones, cameras, headsets, and so forth.\n\n Output:\n\n list(dict) - list of dictionaries representing the available media devices.\n Each dictionary has the following keys:\n * deviceId: a string that is an identifier for the represented device\n that is persisted across sessions. It is un-guessable by other\n applications and unique to the origin of the calling application.\n It is reset when the user clears cookies (for Private Browsing, a\n different identifier is used that is not persisted across sessions).\n\n * groupId: a string that is a group identifier. Two devices have the same\n group identifier if they belong to the same physical device — for\n example a monitor with both a built-in camera and a microphone.\n\n * kind: an enumerated value that is either \"videoinput\", \"audioinput\"\n or \"audiooutput\".\n\n * label: a string describing this device (for example \"External USB\n Webcam\").\n\n Note: the returned list will omit any devices that are blocked by the document\n Permission Policy: microphone, camera, speaker-selection (for output devices),\n and so on. Access to particular non-default devices is also gated by the\n Permissions API, and the list will omit devices for which the user has not\n granted explicit permission.\n \"\"\"\n # https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/enumerateDevices\n return [\n Device(obj) for obj in await window.navigator.mediaDevices.enumerateDevices()\n ]\n",
|
16
|
-
"pydom.py": "try:\n from typing import Any\nexcept ImportError:\n Any = \"Any\"\n\ntry:\n import warnings\nexcept ImportError:\n # TODO: For now it probably means we are in MicroPython. We should figure\n # out the \"right\" way to handle this. For now we just ignore the warning\n # and logging to console\n class warnings:\n @staticmethod\n def warn(*args, **kwargs):\n print(\"WARNING: \", *args, **kwargs)\n\n\ntry:\n from functools import cached_property\nexcept ImportError:\n # TODO: same comment about micropython as above\n cached_property = property\n\ntry:\n from pyodide.ffi import JsProxy\nexcept ImportError:\n # TODO: same comment about micropython as above\n def JsProxy(obj):\n return obj\n\n\nfrom pyscript import display, document, window\n\nalert = window.alert\n\n\nclass BaseElement:\n def __init__(self, js_element):\n self._js = js_element\n self._parent = None\n self.style = StyleProxy(self)\n self._proxies = {}\n\n def __eq__(self, obj):\n \"\"\"Check if the element is the same as the other element by comparing\n the underlying JS element\"\"\"\n return isinstance(obj, BaseElement) and obj._js == self._js\n\n @property\n def parent(self):\n if self._parent:\n return self._parent\n\n if self._js.parentElement:\n self._parent = self.__class__(self._js.parentElement)\n\n return self._parent\n\n @property\n def __class(self):\n return self.__class__ if self.__class__ != PyDom else Element\n\n def create(self, type_, is_child=True, classes=None, html=None, label=None):\n js_el = document.createElement(type_)\n element = self.__class(js_el)\n\n if classes:\n for class_ in classes:\n element.add_class(class_)\n\n if html is not None:\n element.html = html\n\n if label is not None:\n element.label = label\n\n if is_child:\n self.append(element)\n\n return element\n\n def find(self, selector):\n \"\"\"Return an ElementCollection representing all the child elements that\n match the specified selector.\n\n Args:\n selector (str): A string containing a selector expression\n\n Returns:\n ElementCollection: A collection of elements matching the selector\n \"\"\"\n elements = self._js.querySelectorAll(selector)\n if not elements:\n return None\n return ElementCollection([Element(el) for el in elements])\n\n\nclass Element(BaseElement):\n @property\n def children(self):\n return [self.__class__(el) for el in self._js.children]\n\n def append(self, child):\n # TODO: this is Pyodide specific for now!!!!!!\n # if we get passed a JSProxy Element directly we just map it to the\n # higher level Python element\n if isinstance(child, JsProxy):\n return self.append(Element(child))\n\n elif isinstance(child, Element):\n self._js.appendChild(child._js)\n\n return child\n\n elif isinstance(child, ElementCollection):\n for el in child:\n self.append(el)\n\n # -------- Pythonic Interface to Element -------- #\n @property\n def html(self):\n return self._js.innerHTML\n\n @html.setter\n def html(self, value):\n self._js.innerHTML = value\n\n @property\n def text(self):\n return self._js.textContent\n\n @text.setter\n def text(self, value):\n self._js.textContent = value\n\n @property\n def content(self):\n # TODO: This breaks with with standard template elements. Define how to best\n # handle this specifica use case. Just not support for now?\n if self._js.tagName == \"TEMPLATE\":\n warnings.warn(\n \"Content attribute not supported for template elements.\", stacklevel=2\n )\n return None\n return self._js.innerHTML\n\n @content.setter\n def content(self, value):\n # TODO: (same comment as above)\n if self._js.tagName == \"TEMPLATE\":\n warnings.warn(\n \"Content attribute not supported for template elements.\", stacklevel=2\n )\n return\n\n display(value, target=self.id)\n\n @property\n def id(self):\n return self._js.id\n\n @id.setter\n def id(self, value):\n self._js.id = value\n\n @property\n def options(self):\n if \"options\" in self._proxies:\n return self._proxies[\"options\"]\n\n if not self._js.tagName.lower() in {\"select\", \"datalist\", \"optgroup\"}:\n raise AttributeError(\n f\"Element {self._js.tagName} has no options attribute.\"\n )\n self._proxies[\"options\"] = OptionsProxy(self)\n return self._proxies[\"options\"]\n\n @property\n def value(self):\n return self._js.value\n\n @value.setter\n def value(self, value):\n # in order to avoid confusion to the user, we don't allow setting the\n # value of elements that don't have a value attribute\n if not hasattr(self._js, \"value\"):\n raise AttributeError(\n f\"Element {self._js.tagName} has no value attribute. If you want to \"\n \"force a value attribute, set it directly using the `_js.value = <value>` \"\n \"javascript API attribute instead.\"\n )\n self._js.value = value\n\n @property\n def selected(self):\n return self._js.selected\n\n @selected.setter\n def selected(self, value):\n # in order to avoid confusion to the user, we don't allow setting the\n # value of elements that don't have a value attribute\n if not hasattr(self._js, \"selected\"):\n raise AttributeError(\n f\"Element {self._js.tagName} has no value attribute. If you want to \"\n \"force a value attribute, set it directly using the `_js.value = <value>` \"\n \"javascript API attribute instead.\"\n )\n self._js.selected = value\n\n def clone(self, new_id=None):\n clone = Element(self._js.cloneNode(True))\n clone.id = new_id\n\n return clone\n\n def remove_class(self, classname):\n classList = self._js.classList\n if isinstance(classname, list):\n classList.remove(*classname)\n else:\n classList.remove(classname)\n return self\n\n def add_class(self, classname):\n classList = self._js.classList\n if isinstance(classname, list):\n classList.add(*classname)\n else:\n self._js.classList.add(classname)\n return self\n\n @property\n def classes(self):\n classes = self._js.classList.values()\n return [x for x in classes]\n\n def show_me(self):\n self._js.scrollIntoView()\n\n def snap(\n self,\n to: BaseElement | str = None,\n width: int | None = None,\n height: int | None = None,\n ):\n \"\"\"\n Captures a snapshot of a video element. (Only available for video elements)\n\n Inputs:\n\n * to: element where to save the snapshot of the video frame to\n * width: width of the image\n * height: height of the image\n\n Output:\n (Element) canvas element where the video frame snapshot was drawn into\n \"\"\"\n if self._js.tagName != \"VIDEO\":\n raise AttributeError(\"Snap method is only available for video Elements\")\n\n if to is None:\n canvas = self.create(\"canvas\")\n if width is None:\n width = self._js.width\n if height is None:\n height = self._js.height\n canvas._js.width = width\n canvas._js.height = height\n\n elif isistance(to, Element):\n if to._js.tagName != \"CANVAS\":\n raise TypeError(\"Element to snap to must a canvas.\")\n canvas = to\n elif getattr(to, \"tagName\", \"\") == \"CANVAS\":\n canvas = Element(to)\n elif isinstance(to, str):\n canvas = pydom[to][0]\n if canvas._js.tagName != \"CANVAS\":\n raise TypeError(\"Element to snap to must a be canvas.\")\n\n canvas.draw(self, width, height)\n\n return canvas\n\n def download(self, filename: str = \"snapped.png\") -> None:\n \"\"\"Download the current element (only available for canvas elements) with the filename\n provided in input.\n\n Inputs:\n * filename (str): name of the file being downloaded\n\n Output:\n None\n \"\"\"\n if self._js.tagName != \"CANVAS\":\n raise AttributeError(\n \"The download method is only available for canvas Elements\"\n )\n\n link = self.create(\"a\")\n link._js.download = filename\n link._js.href = self._js.toDataURL()\n link._js.click()\n\n def draw(self, what, width, height):\n \"\"\"Draw `what` on the current element (only available for canvas elements).\n\n Inputs:\n\n * what (canvas image source): An element to draw into the context. The specification permits any canvas\n image source, specifically, an HTMLImageElement, an SVGImageElement, an HTMLVideoElement,\n an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a VideoFrame.\n \"\"\"\n if self._js.tagName != \"CANVAS\":\n raise AttributeError(\n \"The draw method is only available for canvas Elements\"\n )\n\n if isinstance(what, Element):\n what = what._js\n\n # https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage\n self._js.getContext(\"2d\").drawImage(what, 0, 0, width, height)\n\n\nclass OptionsProxy:\n \"\"\"This class represents the options of a select element. It\n allows to access to add and remove options by using the `add` and `remove` methods.\n \"\"\"\n\n def __init__(self, element: Element) -> None:\n self._element = element\n if self._element._js.tagName.lower() != \"select\":\n raise AttributeError(\n f\"Element {self._element._js.tagName} has no options attribute.\"\n )\n\n def add(\n self,\n value: Any = None,\n html: str = None,\n text: str = None,\n before: Element | int = None,\n **kws,\n ) -> None:\n \"\"\"Add a new option to the select element\"\"\"\n # create the option element and set the attributes\n option = document.createElement(\"option\")\n if value is not None:\n kws[\"value\"] = value\n if html is not None:\n option.innerHTML = html\n if text is not None:\n kws[\"text\"] = text\n\n for key, value in kws.items():\n option.setAttribute(key, value)\n\n if before:\n if isinstance(before, Element):\n before = before._js\n\n self._element._js.add(option, before)\n\n def remove(self, item: int) -> None:\n \"\"\"Remove the option at the specified index\"\"\"\n self._element._js.remove(item)\n\n def clear(self) -> None:\n \"\"\"Remove all the options\"\"\"\n for i in range(len(self)):\n self.remove(0)\n\n @property\n def options(self):\n \"\"\"Return the list of options\"\"\"\n return [Element(opt) for opt in self._element._js.options]\n\n @property\n def selected(self):\n \"\"\"Return the selected option\"\"\"\n return self.options[self._element._js.selectedIndex]\n\n def __iter__(self):\n yield from self.options\n\n def __len__(self):\n return len(self.options)\n\n def __repr__(self):\n return f\"{self.__class__.__name__} (length: {len(self)}) {self.options}\"\n\n def __getitem__(self, key):\n return self.options[key]\n\n\nclass StyleProxy: # (dict):\n def __init__(self, element: Element) -> None:\n self._element = element\n\n @cached_property\n def _style(self):\n return self._element._js.style\n\n def __getitem__(self, key):\n return self._style.getPropertyValue(key)\n\n def __setitem__(self, key, value):\n self._style.setProperty(key, value)\n\n def remove(self, key):\n self._style.removeProperty(key)\n\n def set(self, **kws):\n for k, v in kws.items():\n self._element._js.style.setProperty(k, v)\n\n # CSS Properties\n # Reference: https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L3799C1-L5005C2\n # Following prperties automatically generated from the above reference using\n # tools/codegen_css_proxy.py\n @property\n def visible(self):\n return self._element._js.style.visibility\n\n @visible.setter\n def visible(self, value):\n self._element._js.style.visibility = value\n\n\nclass StyleCollection:\n def __init__(self, collection: \"ElementCollection\") -> None:\n self._collection = collection\n\n def __get__(self, obj, objtype=None):\n return obj._get_attribute(\"style\")\n\n def __getitem__(self, key):\n return self._collection._get_attribute(\"style\")[key]\n\n def __setitem__(self, key, value):\n for element in self._collection._elements:\n element.style[key] = value\n\n def remove(self, key):\n for element in self._collection._elements:\n element.style.remove(key)\n\n\nclass ElementCollection:\n def __init__(self, elements: [Element]) -> None:\n self._elements = elements\n self.style = StyleCollection(self)\n\n def __getitem__(self, key):\n # If it's an integer we use it to access the elements in the collection\n if isinstance(key, int):\n return self._elements[key]\n # If it's a slice we use it to support slice operations over the elements\n # in the collection\n elif isinstance(key, slice):\n return ElementCollection(self._elements[key])\n\n # If it's anything else (basically a string) we use it as a selector\n # TODO: Write tests!\n elements = self._element.querySelectorAll(key)\n return ElementCollection([Element(el) for el in elements])\n\n def __len__(self):\n return len(self._elements)\n\n def __eq__(self, obj):\n \"\"\"Check if the element is the same as the other element by comparing\n the underlying JS element\"\"\"\n return isinstance(obj, ElementCollection) and obj._elements == self._elements\n\n def _get_attribute(self, attr, index=None):\n if index is None:\n return [getattr(el, attr) for el in self._elements]\n\n # As JQuery, when getting an attr, only return it for the first element\n return getattr(self._elements[index], attr)\n\n def _set_attribute(self, attr, value):\n for el in self._elements:\n setattr(el, attr, value)\n\n @property\n def html(self):\n return self._get_attribute(\"html\")\n\n @html.setter\n def html(self, value):\n self._set_attribute(\"html\", value)\n\n @property\n def value(self):\n return self._get_attribute(\"value\")\n\n @value.setter\n def value(self, value):\n self._set_attribute(\"value\", value)\n\n @property\n def children(self):\n return self._elements\n\n def __iter__(self):\n yield from self._elements\n\n def __repr__(self):\n return f\"{self.__class__.__name__} (length: {len(self._elements)}) {self._elements}\"\n\n\nclass DomScope:\n def __getattr__(self, __name: str):\n element = document[f\"#{__name}\"]\n if element:\n return element[0]\n\n\nclass PyDom(BaseElement):\n # Add objects we want to expose to the DOM namespace since this class instance is being\n # remapped as \"the module\" itself\n BaseElement = BaseElement\n Element = Element\n ElementCollection = ElementCollection\n\n def __init__(self):\n # PyDom is a special case of BaseElement where we don't want to create a new JS element\n # and it really doesn't have a need for styleproxy or parent to to call to __init__\n # (which actually fails in MP for some reason)\n self._js = document\n self._parent = None\n self._proxies = {}\n self.ids = DomScope()\n self.body = Element(document.body)\n self.head = Element(document.head)\n\n def create(self, type_, classes=None, html=None):\n return super().create(type_, is_child=False, classes=classes, html=html)\n\n def __getitem__(self, key):\n elements = self._js.querySelectorAll(key)\n if not elements:\n return None\n return ElementCollection([Element(el) for el in elements])\n\n\ndom = PyDom()\n"
|
16
|
+
"pydom.py": "try:\n from typing import Any\nexcept ImportError:\n Any = \"Any\"\n\ntry:\n import warnings\nexcept ImportError:\n # TODO: For now it probably means we are in MicroPython. We should figure\n # out the \"right\" way to handle this. For now we just ignore the warning\n # and logging to console\n class warnings:\n @staticmethod\n def warn(*args, **kwargs):\n print(\"WARNING: \", *args, **kwargs)\n\n\ntry:\n from functools import cached_property\nexcept ImportError:\n # TODO: same comment about micropython as above\n cached_property = property\n\ntry:\n from pyodide.ffi import JsProxy\nexcept ImportError:\n # TODO: same comment about micropython as above\n def JsProxy(obj):\n return obj\n\n\nfrom pyscript import display, document, window\n\nalert = window.alert\n\n\nclass BaseElement:\n def __init__(self, js_element):\n self._js = js_element\n self._parent = None\n self.style = StyleProxy(self)\n self._proxies = {}\n\n def __eq__(self, obj):\n \"\"\"Check if the element is the same as the other element by comparing\n the underlying JS element\"\"\"\n return isinstance(obj, BaseElement) and obj._js == self._js\n\n @property\n def parent(self):\n if self._parent:\n return self._parent\n\n if self._js.parentElement:\n self._parent = self.__class__(self._js.parentElement)\n\n return self._parent\n\n @property\n def __class(self):\n return self.__class__ if self.__class__ != PyDom else Element\n\n def create(self, type_, is_child=True, classes=None, html=None, label=None):\n js_el = document.createElement(type_)\n element = self.__class(js_el)\n\n if classes:\n for class_ in classes:\n element.add_class(class_)\n\n if html is not None:\n element.html = html\n\n if label is not None:\n element.label = label\n\n if is_child:\n self.append(element)\n\n return element\n\n def find(self, selector):\n \"\"\"Return an ElementCollection representing all the child elements that\n match the specified selector.\n\n Args:\n selector (str): A string containing a selector expression\n\n Returns:\n ElementCollection: A collection of elements matching the selector\n \"\"\"\n elements = self._js.querySelectorAll(selector)\n if not elements:\n return None\n return ElementCollection([Element(el) for el in elements])\n\n\nclass Element(BaseElement):\n @property\n def children(self):\n return [self.__class__(el) for el in self._js.children]\n\n def append(self, child):\n # TODO: this is Pyodide specific for now!!!!!!\n # if we get passed a JSProxy Element directly we just map it to the\n # higher level Python element\n if isinstance(child, JsProxy):\n return self.append(Element(child))\n\n elif isinstance(child, Element):\n self._js.appendChild(child._js)\n\n return child\n\n elif isinstance(child, ElementCollection):\n for el in child:\n self.append(el)\n\n # -------- Pythonic Interface to Element -------- #\n @property\n def html(self):\n return self._js.innerHTML\n\n @html.setter\n def html(self, value):\n self._js.innerHTML = value\n\n @property\n def text(self):\n return self._js.textContent\n\n @text.setter\n def text(self, value):\n self._js.textContent = value\n\n @property\n def content(self):\n # TODO: This breaks with with standard template elements. Define how to best\n # handle this specifica use case. Just not support for now?\n if self._js.tagName == \"TEMPLATE\":\n warnings.warn(\n \"Content attribute not supported for template elements.\", stacklevel=2\n )\n return None\n return self._js.innerHTML\n\n @content.setter\n def content(self, value):\n # TODO: (same comment as above)\n if self._js.tagName == \"TEMPLATE\":\n warnings.warn(\n \"Content attribute not supported for template elements.\", stacklevel=2\n )\n return\n\n display(value, target=self.id)\n\n @property\n def id(self):\n return self._js.id\n\n @id.setter\n def id(self, value):\n self._js.id = value\n\n @property\n def options(self):\n if \"options\" in self._proxies:\n return self._proxies[\"options\"]\n\n if not self._js.tagName.lower() in {\"select\", \"datalist\", \"optgroup\"}:\n raise AttributeError(\n f\"Element {self._js.tagName} has no options attribute.\"\n )\n self._proxies[\"options\"] = OptionsProxy(self)\n return self._proxies[\"options\"]\n\n @property\n def value(self):\n return self._js.value\n\n @value.setter\n def value(self, value):\n # in order to avoid confusion to the user, we don't allow setting the\n # value of elements that don't have a value attribute\n if not hasattr(self._js, \"value\"):\n raise AttributeError(\n f\"Element {self._js.tagName} has no value attribute. If you want to \"\n \"force a value attribute, set it directly using the `_js.value = <value>` \"\n \"javascript API attribute instead.\"\n )\n self._js.value = value\n\n @property\n def selected(self):\n return self._js.selected\n\n @selected.setter\n def selected(self, value):\n # in order to avoid confusion to the user, we don't allow setting the\n # value of elements that don't have a value attribute\n if not hasattr(self._js, \"selected\"):\n raise AttributeError(\n f\"Element {self._js.tagName} has no value attribute. If you want to \"\n \"force a value attribute, set it directly using the `_js.value = <value>` \"\n \"javascript API attribute instead.\"\n )\n self._js.selected = value\n\n def clone(self, new_id=None):\n clone = Element(self._js.cloneNode(True))\n clone.id = new_id\n\n return clone\n\n def remove_class(self, classname):\n classList = self._js.classList\n if isinstance(classname, list):\n classList.remove(*classname)\n else:\n classList.remove(classname)\n return self\n\n def add_class(self, classname):\n classList = self._js.classList\n if isinstance(classname, list):\n classList.add(*classname)\n else:\n self._js.classList.add(classname)\n return self\n\n @property\n def classes(self):\n classes = self._js.classList.values()\n return [x for x in classes]\n\n def show_me(self):\n self._js.scrollIntoView()\n\n def snap(\n self,\n to: BaseElement | str = None,\n width: int | None = None,\n height: int | None = None,\n ):\n \"\"\"\n Captures a snapshot of a video element. (Only available for video elements)\n\n Inputs:\n\n * to: element where to save the snapshot of the video frame to\n * width: width of the image\n * height: height of the image\n\n Output:\n (Element) canvas element where the video frame snapshot was drawn into\n \"\"\"\n if self._js.tagName != \"VIDEO\":\n raise AttributeError(\"Snap method is only available for video Elements\")\n\n if to is None:\n canvas = self.create(\"canvas\")\n if width is None:\n width = self._js.width\n if height is None:\n height = self._js.height\n canvas._js.width = width\n canvas._js.height = height\n\n elif isinstance(to, Element):\n if to._js.tagName != \"CANVAS\":\n raise TypeError(\"Element to snap to must a canvas.\")\n canvas = to\n elif getattr(to, \"tagName\", \"\") == \"CANVAS\":\n canvas = Element(to)\n elif isinstance(to, str):\n canvas = pydom[to][0]\n if canvas._js.tagName != \"CANVAS\":\n raise TypeError(\"Element to snap to must a be canvas.\")\n\n canvas.draw(self, width, height)\n\n return canvas\n\n def download(self, filename: str = \"snapped.png\") -> None:\n \"\"\"Download the current element (only available for canvas elements) with the filename\n provided in input.\n\n Inputs:\n * filename (str): name of the file being downloaded\n\n Output:\n None\n \"\"\"\n if self._js.tagName != \"CANVAS\":\n raise AttributeError(\n \"The download method is only available for canvas Elements\"\n )\n\n link = self.create(\"a\")\n link._js.download = filename\n link._js.href = self._js.toDataURL()\n link._js.click()\n\n def draw(self, what, width, height):\n \"\"\"Draw `what` on the current element (only available for canvas elements).\n\n Inputs:\n\n * what (canvas image source): An element to draw into the context. The specification permits any canvas\n image source, specifically, an HTMLImageElement, an SVGImageElement, an HTMLVideoElement,\n an HTMLCanvasElement, an ImageBitmap, an OffscreenCanvas, or a VideoFrame.\n \"\"\"\n if self._js.tagName != \"CANVAS\":\n raise AttributeError(\n \"The draw method is only available for canvas Elements\"\n )\n\n if isinstance(what, Element):\n what = what._js\n\n # https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage\n self._js.getContext(\"2d\").drawImage(what, 0, 0, width, height)\n\n\nclass OptionsProxy:\n \"\"\"This class represents the options of a select element. It\n allows to access to add and remove options by using the `add` and `remove` methods.\n \"\"\"\n\n def __init__(self, element: Element) -> None:\n self._element = element\n if self._element._js.tagName.lower() != \"select\":\n raise AttributeError(\n f\"Element {self._element._js.tagName} has no options attribute.\"\n )\n\n def add(\n self,\n value: Any = None,\n html: str = None,\n text: str = None,\n before: Element | int = None,\n **kws,\n ) -> None:\n \"\"\"Add a new option to the select element\"\"\"\n # create the option element and set the attributes\n option = document.createElement(\"option\")\n if value is not None:\n kws[\"value\"] = value\n if html is not None:\n option.innerHTML = html\n if text is not None:\n kws[\"text\"] = text\n\n for key, value in kws.items():\n option.setAttribute(key, value)\n\n if before:\n if isinstance(before, Element):\n before = before._js\n\n self._element._js.add(option, before)\n\n def remove(self, item: int) -> None:\n \"\"\"Remove the option at the specified index\"\"\"\n self._element._js.remove(item)\n\n def clear(self) -> None:\n \"\"\"Remove all the options\"\"\"\n for i in range(len(self)):\n self.remove(0)\n\n @property\n def options(self):\n \"\"\"Return the list of options\"\"\"\n return [Element(opt) for opt in self._element._js.options]\n\n @property\n def selected(self):\n \"\"\"Return the selected option\"\"\"\n return self.options[self._element._js.selectedIndex]\n\n def __iter__(self):\n yield from self.options\n\n def __len__(self):\n return len(self.options)\n\n def __repr__(self):\n return f\"{self.__class__.__name__} (length: {len(self)}) {self.options}\"\n\n def __getitem__(self, key):\n return self.options[key]\n\n\nclass StyleProxy: # (dict):\n def __init__(self, element: Element) -> None:\n self._element = element\n\n @cached_property\n def _style(self):\n return self._element._js.style\n\n def __getitem__(self, key):\n return self._style.getPropertyValue(key)\n\n def __setitem__(self, key, value):\n self._style.setProperty(key, value)\n\n def remove(self, key):\n self._style.removeProperty(key)\n\n def set(self, **kws):\n for k, v in kws.items():\n self._element._js.style.setProperty(k, v)\n\n # CSS Properties\n # Reference: https://github.com/microsoft/TypeScript/blob/main/src/lib/dom.generated.d.ts#L3799C1-L5005C2\n # Following prperties automatically generated from the above reference using\n # tools/codegen_css_proxy.py\n @property\n def visible(self):\n return self._element._js.style.visibility\n\n @visible.setter\n def visible(self, value):\n self._element._js.style.visibility = value\n\n\nclass StyleCollection:\n def __init__(self, collection: \"ElementCollection\") -> None:\n self._collection = collection\n\n def __get__(self, obj, objtype=None):\n return obj._get_attribute(\"style\")\n\n def __getitem__(self, key):\n return self._collection._get_attribute(\"style\")[key]\n\n def __setitem__(self, key, value):\n for element in self._collection._elements:\n element.style[key] = value\n\n def remove(self, key):\n for element in self._collection._elements:\n element.style.remove(key)\n\n\nclass ElementCollection:\n def __init__(self, elements: [Element]) -> None:\n self._elements = elements\n self.style = StyleCollection(self)\n\n def __getitem__(self, key):\n # If it's an integer we use it to access the elements in the collection\n if isinstance(key, int):\n return self._elements[key]\n # If it's a slice we use it to support slice operations over the elements\n # in the collection\n elif isinstance(key, slice):\n return ElementCollection(self._elements[key])\n\n # If it's anything else (basically a string) we use it as a selector\n # TODO: Write tests!\n elements = self._element.querySelectorAll(key)\n return ElementCollection([Element(el) for el in elements])\n\n def __len__(self):\n return len(self._elements)\n\n def __eq__(self, obj):\n \"\"\"Check if the element is the same as the other element by comparing\n the underlying JS element\"\"\"\n return isinstance(obj, ElementCollection) and obj._elements == self._elements\n\n def _get_attribute(self, attr, index=None):\n if index is None:\n return [getattr(el, attr) for el in self._elements]\n\n # As JQuery, when getting an attr, only return it for the first element\n return getattr(self._elements[index], attr)\n\n def _set_attribute(self, attr, value):\n for el in self._elements:\n setattr(el, attr, value)\n\n @property\n def html(self):\n return self._get_attribute(\"html\")\n\n @html.setter\n def html(self, value):\n self._set_attribute(\"html\", value)\n\n @property\n def value(self):\n return self._get_attribute(\"value\")\n\n @value.setter\n def value(self, value):\n self._set_attribute(\"value\", value)\n\n @property\n def children(self):\n return self._elements\n\n def __iter__(self):\n yield from self._elements\n\n def __repr__(self):\n return f\"{self.__class__.__name__} (length: {len(self._elements)}) {self._elements}\"\n\n\nclass DomScope:\n def __getattr__(self, __name: str):\n element = document[f\"#{__name}\"]\n if element:\n return element[0]\n\n\nclass PyDom(BaseElement):\n # Add objects we want to expose to the DOM namespace since this class instance is being\n # remapped as \"the module\" itself\n BaseElement = BaseElement\n Element = Element\n ElementCollection = ElementCollection\n\n def __init__(self):\n # PyDom is a special case of BaseElement where we don't want to create a new JS element\n # and it really doesn't have a need for styleproxy or parent to to call to __init__\n # (which actually fails in MP for some reason)\n self._js = document\n self._parent = None\n self._proxies = {}\n self.ids = DomScope()\n self.body = Element(document.body)\n self.head = Element(document.head)\n\n def create(self, type_, classes=None, html=None):\n return super().create(type_, is_child=False, classes=classes, html=html)\n\n def __getitem__(self, key):\n elements = self._js.querySelectorAll(key)\n if not elements:\n return None\n return ElementCollection([Element(el) for el in elements])\n\n\ndom = PyDom()\n"
|
17
17
|
}
|
18
18
|
};
|
@@ -267,7 +267,7 @@ class Element(BaseElement):
|
|
267
267
|
canvas._js.width = width
|
268
268
|
canvas._js.height = height
|
269
269
|
|
270
|
-
elif
|
270
|
+
elif isinstance(to, Element):
|
271
271
|
if to._js.tagName != "CANVAS":
|
272
272
|
raise TypeError("Element to snap to must a canvas.")
|
273
273
|
canvas = to
|
package/types/core.d.ts
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
export function offline_interpreter(config: any): string;
|
2
2
|
import { stdlib } from "./stdlib.js";
|
3
3
|
import { optional } from "./stdlib.js";
|
4
|
+
import { inputFailure } from "./hooks.js";
|
4
5
|
import TYPES from "./types.js";
|
5
6
|
/**
|
6
7
|
* A `Worker` facade able to bootstrap on the worker thread only a PyScript module.
|
@@ -53,4 +54,4 @@ declare const exportedHooks: {
|
|
53
54
|
};
|
54
55
|
declare const exportedConfig: {};
|
55
56
|
declare const exportedWhenDefined: (type: string) => Promise<any>;
|
56
|
-
export { stdlib, optional, TYPES, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
|
57
|
+
export { stdlib, optional, inputFailure, TYPES, exportedPyWorker as PyWorker, exportedMPWorker as MPWorker, exportedHooks as hooks, exportedConfig as config, exportedWhenDefined as whenDefined };
|
package/types/hooks.d.ts
CHANGED
@@ -2,6 +2,7 @@ export function main(name: any): any;
|
|
2
2
|
export function worker(name: any): any;
|
3
3
|
export function codeFor(branch: any, type: any): {};
|
4
4
|
export function createFunction(self: any, name: any): any;
|
5
|
+
export const inputFailure: "\n import builtins\n def input(prompt=\"\"):\n raise Exception(\"\\n \".join([\n \"input() doesn't work when PyScript runs in the main thread.\",\n \"Consider using the worker attribute: https://pyscript.github.io/docs/2023.11.2/user-guide/workers/\"\n ]))\n\n builtins.input = input\n del builtins\n del input\n";
|
5
6
|
export namespace hooks {
|
6
7
|
namespace main {
|
7
8
|
let onWorker: Set<Function>;
|