code-playpad 0.2.0 → 0.2.1
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/README.md +450 -49
- package/dist/{element-DtCQVStG.js → element-VP5lhHx6.js} +10 -10
- package/dist/element.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{javascript-Cv8I8Gkw.js → javascript-RVtPk08Z.js} +1 -1
- package/dist/{pyodide-B8qzHgzU.js → pyodide-5ByQYqgo.js} +1 -1
- package/dist/react.d.ts +4 -1
- package/dist/react.js +1 -1
- package/dist/standalone.js +9 -9
- package/dist/{worker-runtime-HpUlPmaH.js → worker-runtime-CJ3Zww5E.js} +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,13 +9,20 @@ No backend, no iframe, no sign-up.
|
|
|
9
9
|
**[Live demo →](https://code-playpad.swengr.com)**
|
|
10
10
|
|
|
11
11
|
```html
|
|
12
|
-
<script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.2.
|
|
12
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.2.1"></script>
|
|
13
13
|
|
|
14
|
-
<code-playpad height="
|
|
14
|
+
<code-playpad height="auto">
|
|
15
15
|
print("hello from a static page")
|
|
16
16
|
</code-playpad>
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
**Contents** — [Languages](#languages) · [Install](#install) · [Attributes](#attributes)
|
|
20
|
+
· [Sizing](#sizing) · [Several files](#several-files) · [Input](#input)
|
|
21
|
+
· [Controlling it from JavaScript](#controlling-it-from-javascript)
|
|
22
|
+
· [Events and grading](#events-and-grading) · [Saving work](#saving-work)
|
|
23
|
+
· [Light and dark](#light-and-dark) · [Styling](#styling) · [Framework recipes](#framework-recipes)
|
|
24
|
+
· [Production](#production) · [Worth knowing](#worth-knowing)
|
|
25
|
+
|
|
19
26
|
## Languages
|
|
20
27
|
|
|
21
28
|
| `language` | Runtime | Downloaded at runtime |
|
|
@@ -24,16 +31,42 @@ No backend, no iframe, no sign-up.
|
|
|
24
31
|
| `javascript` | The browser's own engine, in a Worker | nothing |
|
|
25
32
|
| `typescript` | Same engine, types stripped before running | ~50 KB, once, cached |
|
|
26
33
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
```html
|
|
35
|
+
<code-playpad height="auto">print("python is the default")</code-playpad>
|
|
36
|
+
|
|
37
|
+
<code-playpad language="javascript" height="auto">
|
|
38
|
+
const doubled = [1, 2, 3].map((n) => n * 2);
|
|
39
|
+
console.log(doubled);
|
|
40
|
+
doubled.reduce((a, b) => a + b, 0)
|
|
41
|
+
</code-playpad>
|
|
42
|
+
|
|
43
|
+
<code-playpad language="typescript" height="auto">
|
|
44
|
+
type Point = { x: number; y: number };
|
|
45
|
+
const dist = (p: Point): number => Math.hypot(p.x, p.y);
|
|
46
|
+
dist({ x: 3, y: 4 })
|
|
47
|
+
</code-playpad>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
A trailing expression prints its value, like a REPL. Each language loads **only
|
|
51
|
+
on pages that use it**: a Python-only page never downloads a byte of the
|
|
52
|
+
JavaScript runtime, and a JavaScript-only page never touches Pyodide's 11 MB.
|
|
30
53
|
|
|
31
54
|
TypeScript here erases types, it does not check them — a type error will not
|
|
32
55
|
stop your code running. This is a playground, not `tsc`.
|
|
33
56
|
|
|
34
57
|
## Install
|
|
35
58
|
|
|
36
|
-
|
|
59
|
+
### From a CDN — nothing to install
|
|
60
|
+
|
|
61
|
+
```html
|
|
62
|
+
<script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.2.1"></script>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
This is the `standalone` build: CodeMirror is bundled in, so a static blog post
|
|
66
|
+
or a Markdown-rendered page needs only that one tag. Pin the version — an
|
|
67
|
+
unpinned URL follows `latest` and will change under your readers.
|
|
68
|
+
|
|
69
|
+
### From npm, with a bundler
|
|
37
70
|
|
|
38
71
|
```bash
|
|
39
72
|
npm install code-playpad @codemirror/state @codemirror/view @codemirror/commands \
|
|
@@ -41,59 +74,72 @@ npm install code-playpad @codemirror/state @codemirror/view @codemirror/commands
|
|
|
41
74
|
```
|
|
42
75
|
|
|
43
76
|
CodeMirror is a peer dependency so you never end up with two copies of
|
|
44
|
-
`@codemirror/state`. Language grammars
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
`@codemirror/state`. Language grammars are **optional** — install only what you
|
|
78
|
+
use (`@codemirror/lang-python`, `@codemirror/lang-javascript`,
|
|
79
|
+
`@codemirror/lang-json`); a missing one degrades to plain text rather than
|
|
80
|
+
throwing.
|
|
47
81
|
|
|
48
82
|
```js
|
|
49
83
|
import 'code-playpad/element'; // registers <code-playpad>
|
|
50
84
|
```
|
|
51
85
|
|
|
52
|
-
|
|
53
|
-
|
|
86
|
+
If your bundler is configured to drop side-effect-only imports, register
|
|
87
|
+
explicitly instead:
|
|
54
88
|
|
|
55
89
|
```js
|
|
56
90
|
import { defineCodePlayPad } from 'code-playpad/element';
|
|
57
91
|
defineCodePlayPad();
|
|
58
92
|
```
|
|
59
93
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
```jsx
|
|
63
|
-
import { CodePlayPad } from 'code-playpad/react';
|
|
94
|
+
### Entry points
|
|
64
95
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
```
|
|
96
|
+
| Import | You get |
|
|
97
|
+
| --- | --- |
|
|
98
|
+
| `code-playpad/element` | registers `<code-playpad>`; smallest thing that works |
|
|
99
|
+
| `code-playpad` | the same, plus every helper below |
|
|
100
|
+
| `code-playpad/react` | the `<CodePlayPad>` React component |
|
|
101
|
+
| `code-playpad/standalone` | CodeMirror bundled in — what the CDN serves |
|
|
72
102
|
|
|
73
|
-
Importing on a server is safe
|
|
74
|
-
|
|
103
|
+
Importing on a server is safe: without a DOM the element degrades to a no-op, so
|
|
104
|
+
Next.js, Astro and SvelteKit need no dynamic import or `client:only` guard.
|
|
75
105
|
|
|
76
|
-
##
|
|
106
|
+
## Attributes
|
|
77
107
|
|
|
78
108
|
| | |
|
|
79
109
|
| --- | --- |
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
110
|
+
| `language` | `python` (default), `javascript`, `typescript` |
|
|
111
|
+
| `height` | number (px), any CSS length, or `auto` |
|
|
112
|
+
| `min-height` / `max-height` | bounds for `auto` |
|
|
113
|
+
| `theme` | `light`, `dark`, or `auto` (default — follows the OS) |
|
|
114
|
+
| `readonly` | editor cannot be typed into; Run still works |
|
|
115
|
+
| `autorun` | runs once, on its own, when scrolled into view |
|
|
116
|
+
| `stdin` | shows the standard-input box up front |
|
|
117
|
+
| `persist` | key under which edits are saved (see [Saving work](#saving-work)) |
|
|
118
|
+
| `entry` | which file Run executes, for multi-file widgets |
|
|
119
|
+
|
|
120
|
+
```html
|
|
121
|
+
<!-- a worked example nobody should edit -->
|
|
122
|
+
<code-playpad height="auto" readonly>
|
|
123
|
+
print("read this, don't change it")
|
|
124
|
+
</code-playpad>
|
|
84
125
|
|
|
85
|
-
|
|
86
|
-
|
|
126
|
+
<!-- output visible before the reader does anything -->
|
|
127
|
+
<code-playpad height="auto" autorun>
|
|
128
|
+
print("this already ran")
|
|
129
|
+
</code-playpad>
|
|
87
130
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
});
|
|
131
|
+
<!-- always dark, regardless of the reader's OS setting -->
|
|
132
|
+
<code-playpad height="auto" theme="dark">
|
|
133
|
+
print("always dark")
|
|
134
|
+
</code-playpad>
|
|
93
135
|
```
|
|
94
136
|
|
|
95
|
-
|
|
96
|
-
|
|
137
|
+
`readonly` blocks typing but not execution, and the code can still be replaced
|
|
138
|
+
from JavaScript — useful for "show the answer" buttons.
|
|
139
|
+
|
|
140
|
+
Note it is `language`, not `lang`: `lang` is a standard HTML attribute for the
|
|
141
|
+
*human* language of an element's text, and putting `python` there confuses
|
|
142
|
+
screen readers.
|
|
97
143
|
|
|
98
144
|
## Sizing
|
|
99
145
|
|
|
@@ -125,11 +171,11 @@ length (`40vh`, `30em`).
|
|
|
125
171
|
|
|
126
172
|
## Several files
|
|
127
173
|
|
|
128
|
-
A widget can hold a small project. Relative imports, packages
|
|
129
|
-
|
|
174
|
+
A widget can hold a small project. Relative imports, packages and data files all
|
|
175
|
+
work, and tracebacks name the real file and line.
|
|
130
176
|
|
|
131
177
|
```html
|
|
132
|
-
<code-playpad entry="main.py" height="
|
|
178
|
+
<code-playpad entry="main.py" height="auto" max-height="420">
|
|
133
179
|
<playpad-file name="main.py">
|
|
134
180
|
from geometry import Circle
|
|
135
181
|
print(Circle(1).area())
|
|
@@ -149,9 +195,34 @@ all work, and tracebacks name the real file and line.
|
|
|
149
195
|
</code-playpad>
|
|
150
196
|
```
|
|
151
197
|
|
|
198
|
+
JavaScript and TypeScript work the same way, including `.json` imported as data:
|
|
199
|
+
|
|
200
|
+
```html
|
|
201
|
+
<code-playpad language="javascript" entry="main.js" height="auto">
|
|
202
|
+
<playpad-file name="main.js">
|
|
203
|
+
import { total } from "./cart.js";
|
|
204
|
+
import items from "./items.json";
|
|
205
|
+
|
|
206
|
+
console.log(total(items));
|
|
207
|
+
</playpad-file>
|
|
208
|
+
|
|
209
|
+
<playpad-file name="cart.js">
|
|
210
|
+
export const total = (items) =>
|
|
211
|
+
items.reduce((sum, item) => sum + item.price, 0);
|
|
212
|
+
</playpad-file>
|
|
213
|
+
|
|
214
|
+
<playpad-file name="items.json">
|
|
215
|
+
[{ "price": 12 }, { "price": 30 }]
|
|
216
|
+
</playpad-file>
|
|
217
|
+
</code-playpad>
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
A tab bar appears once there is more than one file, with `▸` marking the entry
|
|
221
|
+
point, and each file keeps its own undo history. Editing a module takes effect
|
|
222
|
+
on the next run — the import cache is cleared between runs.
|
|
223
|
+
|
|
152
224
|
Highlighting follows the **file**, not the runtime: a `.json` beside your
|
|
153
225
|
JavaScript is highlighted as JSON, a `.csv` beside your Python is plain text.
|
|
154
|
-
`.json` files are importable as data, the same as in Node.
|
|
155
226
|
|
|
156
227
|
## Input
|
|
157
228
|
|
|
@@ -165,10 +236,297 @@ age? 36
|
|
|
165
236
|
hello Ada, next year you turn 37
|
|
166
237
|
```
|
|
167
238
|
|
|
168
|
-
`Enter` sends a line; **EOF** (or `Ctrl-D`) ends input
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
239
|
+
`Enter` sends a line; **EOF** (or `Ctrl-D`) ends input, which is how you finish
|
|
240
|
+
a `while True:` loop.
|
|
241
|
+
|
|
242
|
+
In JavaScript and TypeScript, `input()` is a global returning a promise, so
|
|
243
|
+
`await` works at the top level:
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<code-playpad language="javascript" height="auto">
|
|
247
|
+
const name = await input("your name? ");
|
|
248
|
+
console.log(`hello ${name}`);
|
|
249
|
+
</code-playpad>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
To script the answers instead of typing them — useful for a worked example —
|
|
253
|
+
pre-fill the box. Those lines are used before the reader is asked:
|
|
254
|
+
|
|
255
|
+
```html
|
|
256
|
+
<code-playpad height="auto" stdin>
|
|
257
|
+
name = input("name? ")
|
|
258
|
+
print("hi", name)
|
|
259
|
+
</code-playpad>
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
```js
|
|
263
|
+
document.querySelector('code-playpad').stdin = 'Ada\n36';
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Echoed input is display-only: `stdout` stays exactly what the program printed,
|
|
267
|
+
so graders are unaffected.
|
|
268
|
+
|
|
269
|
+
## Controlling it from JavaScript
|
|
270
|
+
|
|
271
|
+
```js
|
|
272
|
+
const pad = document.querySelector('code-playpad');
|
|
273
|
+
|
|
274
|
+
pad.code = 'print("set from outside")'; // replace the visible file
|
|
275
|
+
const source = pad.getCode(); // read it back
|
|
276
|
+
const result = await pad.run(); // { stdout, stderr?, value?, durationMs }
|
|
277
|
+
pad.reset(); // back to the snippet the page shipped
|
|
278
|
+
pad.stop(); // kill a runaway program
|
|
279
|
+
await pad.prewarm(); // boot the runtime before the reader runs anything
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
Multi-file widgets add:
|
|
283
|
+
|
|
284
|
+
```js
|
|
285
|
+
pad.files = { // replace the whole project
|
|
286
|
+
'main.py': 'from util import shout\nprint(shout("hi"))',
|
|
287
|
+
'util.py': 'def shout(s): return s.upper() + "!"'
|
|
288
|
+
};
|
|
289
|
+
pad.entry = 'main.py'; // which file Run executes
|
|
290
|
+
pad.openFile('util.py'); // bring a tab on screen
|
|
291
|
+
const everything = pad.getFiles(); // { name: contents }
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
`prewarm()` is worth calling when you know the reader is about to run something
|
|
295
|
+
— on a lesson page, for instance — so the ~11 MB Python download starts before
|
|
296
|
+
they press Run rather than after.
|
|
297
|
+
|
|
298
|
+
## Events and grading
|
|
299
|
+
|
|
300
|
+
Four events bubble and cross the shadow boundary, so answer-checking lives
|
|
301
|
+
outside the widget rather than inside it:
|
|
302
|
+
|
|
303
|
+
| event | `event.detail` |
|
|
304
|
+
| --- | --- |
|
|
305
|
+
| `py-ready` | `{ language, label }` — runtime booted |
|
|
306
|
+
| `py-run` | `{ code, entry, files, language }` — a run started |
|
|
307
|
+
| `py-output` | `{ stdout, stderr?, value?, table?, durationMs, language }` |
|
|
308
|
+
| `py-error` | `{ message, hint?, language }` |
|
|
309
|
+
|
|
310
|
+
A complete exercise check:
|
|
311
|
+
|
|
312
|
+
```html
|
|
313
|
+
<code-playpad id="ex1" height="auto" min-height="150" persist="ex1">
|
|
314
|
+
# Print the numbers 1 to 5, one per line.
|
|
315
|
+
</code-playpad>
|
|
316
|
+
<p id="ex1-result"></p>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
```js
|
|
320
|
+
const expected = '1\n2\n3\n4\n5\n';
|
|
321
|
+
|
|
322
|
+
document.addEventListener('py-output', (event) => {
|
|
323
|
+
if (event.target.id !== 'ex1') return;
|
|
324
|
+
|
|
325
|
+
const el = document.getElementById('ex1-result');
|
|
326
|
+
if (event.detail.stderr) {
|
|
327
|
+
el.textContent = 'Your code raised an error — read the message above.';
|
|
328
|
+
} else if (event.detail.stdout === expected) {
|
|
329
|
+
el.textContent = '✅ Correct';
|
|
330
|
+
} else {
|
|
331
|
+
el.textContent = `❌ Expected ${JSON.stringify(expected)}, got ${JSON.stringify(event.detail.stdout)}`;
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
`py-error` also carries a `hint` when the runtime can explain the failure in
|
|
337
|
+
plain language — for example when a program calls `input()` more times than the
|
|
338
|
+
standard-input box has lines.
|
|
339
|
+
|
|
340
|
+
## Saving work
|
|
341
|
+
|
|
342
|
+
`persist` stores the whole file map in IndexedDB, so a reader's edits survive
|
|
343
|
+
navigation *and* a full reload:
|
|
344
|
+
|
|
345
|
+
```html
|
|
346
|
+
<code-playpad height="auto" persist="lesson-3-exercise-1">
|
|
347
|
+
# Your code here
|
|
348
|
+
</code-playpad>
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
The page's file list stays authoritative: edits come back, but files you add or
|
|
352
|
+
remove later follow the page, not the old snapshot.
|
|
353
|
+
|
|
354
|
+
For a course you usually also want completion state, which the package exports:
|
|
355
|
+
|
|
356
|
+
```js
|
|
357
|
+
import { setProgress, getProgress, allProgress, loadSnippet, clearSnippet } from 'code-playpad';
|
|
358
|
+
|
|
359
|
+
// mark a lesson done when its exercise passes
|
|
360
|
+
document.addEventListener('py-output', async (event) => {
|
|
361
|
+
if (event.detail.stdout === expected) {
|
|
362
|
+
await setProgress('lesson-3', { completed: true, score: 100 });
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
await getProgress('lesson-3'); // { id, completed, score, updatedAt }
|
|
367
|
+
await allProgress(); // every record, for a progress bar
|
|
368
|
+
await loadSnippet('lesson-3-exercise-1'); // what the reader last wrote
|
|
369
|
+
await clearSnippet('lesson-3-exercise-1'); // a "start over" button
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
All of it is per-browser: nothing leaves the reader's machine.
|
|
373
|
+
|
|
374
|
+
## Light and dark
|
|
375
|
+
|
|
376
|
+
`theme` takes `light`, `dark`, or `auto` (the default).
|
|
377
|
+
|
|
378
|
+
```html
|
|
379
|
+
<code-playpad theme="dark" height="auto">print("always dark")</code-playpad>
|
|
380
|
+
<code-playpad theme="light" height="auto">print("always light")</code-playpad>
|
|
381
|
+
<code-playpad height="auto">print("follows the page, then the OS")</code-playpad>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**`auto` follows your site first, the operating system second.** If your page
|
|
385
|
+
has its own light/dark switch, the widget follows it as long as the switch sets
|
|
386
|
+
`color-scheme` on the root element — which is how most themes are written:
|
|
387
|
+
|
|
388
|
+
```css
|
|
389
|
+
:root { color-scheme: light; }
|
|
390
|
+
:root[data-theme="dark"] { color-scheme: dark; }
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Flip `data-theme` (or a class, or an inline style) and every widget on the page
|
|
394
|
+
re-themes itself. Nothing else to wire up. With no `color-scheme` on the page,
|
|
395
|
+
`auto` falls back to the reader's OS setting via `prefers-color-scheme`.
|
|
396
|
+
|
|
397
|
+
If your theme switch does *not* set `color-scheme`, drive the widgets directly:
|
|
398
|
+
|
|
399
|
+
```js
|
|
400
|
+
function setTheme(mode) { // mode: 'light' | 'dark'
|
|
401
|
+
document.documentElement.dataset.theme = mode;
|
|
402
|
+
for (const pad of document.querySelectorAll('code-playpad')) {
|
|
403
|
+
pad.setAttribute('theme', mode);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
An explicit `theme` attribute always wins over the page, so a deliberately dark
|
|
409
|
+
snippet stays dark on a light page.
|
|
410
|
+
|
|
411
|
+
## Styling
|
|
412
|
+
|
|
413
|
+
The widget lives in a shadow root, so page CSS cannot leak in — and its own
|
|
414
|
+
styles cannot leak out. Two supported ways to restyle it.
|
|
415
|
+
|
|
416
|
+
**Custom properties** inherit through the shadow boundary:
|
|
417
|
+
|
|
418
|
+
```css
|
|
419
|
+
code-playpad {
|
|
420
|
+
--cp-accent: #b5179e; /* buttons, focus, the caret */
|
|
421
|
+
--cp-radius: 14px;
|
|
422
|
+
--cp-font-mono: "JetBrains Mono", monospace;
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
| | |
|
|
427
|
+
| --- | --- |
|
|
428
|
+
| colours | `--cp-bg`, `--cp-fg`, `--cp-muted`, `--cp-border`, `--cp-surface` |
|
|
429
|
+
| accents | `--cp-accent`, `--cp-accent-fg`, `--cp-danger`, `--cp-ok` |
|
|
430
|
+
| shape and type | `--cp-radius`, `--cp-font-mono`, `--cp-font-ui` |
|
|
431
|
+
|
|
432
|
+
**Parts** expose the pieces worth targeting:
|
|
433
|
+
|
|
434
|
+
```css
|
|
435
|
+
code-playpad::part(run-button) { border-radius: 999px; }
|
|
436
|
+
code-playpad::part(stop-button) { font-weight: 700; }
|
|
437
|
+
code-playpad::part(output) { background: #fff7fb; }
|
|
438
|
+
code-playpad::part(tabs) { border-bottom-width: 2px; }
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Both work from an ordinary stylesheet, no `::shadow` hacks. For dark mode, the
|
|
442
|
+
`theme` attribute already follows the reader's OS by default.
|
|
443
|
+
|
|
444
|
+
## Framework recipes
|
|
445
|
+
|
|
446
|
+
**Astro** — put the import in a page or layout script; the element does the rest.
|
|
447
|
+
|
|
448
|
+
```astro
|
|
449
|
+
---
|
|
450
|
+
// src/layouts/Lesson.astro
|
|
451
|
+
---
|
|
452
|
+
<script>
|
|
453
|
+
import 'code-playpad/element';
|
|
454
|
+
</script>
|
|
455
|
+
|
|
456
|
+
<code-playpad height="auto" persist={Astro.props.id}>
|
|
457
|
+
{Astro.props.code}
|
|
458
|
+
</code-playpad>
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
**React** — the wrapper assigns props through a ref, so React 18 behaves like 19.
|
|
462
|
+
|
|
463
|
+
```jsx
|
|
464
|
+
import { useState } from 'react';
|
|
465
|
+
import { CodePlayPad } from 'code-playpad/react';
|
|
466
|
+
|
|
467
|
+
export function Exercise() {
|
|
468
|
+
const [passed, setPassed] = useState(false);
|
|
469
|
+
|
|
470
|
+
return (
|
|
471
|
+
<>
|
|
472
|
+
<CodePlayPad
|
|
473
|
+
language="python"
|
|
474
|
+
code={'# Your code here'}
|
|
475
|
+
height="auto"
|
|
476
|
+
minHeight={150}
|
|
477
|
+
persist="exercise-1"
|
|
478
|
+
onOutput={(r) => setPassed(r.stdout.trim() === '42')}
|
|
479
|
+
/>
|
|
480
|
+
{passed && <p>✅ Correct</p>}
|
|
481
|
+
</>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
Props: `code`, `files`, `entry`, `language`, `height`, `stdin`, `theme`,
|
|
487
|
+
`readOnly`, `autoRun`, `persist`, `className`, `style`, and the callbacks
|
|
488
|
+
`onReady`, `onRun`, `onOutput`, `onError`.
|
|
489
|
+
|
|
490
|
+
**Next.js** — the element is SSR-safe, so a plain import works in a client
|
|
491
|
+
component:
|
|
492
|
+
|
|
493
|
+
```jsx
|
|
494
|
+
'use client';
|
|
495
|
+
import 'code-playpad/element';
|
|
496
|
+
|
|
497
|
+
export default function Lesson() {
|
|
498
|
+
return <code-playpad height="auto">{'print("hi")'}</code-playpad>;
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Vue** — tell the compiler the tag is a custom element, then use it directly:
|
|
503
|
+
|
|
504
|
+
```js
|
|
505
|
+
// vite.config.js
|
|
506
|
+
export default {
|
|
507
|
+
plugins: [vue({ template: { compilerOptions: { isCustomElement: (tag) => tag === 'code-playpad' } } })]
|
|
508
|
+
};
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**Plain HTML / Markdown** — the CDN tag plus the element. Because the code lives
|
|
512
|
+
in the element's text content, the snippet is still readable if JavaScript never
|
|
513
|
+
loads.
|
|
514
|
+
|
|
515
|
+
## Production
|
|
516
|
+
|
|
517
|
+
Serve Pyodide from your own origin so you control its cache headers, rather than
|
|
518
|
+
depending on a public CDN:
|
|
519
|
+
|
|
520
|
+
```js
|
|
521
|
+
import { configurePython, pyodideAssetUrls } from 'code-playpad';
|
|
522
|
+
|
|
523
|
+
configurePython({ indexURL: '/pyodide/' }); // before the first run
|
|
524
|
+
pyodideAssetUrls(); // the files worth <link rel=preload>-ing
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
`configureTypeScript({ sucraseURL })` does the same for the TypeScript
|
|
528
|
+
transform. Give those files a long `Cache-Control`: one page's download then
|
|
529
|
+
warms every other page on the site.
|
|
172
530
|
|
|
173
531
|
## Worth knowing
|
|
174
532
|
|
|
@@ -180,12 +538,55 @@ program printed, so graders are unaffected.
|
|
|
180
538
|
- **A program waiting on `input()` holds the shared runtime.** Other widgets
|
|
181
539
|
show `Queued…` until it finishes or is stopped.
|
|
182
540
|
- **Nothing boots on page load.** An IntersectionObserver starts a runtime as a
|
|
183
|
-
widget nears the viewport; touching the editor also wakes it.
|
|
541
|
+
widget nears the viewport; touching the editor also wakes it. Use `prewarm()`
|
|
542
|
+
to start earlier on purpose.
|
|
184
543
|
- **Python in the browser has limits**: no threads, no `multiprocessing`, no
|
|
185
544
|
sockets, no `subprocess`. `requests` works but goes through browser `fetch`,
|
|
186
545
|
so CORS applies.
|
|
187
|
-
- **
|
|
188
|
-
|
|
546
|
+
- **If CodeMirror fails to load**, the widget falls back to a plain textarea that
|
|
547
|
+
still runs code, rather than showing nothing.
|
|
548
|
+
|
|
549
|
+
## Version history
|
|
550
|
+
|
|
551
|
+
No breaking changes so far: markup and API written against 0.1.3 still work on
|
|
552
|
+
0.2.1.
|
|
553
|
+
|
|
554
|
+
### 0.2.1
|
|
555
|
+
|
|
556
|
+
- `theme="auto"` now follows the **page** before the operating system: if your
|
|
557
|
+
site's light/dark switch sets `color-scheme` on the root, widgets re-theme
|
|
558
|
+
with it. Previously a page could go dark while every widget stayed light.
|
|
559
|
+
- `minHeight` and `maxHeight` props on the React wrapper — 0.2.0 added them as
|
|
560
|
+
attributes but missed the wrapper.
|
|
561
|
+
- README expanded into full usage documentation: entry points, an example per
|
|
562
|
+
attribute, programmatic control, a complete grading example, the progress
|
|
563
|
+
store, styling, and framework recipes.
|
|
564
|
+
|
|
565
|
+
### 0.2.0
|
|
566
|
+
|
|
567
|
+
- `height="auto"` is a supported content-fitting mode. It previously worked by
|
|
568
|
+
accident and could have broken silently.
|
|
569
|
+
- New `min-height` and `max-height` attributes. `max-height` scrolls rather than
|
|
570
|
+
clipping; `min-height` grows the editor itself, so the empty space below a
|
|
571
|
+
short snippet is still clickable — what an exercise answer box needs.
|
|
572
|
+
|
|
573
|
+
### 0.1.4
|
|
574
|
+
|
|
575
|
+
- **Fixed:** a `<pre>` or other wrapper on its own line leaked its indentation
|
|
576
|
+
into the program, so two-line snippets failed with `IndentationError` while
|
|
577
|
+
single-line ones looked fine.
|
|
578
|
+
- **Fixed:** `sideEffects` named the re-export shims rather than the chunk that
|
|
579
|
+
registers the element, so a bundler could legitimately drop it and
|
|
580
|
+
`<code-playpad>` would never register in a production build.
|
|
581
|
+
- No runtime dependencies (an analytics package used only by the demo had crept
|
|
582
|
+
into `dependencies`).
|
|
583
|
+
|
|
584
|
+
### 0.1.3
|
|
585
|
+
|
|
586
|
+
- Relicensed **MIT**. Earlier versions were published as `UNLICENSED`, which
|
|
587
|
+
granted nobody the right to use them.
|
|
588
|
+
|
|
589
|
+
Versions 0.1.0 to 0.1.2 were withdrawn and are not installable.
|
|
189
590
|
|
|
190
591
|
## Licence
|
|
191
592
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
async function e(e){try{return await t(e)}catch(t){return console.warn(`[code-playpad] CodeMirror unavailable, using plain textarea:`,t),n(e)}}async function t(e){let[t,n,r,i,a,o]=await Promise.all([import(`@codemirror/view`),import(`@codemirror/state`),import(`@codemirror/commands`),import(`@codemirror/language`),import(`@codemirror/autocomplete`),import(`@codemirror/theme-one-dark`)]),{EditorView:s,keymap:c,lineNumbers:l,highlightActiveLine:u,highlightActiveLineGutter:d,highlightSpecialChars:f,drawSelection:p,rectangularSelection:m,crosshairCursor:h}=t,{EditorState:g,Compartment:_}=n,{defaultKeymap:v,history:y,historyKeymap:b,indentWithTab:x}=r,{syntaxHighlighting:S,defaultHighlightStyle:C,indentOnInput:w,bracketMatching:T,foldGutter:E,foldKeymap:D}=i,{autocompletion:O,closeBrackets:k,closeBracketsKeymap:A,completionKeymap:j}=a,M=new _,N=new _,P=new _,F=[s.theme({"&":{color:`#1f2328`,backgroundColor:`#ffffff`},".cm-gutters":{backgroundColor:`#f6f8fa`,color:`#8b949e`,border:`none`},".cm-activeLine":{backgroundColor:`#f6f8fa88`},".cm-activeLineGutter":{backgroundColor:`#eaeef2`},".cm-cursor":{borderLeftColor:`#1f2328`},"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection":{backgroundColor:`#cfe3ff`}}),S(C,{fallback:!0})],I=e=>e?o.oneDark:F,L=c.of([{key:`Mod-Enter`,preventDefault:!0,run:()=>(e.onRun?.(),!0)},{key:`Shift-Enter`,preventDefault:!0,run:()=>(e.onRun?.(),!0)}]),R=e.dark,z=e.readOnly,B=e.language??[],V=t=>g.create({doc:t,extensions:[l(),d(),f(),y(),E(),p(),g.allowMultipleSelections.of(!0),w(),T(),k(),O(),m(),h(),u(),L,c.of([...A,...v,...b,...D,...j,x]),s.lineWrapping,P.of(B),M.of(I(R)),N.of(g.readOnly.of(z)),s.updateListener.of(t=>{t.docChanged&&e.onChange?.(t.state.doc.toString())})]}),H=new s({parent:e.parent,state:V(e.doc)}),U=new Map,W=e.name??``;return{fallback:!1,getCode:()=>H.state.doc.toString(),setCode(e){H.dispatch({changes:{from:0,to:H.state.doc.length,insert:e}})},openFile(e,t){if(e===W)return;U.set(W,H.state);let n=U.get(e);H.setState(n??V(t)),W=e,n&&H.dispatch({effects:[M.reconfigure(I(R)),N.reconfigure(g.readOnly.of(z)),P.reconfigure(B)]})},fileCode(e,t){if(e===W)return H.state.doc.toString();let n=U.get(e);return n?n.doc.toString():t},forgetFiles(e){for(let t of[...U.keys()])e.includes(t)||U.delete(t)},setTheme(e){R=e,H.dispatch({effects:M.reconfigure(I(R))})},setReadOnly(e){z=e,H.dispatch({effects:N.reconfigure(g.readOnly.of(z))})},setLanguage(e){B=e,H.dispatch({effects:P.reconfigure(B)})},focus:()=>H.focus(),destroy:()=>H.destroy()}}function n(e){let t=document.createElement(`textarea`);t.className=`cp-fallback-editor`,t.spellcheck=!1,t.value=e.doc,t.readOnly=e.readOnly,t.addEventListener(`input`,()=>e.onChange?.(t.value)),t.addEventListener(`keydown`,t=>{(t.metaKey||t.ctrlKey)&&t.key===`Enter`&&(t.preventDefault(),e.onRun?.())}),e.parent.appendChild(t);let n=new Map,r=e.name??``;return{fallback:!0,getCode:()=>t.value,setCode:e=>{t.value=e},openFile(e,i){e!==r&&(n.set(r,t.value),t.value=n.get(e)??i,r=e)},fileCode(e,i){return e===r?t.value:n.get(e)??i},forgetFiles(e){for(let t of[...n.keys()])e.includes(t)||n.delete(t)},setTheme:()=>{},setReadOnly:e=>{t.readOnly=e},setLanguage:()=>{},focus:()=>t.focus(),destroy:()=>t.remove()}}var r=new Map,i=new Map;function a(e,t){r.set(e,t)}function o(){return[...r.keys()]}function s(e){let t=i.get(e);if(t)return t;let n=r.get(e);if(!n)return Promise.reject(Error(`Unknown runtime "${e}". Registered: ${o().join(`, `)||`none`}`));let a=n().catch(t=>{throw i.delete(e),t});return i.set(e,a),a}a(`python`,()=>import(`./pyodide-
|
|
1
|
+
async function e(e){try{return await t(e)}catch(t){return console.warn(`[code-playpad] CodeMirror unavailable, using plain textarea:`,t),n(e)}}async function t(e){let[t,n,r,i,a,o]=await Promise.all([import(`@codemirror/view`),import(`@codemirror/state`),import(`@codemirror/commands`),import(`@codemirror/language`),import(`@codemirror/autocomplete`),import(`@codemirror/theme-one-dark`)]),{EditorView:s,keymap:c,lineNumbers:l,highlightActiveLine:u,highlightActiveLineGutter:d,highlightSpecialChars:f,drawSelection:p,rectangularSelection:m,crosshairCursor:h}=t,{EditorState:g,Compartment:_}=n,{defaultKeymap:v,history:y,historyKeymap:b,indentWithTab:x}=r,{syntaxHighlighting:S,defaultHighlightStyle:C,indentOnInput:w,bracketMatching:T,foldGutter:E,foldKeymap:D}=i,{autocompletion:O,closeBrackets:k,closeBracketsKeymap:A,completionKeymap:j}=a,M=new _,N=new _,P=new _,F=[s.theme({"&":{color:`#1f2328`,backgroundColor:`#ffffff`},".cm-gutters":{backgroundColor:`#f6f8fa`,color:`#8b949e`,border:`none`},".cm-activeLine":{backgroundColor:`#f6f8fa88`},".cm-activeLineGutter":{backgroundColor:`#eaeef2`},".cm-cursor":{borderLeftColor:`#1f2328`},"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, ::selection":{backgroundColor:`#cfe3ff`}}),S(C,{fallback:!0})],I=e=>e?o.oneDark:F,L=c.of([{key:`Mod-Enter`,preventDefault:!0,run:()=>(e.onRun?.(),!0)},{key:`Shift-Enter`,preventDefault:!0,run:()=>(e.onRun?.(),!0)}]),R=e.dark,z=e.readOnly,B=e.language??[],V=t=>g.create({doc:t,extensions:[l(),d(),f(),y(),E(),p(),g.allowMultipleSelections.of(!0),w(),T(),k(),O(),m(),h(),u(),L,c.of([...A,...v,...b,...D,...j,x]),s.lineWrapping,P.of(B),M.of(I(R)),N.of(g.readOnly.of(z)),s.updateListener.of(t=>{t.docChanged&&e.onChange?.(t.state.doc.toString())})]}),H=new s({parent:e.parent,state:V(e.doc)}),U=new Map,W=e.name??``;return{fallback:!1,getCode:()=>H.state.doc.toString(),setCode(e){H.dispatch({changes:{from:0,to:H.state.doc.length,insert:e}})},openFile(e,t){if(e===W)return;U.set(W,H.state);let n=U.get(e);H.setState(n??V(t)),W=e,n&&H.dispatch({effects:[M.reconfigure(I(R)),N.reconfigure(g.readOnly.of(z)),P.reconfigure(B)]})},fileCode(e,t){if(e===W)return H.state.doc.toString();let n=U.get(e);return n?n.doc.toString():t},forgetFiles(e){for(let t of[...U.keys()])e.includes(t)||U.delete(t)},setTheme(e){R=e,H.dispatch({effects:M.reconfigure(I(R))})},setReadOnly(e){z=e,H.dispatch({effects:N.reconfigure(g.readOnly.of(z))})},setLanguage(e){B=e,H.dispatch({effects:P.reconfigure(B)})},focus:()=>H.focus(),destroy:()=>H.destroy()}}function n(e){let t=document.createElement(`textarea`);t.className=`cp-fallback-editor`,t.spellcheck=!1,t.value=e.doc,t.readOnly=e.readOnly,t.addEventListener(`input`,()=>e.onChange?.(t.value)),t.addEventListener(`keydown`,t=>{(t.metaKey||t.ctrlKey)&&t.key===`Enter`&&(t.preventDefault(),e.onRun?.())}),e.parent.appendChild(t);let n=new Map,r=e.name??``;return{fallback:!0,getCode:()=>t.value,setCode:e=>{t.value=e},openFile(e,i){e!==r&&(n.set(r,t.value),t.value=n.get(e)??i,r=e)},fileCode(e,i){return e===r?t.value:n.get(e)??i},forgetFiles(e){for(let t of[...n.keys()])e.includes(t)||n.delete(t)},setTheme:()=>{},setReadOnly:e=>{t.readOnly=e},setLanguage:()=>{},focus:()=>t.focus(),destroy:()=>t.remove()}}var r=new Map,i=new Map;function a(e,t){r.set(e,t)}function o(){return[...r.keys()]}function s(e){let t=i.get(e);if(t)return t;let n=r.get(e);if(!n)return Promise.reject(Error(`Unknown runtime "${e}". Registered: ${o().join(`, `)||`none`}`));let a=n().catch(t=>{throw i.delete(e),t});return i.set(e,a),a}a(`python`,()=>import(`./pyodide-5ByQYqgo.js`).then(e=>e.pythonRuntime)),a(`javascript`,()=>import(`./javascript-RVtPk08Z.js`).then(e=>e.javascriptRuntime)),a(`typescript`,()=>import(`./javascript-RVtPk08Z.js`).then(e=>e.typescriptRuntime));var c=class extends Error{constructor(e=`Execution stopped`){super(e),this.name=`RunStopped`}},l=class extends Error{constructor(e=`Web Workers are unavailable in this page`){super(e),this.name=`WorkerUnavailable`}},u=String.raw`
|
|
2
2
|
:host {
|
|
3
3
|
--cp-bg: #ffffff;
|
|
4
4
|
--cp-fg: #1f2328;
|
|
@@ -296,10 +296,10 @@ table.cp-table th { background: var(--cp-bg); }
|
|
|
296
296
|
}
|
|
297
297
|
`,d=e=>()=>import(`@codemirror/lang-javascript`).then(t=>t.javascript(e)),f={py:()=>import(`@codemirror/lang-python`).then(e=>e.python()),pyi:()=>import(`@codemirror/lang-python`).then(e=>e.python()),js:d({}),mjs:d({}),cjs:d({}),jsx:d({jsx:!0}),ts:d({typescript:!0}),mts:d({typescript:!0}),cts:d({typescript:!0}),tsx:d({typescript:!0,jsx:!0}),json:()=>import(`@codemirror/lang-json`).then(e=>e.json())};async function p(e){let t=f[e.includes(`.`)?e.slice(e.lastIndexOf(`.`)+1).toLowerCase():``];return t?t().catch(()=>null):null}var m=`code-playpad`,h=1,g=`snippets`,_=`progress`,v=null;function y(){return typeof indexedDB>`u`?Promise.reject(Error(`IndexedDB unavailable`)):(v||=new Promise((e,t)=>{let n=indexedDB.open(m,h);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(g)||e.createObjectStore(g),e.objectStoreNames.contains(_)||e.createObjectStore(_,{keyPath:`id`})},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)}).catch(e=>{throw v=null,e}),v)}function b(e,t,n){return y().then(r=>new Promise((i,a)=>{let o=n(r.transaction(e,t).objectStore(e));o.onsuccess=()=>i(o.result),o.onerror=()=>a(o.error)}))}function x(e,t){return b(g,`readwrite`,n=>n.put(t,e)).then(()=>void 0)}function S(e){return b(g,`readonly`,t=>t.get(e)).then(e=>e??null)}function C(e){return b(g,`readwrite`,t=>t.delete(e)).then(()=>void 0)}function w(e,t={}){let n={completed:!0,...t,id:e,updatedAt:Date.now()};return b(_,`readwrite`,e=>e.put(n)).then(()=>n)}function T(e){return b(_,`readonly`,t=>t.get(e)).then(e=>e??null)}function E(){return b(_,`readonly`,e=>e.getAll())}var D=`code-playpad`;function O(e){let t=e.replace(/\t/g,` `).split(`
|
|
298
298
|
`);for(;t.length&&t[0].trim()===``;)t.shift();for(;t.length&&t[t.length-1].trim()===``;)t.pop();let n=t.filter(e=>e.trim()!==``).reduce((e,t)=>Math.min(e,t.match(/^ */)[0].length),1/0);return(n===1/0?t:t.map(e=>e.slice(n))).join(`
|
|
299
|
-
`)}var k=2e5,A=`playpad-file`,j={python:`main.py`,javascript:`main.js`,typescript:`main.ts`};function M(e){return j[e]??`main.txt`}function N(e,t){let n={};for(let t of e.children){if(t.tagName.toLowerCase()!==A)continue;let e=t.getAttribute(`name`);e&&(n[e]=O(t.textContent??``))}if(Object.keys(n).length)return n;let r=[...e.childNodes].filter(e=>e.nodeType!==Node.ELEMENT_NODE||e.tagName.toLowerCase()!==A),i=r.some(e=>e.nodeType===Node.ELEMENT_NODE),a=O(r.filter(e=>!(i&&e.nodeType===Node.TEXT_NODE&&!(e.textContent??``).trim())).map(e=>e.textContent??``).join(``));return{[t]:a}}function P(e,t){try{let t=JSON.parse(e);if(t&&typeof t==`object`&&!Array.isArray(t)){let e=t;return Object.keys(e).length?e:null}}catch{}return e?{[t]:e}:null}var F=typeof HTMLElement>`u`?class{}:HTMLElement,I=class extends F{static get observedAttributes(){return[`language`,`height`,`min-height`,`max-height`,`theme`,`readonly`,`autorun`,`stdin`,`persist`,`entry`]}#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u=null;#d=null;#f=null;#p=null;#m={};#h={};#g=`main.py`;#_=null;#v;#y=!1;#b=!1;#x=null;#S=null;#C=()=>this.#
|
|
299
|
+
`)}var k=2e5,A=`playpad-file`,j={python:`main.py`,javascript:`main.js`,typescript:`main.ts`};function M(e){return j[e]??`main.txt`}function N(e,t){let n={};for(let t of e.children){if(t.tagName.toLowerCase()!==A)continue;let e=t.getAttribute(`name`);e&&(n[e]=O(t.textContent??``))}if(Object.keys(n).length)return n;let r=[...e.childNodes].filter(e=>e.nodeType!==Node.ELEMENT_NODE||e.tagName.toLowerCase()!==A),i=r.some(e=>e.nodeType===Node.ELEMENT_NODE),a=O(r.filter(e=>!(i&&e.nodeType===Node.TEXT_NODE&&!(e.textContent??``).trim())).map(e=>e.textContent??``).join(``));return{[t]:a}}function P(e,t){try{let t=JSON.parse(e);if(t&&typeof t==`object`&&!Array.isArray(t)){let e=t;return Object.keys(e).length?e:null}}catch{}return e?{[t]:e}:null}var F=typeof HTMLElement>`u`?class{}:HTMLElement,I=class extends F{static get observedAttributes(){return[`language`,`height`,`min-height`,`max-height`,`theme`,`readonly`,`autorun`,`stdin`,`persist`,`entry`]}#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u=null;#d=null;#f=null;#p=null;#m={};#h={};#g=`main.py`;#_=null;#v;#y=!1;#b=!1;#x=null;#S=null;#C=null;#w=()=>this.#W();#T=null;#E=!0;#D=[];#O=null;#k=null;#A=null;#j=0;#M=!1;constructor(){super(),this.#e=this.attachShadow({mode:`open`})}get code(){return this.#u?this.#u.getCode():this.#m[this.#g]??``}set code(e){this.#m[this.#g]=e,this.#R()}get files(){return this.#B()}set files(e){let t=Object.keys(e);if(!t.length)return;let n=Object.keys(this.#h);n.length===1&&!this.#h[n[0]]&&(this.#h={...e}),this.#m={...e},t.includes(this.#g)||(this.#g=this.#z()),this.#u?.forgetFiles([]),this.#R(),this.#V(),this.#L()}get entry(){return this.#z()}set entry(e){this.setAttribute(`entry`,e)}openFile(e){e in this.#m&&(this.#u&&(this.#m[this.#g]=this.#u.getCode(),this.#u.openFile(e,this.#m[e]??``)),this.#g=e,this.#V(),this.#L())}get stdin(){return this.#l?this.#l.value:``}set stdin(e){this.#l&&(this.#l.value=e,this.#c.hidden=!1)}get language(){return this.getAttribute(`language`)||`python`}set language(e){this.setAttribute(`language`,e)}getCode(){return this.code}getFiles(){return this.#B()}reset(){this.#m={...this.#h},this.#g=this.#z(),this.#u?.forgetFiles([]),this.#R(),this.#V(),this.#q(),this.#s.hidden=!0,this.#K(this.#f?.ready?`${this.#f.label} ready`:``,`idle`)}async run(){if(this.#y)return;await this.#d;let e=this.#B(),t=this.#z(),n=e[t]??``;this.#y=!0,this.#n.disabled=!0,this.#r.hidden=!1,this.#q(),this.#s.hidden=!1,this.#ee(`py-run`,{code:n,entry:t,files:e,language:this.language});try{let r=await this.#I();this.#K(`Queued…`,`busy`),this.#o.hidden=!1;let i=await r.run(n,{stdin:this.#l.value,files:e,entry:t,onStart:()=>this.#K(`Running…`,`busy`),onStdout:e=>this.#J(e),onStderr:e=>this.#J(e,`cp-err`),onEcho:e=>this.#J(e,`cp-in`),onStdinRequest:()=>this.#X()});return i.table&&this.#$(i.table),i.value!==void 0&&this.#J(`${i.value}\n`,`cp-value`),i.hint&&this.#Q(i.hint),i.stderr?(this.#K(`Error · ${i.durationMs} ms`,`error`),this.#ee(`py-error`,{message:i.stderr,hint:i.hint,language:this.language})):this.#K(`Done in ${i.durationMs} ms`,`ok`),this.#ee(`py-output`,{...i,language:this.language}),i}catch(e){if(e instanceof c)this.#J(`
|
|
300
300
|
[stopped]
|
|
301
|
-
`,`cp-err`),this.#
|
|
302
|
-
`,`cp-err`),this.#
|
|
301
|
+
`,`cp-err`),this.#K(`Stopped`,`error`);else{let t=e instanceof Error?e.message:String(e);this.#J(t+`
|
|
302
|
+
`,`cp-err`),this.#K(`Failed to start`,`error`),this.#ee(`py-error`,{message:t,language:this.language})}return}finally{this.#Z(null),this.#Y(),this.#y=!1,this.#n.disabled=!1,this.#r.hidden=!0,this.#o.hidden=!0}}stop(){this.#f?.stop?.()}prewarm(){return this.#I()}connectedCallback(){if(this.#M){this.#F();return}if(!this.textContent?.trim()&&document.readyState===`loading`){document.addEventListener(`DOMContentLoaded`,()=>this.isConnected&&this.#N(),{once:!0});return}this.#N()}disconnectedCallback(){this.#x?.disconnect(),this.#x=null,this.#S?.removeEventListener(`change`,this.#w),this.#S=null,this.#C?.disconnect(),this.#C=null,this.#T&&clearTimeout(this.#T),this.#Z(null),setTimeout(()=>{this.isConnected||!this.#u||(this.#_=this.#B(),this.#u.destroy(),this.#u=null,this.#d=null,this.#M=!1)},0)}attributeChangedCallback(e,t,n){if(this.#M)switch(e){case`theme`:this.#W();break;case`height`:case`min-height`:case`max-height`:this.#G();break;case`readonly`:this.#u?.setReadOnly(n!==null);break;case`stdin`:this.#c.hidden=n===null&&!this.#l.value;break;case`entry`:this.#V();break;case`language`:this.#f=null,this.#p=null,this.#b=!1,this.#I().catch(()=>{})}}#N(){this.#M=!0,this.#h=N(this,M(this.language)),this.#m=this.#_??{...this.#h},this.#g=this.#z(),this.#P(),this.#W(),this.#G();let t=this.getAttribute(`persist`);this.#d=(async()=>{if(t&&this.#_==null){let e=await S(t).catch(()=>null),n=e==null?null:P(e,M(this.language));if(n){let e={};for(let t of Object.keys(this.#h))e[t]=n[t]??this.#h[t];this.#m=e,this.#g=this.#z()}}this.#V();let n=await s(this.language).catch(()=>null),r=n?await n.editorLanguage().catch(()=>null):null;this.#u=await e({parent:this.#t,doc:this.#m[this.#g]??``,name:this.#g,dark:this.#U(),readOnly:this.hasAttribute(`readonly`),language:r,onRun:()=>void this.run(),onChange:e=>this.#H(e)}),this.#_=null,this.#L()})(),this.#F()}#P(){let e=document.createElement(`style`);e.textContent=u;let t=document.createElement(`div`);t.className=`cp-root`,t.innerHTML=`
|
|
303
303
|
<div class="cp-toolbar">
|
|
304
304
|
<button class="cp-primary" part="run-button" data-action="run">Run</button>
|
|
305
305
|
<button class="cp-danger" part="stop-button" data-action="stop" hidden>Stop</button>
|
|
@@ -314,10 +314,10 @@ table.cp-table th { background: var(--cp-bg); }
|
|
|
314
314
|
<label>Standard input — one value per line</label>
|
|
315
315
|
<textarea rows="2" spellcheck="false"></textarea>
|
|
316
316
|
</div>
|
|
317
|
-
<pre class="cp-output" part="output" hidden></pre>`,this.#e.replaceChildren(e,t),this.#n=t.querySelector(`[data-action="run"]`),this.#r=t.querySelector(`[data-action="stop"]`),this.#i=t.querySelector(`[data-action="reset"]`),this.#a=t.querySelector(`.cp-status`),this.#o=t.querySelector(`.cp-progress`),this.#v=t.querySelector(`.cp-tabs`),this.#t=t.querySelector(`.cp-editor`),this.#c=t.querySelector(`.cp-stdin`),this.#l=t.querySelector(`.cp-stdin textarea`),this.#s=t.querySelector(`.cp-output`),this.#c.hidden=!this.hasAttribute(`stdin`),this.#l.addEventListener(`input`,()=>this.#c.classList.remove(`cp-attention`)),this.#v.addEventListener(`click`,e=>{let t=e.target.closest(`button[data-file]`);t&&this.openFile(t.getAttribute(`data-file`))}),this.#n.addEventListener(`click`,()=>void this.run()),this.#r.addEventListener(`click`,()=>this.stop()),this.#i.addEventListener(`click`,()=>this.reset());let n=()=>void this.#
|
|
318
|
-
`,`cp-err`),this.#s.hidden=!1,e}finally{this.#o.hidden=!0}return this.#
|
|
319
|
-
`),this.#
|
|
317
|
+
<pre class="cp-output" part="output" hidden></pre>`,this.#e.replaceChildren(e,t),this.#n=t.querySelector(`[data-action="run"]`),this.#r=t.querySelector(`[data-action="stop"]`),this.#i=t.querySelector(`[data-action="reset"]`),this.#a=t.querySelector(`.cp-status`),this.#o=t.querySelector(`.cp-progress`),this.#v=t.querySelector(`.cp-tabs`),this.#t=t.querySelector(`.cp-editor`),this.#c=t.querySelector(`.cp-stdin`),this.#l=t.querySelector(`.cp-stdin textarea`),this.#s=t.querySelector(`.cp-output`),this.#c.hidden=!this.hasAttribute(`stdin`),this.#l.addEventListener(`input`,()=>this.#c.classList.remove(`cp-attention`)),this.#v.addEventListener(`click`,e=>{let t=e.target.closest(`button[data-file]`);t&&this.openFile(t.getAttribute(`data-file`))}),this.#n.addEventListener(`click`,()=>void this.run()),this.#r.addEventListener(`click`,()=>this.stop()),this.#i.addEventListener(`click`,()=>this.reset());let n=()=>void this.#I().catch(()=>{});this.#t.addEventListener(`pointerdown`,n,{once:!0}),this.#t.addEventListener(`focusin`,n,{once:!0})}#F(){this.#x||typeof IntersectionObserver>`u`||(this.#x=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&(this.#x?.disconnect(),this.#x=null,this.#I().then(()=>{this.hasAttribute(`autorun`)&&this.run()}).catch(()=>{}))},{rootMargin:`300px`}),this.#x.observe(this))}#I(){return this.#p||=(async()=>{let e=await s(this.language);this.#f=e,e.ready||(this.#o.hidden=!1,this.#K(`Loading ${e.label}…`,`busy`));try{await e.load(e=>{this.#y||this.#K(e.message,e.phase===`ready`?`ok`:`busy`)})}catch(e){this.#p=null,this.#K(`Runtime unavailable`,`error`);let t=e instanceof Error?e.message:String(e);throw this.#J(t+`
|
|
318
|
+
`,`cp-err`),this.#s.hidden=!1,e}finally{this.#o.hidden=!0}return this.#L(),this.#b||(this.#b=!0,this.#K(`${e.label} ready`,`ok`),this.#ee(`py-ready`,{language:e.id,label:e.label})),e})(),this.#p}async#L(){if(!this.#u)return;let e=this.#g,t=await p(e);if(t){this.#g===e&&this.#u?.setLanguage(t);return}let n=e.includes(`.`)?null:await(this.#f??await s(this.language).catch(()=>null))?.editorLanguage().catch(()=>null);this.#g===e&&this.#u?.setLanguage(n??[])}#R(){if(this.#u){this.#u.setCode(this.#m[this.#g]??``);return}this.#d?.then(()=>{this.#u?.setCode(this.#m[this.#g]??``),this.#V()})}#z(){let e=Object.keys(this.#m),t=this.getAttribute(`entry`);if(t&&e.includes(t))return t;let n=M(this.language);return e.includes(n)?n:e[0]??n}#B(){let e={...this.#m};if(this.#u)for(let t of Object.keys(e))e[t]=this.#u.fileCode(t,e[t]);return e}#V(){let e=Object.keys(this.#m);if(this.#v.hidden=e.length<2,this.#v.hidden){this.#v.replaceChildren();return}let t=this.#z();this.#v.replaceChildren(...e.map(e=>{let n=document.createElement(`button`);return n.dataset.file=e,n.textContent=e,n.setAttribute(`aria-pressed`,String(e===this.#g)),e===t&&(n.classList.add(`cp-entry`),n.title=`entry point — this is the file Run executes`),n}))}#H(e){this.#m[this.#g]=e;let t=this.getAttribute(`persist`);if(!t)return;this.#T&&clearTimeout(this.#T);let n=JSON.stringify(this.#B());this.#T=setTimeout(()=>void x(t,n).catch(()=>{}),400)}#U(){let e=this.getAttribute(`theme`)??`auto`;if(e===`dark`)return!0;if(e===`light`)return!1;let t=typeof document>`u`?``:getComputedStyle(document.documentElement).colorScheme,n=t.includes(`dark`);return n===t.includes(`light`)?!!this.#S?.matches:n}#W(){!this.#S&&typeof matchMedia==`function`&&(this.#S=matchMedia(`(prefers-color-scheme: dark)`),this.#S.addEventListener(`change`,this.#w)),!this.#C&&typeof MutationObserver==`function`&&typeof document<`u`&&(this.#C=new MutationObserver(this.#w),this.#C.observe(document.documentElement,{attributes:!0,attributeFilter:[`class`,`style`,`data-theme`,`data-color-scheme`,`color-scheme`]}));let e=this.#U();this.setAttribute(`data-resolved-theme`,e?`dark`:`light`),this.#u?.setTheme(e)}#G(){for(let[e,t]of[[`height`,`--cp-height`],[`min-height`,`--cp-min-height`],[`max-height`,`--cp-max-height`]]){let n=this.getAttribute(e);n===null||n===``?this.style.removeProperty(t):this.style.setProperty(t,/^\d+$/.test(n)?`${n}px`:n)}}#K(e,t){this.#a.textContent=e,this.#a.dataset.state=t}#q(){this.#A&&clearTimeout(this.#A),this.#A=null,this.#D=[],this.#j=0,this.#s.replaceChildren(),this.#E=!0}#J(e,t){if(!e)return;let n=t===`cp-err`&&!this.#E?`\n${e}`:e;this.#E=n.endsWith(`
|
|
319
|
+
`),this.#D.push({text:n,className:t}),this.#A??=setTimeout(()=>this.#Y(),16)}#Y(){if(this.#A&&clearTimeout(this.#A),this.#A=null,!this.#D.length)return;let e=this.#D;this.#D=[];let t=document.createDocumentFragment(),n=``,r,i=()=>{if(!n)return;let e=document.createElement(`span`);r&&(e.className=r),e.textContent=n,t.appendChild(e),n=``};for(let t of e){if(this.#j>=k)break;let e=t.text;this.#j+e.length>k&&(e=e.slice(0,k-this.#j)+`
|
|
320
320
|
… output truncated for display — the full text is in the py-output event
|
|
321
|
-
`),this.#
|
|
322
|
-
`,`cp-in`):this.#
|
|
323
|
-
`,`cp-in`),this.#
|
|
321
|
+
`),this.#j+=t.text.length,r!==t.className&&(i(),r=t.className),n+=e}i(),this.#s.hidden=!1;let a=this.#s.querySelector(`.cp-prompt`);a?this.#s.insertBefore(t,a):this.#s.appendChild(t),this.#s.scrollTop=this.#s.scrollHeight}#X(){return this.#Y(),this.#Z(null),new Promise(e=>{let t=document.createElement(`form`);t.className=`cp-prompt`;let n=document.createElement(`input`);n.type=`text`,n.setAttribute(`aria-label`,`Program input — press Enter to send`),n.autocapitalize=`off`,n.autocomplete=`off`,n.spellcheck=!1;let r=document.createElement(`button`);r.type=`submit`,r.className=`cp-send`,r.textContent=`⏎`,r.title=`Send this line (Enter)`;let i=document.createElement(`button`);i.type=`button`,i.className=`cp-eof`,i.textContent=`EOF`,i.title=`End of input (Ctrl-D) — makes input() raise EOFError`;let a=n=>{this.#O===e&&(this.#O=null,this.#k=null,t.remove(),n===null?this.#J(`
|
|
322
|
+
`,`cp-in`):this.#J(n+`
|
|
323
|
+
`,`cp-in`),this.#Y(),e(n))};t.addEventListener(`submit`,e=>{e.preventDefault(),a(n.value)}),i.addEventListener(`click`,()=>a(null)),n.addEventListener(`keydown`,e=>{(e.key===`Escape`||e.key===`d`&&e.ctrlKey)&&(e.preventDefault(),a(null))}),t.append(n,r,i),this.#s.hidden=!1,this.#s.appendChild(t),this.#s.scrollTop=this.#s.scrollHeight,this.#O=e,this.#k=a,n.focus({preventScroll:!0})})}#Z(e){this.#k?.(e),this.#k=null,this.#O=null}#Q(e){this.#Y();let t=document.createElement(`div`);t.className=`cp-hint`,t.textContent=e,this.#s.hidden=!1,this.#s.appendChild(t),this.#s.scrollTop=this.#s.scrollHeight,this.#c.hidden=!1,this.#c.classList.add(`cp-attention`)}#$(e){this.#Y();let t=document.createElement(`table`);t.className=`cp-table`;let n=t.createTHead().insertRow();for(let t of e.columns){let e=document.createElement(`th`);e.textContent=t,n.appendChild(e)}let r=t.createTBody();for(let t of e.rows){let e=r.insertRow();for(let n of t)e.insertCell().textContent=n==null?``:String(n)}this.#s.hidden=!1,this.#s.appendChild(t)}#ee(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}};function L(e=D){typeof window>`u`||typeof customElements>`u`||customElements.get(e)||customElements.define(e,I)}L();export{T as a,w as c,s as d,o as f,C as i,c as l,e as m,L as n,S as o,a as p,E as r,x as s,I as t,l as u};
|
package/dist/element.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e,t}from"./element-
|
|
1
|
+
import{n as e,t}from"./element-VP5lhHx6.js";export{t as CodePlayPadElement,e as defineCodePlayPad};
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,f as r,i,l as a,m as o,n as s,o as c,p as l,r as u,s as d,t as f,u as p}from"./element-
|
|
1
|
+
import{a as e,c as t,d as n,f as r,i,l as a,m as o,n as s,o as c,p as l,r as u,s as d,t as f,u as p}from"./element-VP5lhHx6.js";import{n as m,o as h,r as g,t as _}from"./config-DZqcFPEP.js";export{f as CodePlayPadElement,_ as PYODIDE_VERSION,a as RunStopped,p as WorkerUnavailable,u as allProgress,i as clearSnippet,m as configurePython,g as configureTypeScript,o as createEditor,s as defineCodePlayPad,e as getProgress,n as getRuntime,r as listRuntimes,c as loadSnippet,h as pyodideAssetUrls,l as registerRuntime,d as saveSnippet,t as setProgress};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as e}from"./config-DZqcFPEP.js";import{t}from"./worker-runtime-
|
|
1
|
+
import{a as e}from"./config-DZqcFPEP.js";import{t}from"./worker-runtime-CJ3Zww5E.js";var n=String.raw`
|
|
2
2
|
let currentId = null;
|
|
3
3
|
let stdinLines = [];
|
|
4
4
|
let stdinExhausted = false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{i as e}from"./config-DZqcFPEP.js";import{t}from"./worker-runtime-
|
|
1
|
+
import{i as e}from"./config-DZqcFPEP.js";import{t}from"./worker-runtime-CJ3Zww5E.js";var n=`let pyodide=null,stdinLines=[],stdinExhausted=!1,currentId=null,interactive=!1,inputWaitMs=0,nextRequest=1;const pendingLines=new Map,MOUNT="/home/pyodide/playground";let written=new Set,internals=null;const REFRESH=["import importlib, os, sys","MOUNT = "+JSON.stringify(MOUNT),"os.makedirs(MOUNT, exist_ok=True)","sys.path.insert(0, MOUNT) if MOUNT not in sys.path else None","os.chdir(MOUNT)","for _name, _mod in list(sys.modules.items()):",' _file = getattr(_mod, "__file__", None) or ""',' _paths = list(getattr(_mod, "__path__", None) or [])'," if _file.startswith(MOUNT) or any(str(p).startswith(MOUNT) for p in _paths):"," del sys.modules[_name]","importlib.invalidate_caches()"].join(\`
|
|
2
2
|
\`);function flushStreams(){try{pyodide.runPython(\`import sys
|
|
3
3
|
sys.stdout.flush()
|
|
4
4
|
sys.stderr.flush()\`,{globals:internals})}catch{}flushOutput()}const INPUT_OVERRIDE=["import builtins, sys, js","from pyodide.ffi import run_sync","",'def _pg_input(prompt=""):',' if prompt != "":'," sys.stdout.write(str(prompt))"," sys.stdout.flush()"," line = run_sync(js._pgReadLine())"," # Pyodide maps JS null to its own JsNull singleton, not None, so test"," # for what we want rather than for the several ways EOF can arrive."," if not isinstance(line, str):",' raise EOFError("EOF when reading a line")'," return line","",'_pg_input.__name__ = "input"','_pg_input.__qualname__ = "input"',"_pg_input.__doc__ = builtins.input.__doc__","builtins.input = _pg_input"].join(\`
|
package/dist/react.d.ts
CHANGED
|
@@ -13,6 +13,9 @@ export interface CodePlayPadProps {
|
|
|
13
13
|
/** "python" (default), "javascript" or "typescript". */
|
|
14
14
|
language?: string;
|
|
15
15
|
height?: number | string;
|
|
16
|
+
/** Bounds for `height="auto"`. A number means pixels. */
|
|
17
|
+
minHeight?: number | string;
|
|
18
|
+
maxHeight?: number | string;
|
|
16
19
|
theme?: 'light' | 'dark' | 'auto';
|
|
17
20
|
readOnly?: boolean;
|
|
18
21
|
autoRun?: boolean;
|
|
@@ -33,7 +36,7 @@ export interface CodePlayPadProps {
|
|
|
33
36
|
* which keeps React 18 (no non-string prop support on custom elements) working
|
|
34
37
|
* the same as React 19.
|
|
35
38
|
*/
|
|
36
|
-
export declare function CodePlayPad({ code, files, entry, stdin, language, height, theme, readOnly, autoRun, persist, className, style, onReady, onRun, onOutput, onError }: CodePlayPadProps): import("react").JSX.Element;
|
|
39
|
+
export declare function CodePlayPad({ code, files, entry, stdin, language, height, minHeight, maxHeight, theme, readOnly, autoRun, persist, className, style, onReady, onRun, onOutput, onError }: CodePlayPadProps): import("react").JSX.Element;
|
|
37
40
|
declare module 'react' {
|
|
38
41
|
namespace JSX {
|
|
39
42
|
interface IntrinsicElements {
|
package/dist/react.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{n as e}from"./element-
|
|
1
|
+
import{n as e}from"./element-VP5lhHx6.js";import{useEffect as t,useRef as n}from"react";import{jsx as r}from"react/jsx-runtime";e();function i({code:e=``,files:i,entry:a,stdin:o,language:s=`python`,height:c,minHeight:l,maxHeight:u,theme:d=`auto`,readOnly:f,autoRun:p,persist:m,className:h,style:g,onReady:_,onRun:v,onOutput:y,onError:b}){let x=n(null),S=n({onReady:_,onRun:v,onOutput:y,onError:b});S.current={onReady:_,onRun:v,onOutput:y,onError:b},t(()=>{let e=x.current;if(!e)return;let t=(t,n)=>{let r=e=>n(S.current)?.(e.detail);return e.addEventListener(t,r),()=>e.removeEventListener(t,r)},n=[t(`py-ready`,e=>e.onReady),t(`py-run`,e=>e.onRun),t(`py-output`,e=>e.onOutput),t(`py-error`,e=>e.onError)];return()=>n.forEach(e=>e())},[]);let C=i?JSON.stringify(i):null;return t(()=>{let t=x.current;t&&(C?t.files=JSON.parse(C):e!==t.code&&(t.code=e))},[e,C]),t(()=>{x.current&&o!==void 0&&(x.current.stdin=o)},[o]),r(`code-playpad`,{ref:x,language:s,theme:d,class:h,style:g,...c===void 0?{}:{height:String(c)},...l===void 0?{}:{"min-height":String(l)},...u===void 0?{}:{"max-height":String(u)},...f?{readonly:``}:{},...p?{autorun:``}:{},...m?{persist:m}:{},...a?{entry:a}:{},children:i?null:e})}export{i as CodePlayPad,i as default};
|
package/dist/standalone.js
CHANGED
|
@@ -301,10 +301,10 @@ table.cp-table th { background: var(--cp-bg); }
|
|
|
301
301
|
}
|
|
302
302
|
`,h=e=>()=>import(`./dist-CqpVTRAq.js`).then(t=>t.javascript(e)),g={py:()=>import(`./dist-D_qJovVA.js`).then(e=>e.python()),pyi:()=>import(`./dist-D_qJovVA.js`).then(e=>e.python()),js:h({}),mjs:h({}),cjs:h({}),jsx:h({jsx:!0}),ts:h({typescript:!0}),mts:h({typescript:!0}),cts:h({typescript:!0}),tsx:h({typescript:!0,jsx:!0}),json:()=>import(`./dist-XlpaG2JF.js`).then(e=>e.json())};async function _(e){let t=g[e.includes(`.`)?e.slice(e.lastIndexOf(`.`)+1).toLowerCase():``];return t?t().catch(()=>null):null}var v=`code-playpad`,y=1,b=`snippets`,x=`progress`,S=null;function C(){return typeof indexedDB>`u`?Promise.reject(Error(`IndexedDB unavailable`)):(S||=new Promise((e,t)=>{let n=indexedDB.open(v,y);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(b)||e.createObjectStore(b),e.objectStoreNames.contains(x)||e.createObjectStore(x,{keyPath:`id`})},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)}).catch(e=>{throw S=null,e}),S)}function w(e,t,n){return C().then(r=>new Promise((i,a)=>{let o=n(r.transaction(e,t).objectStore(e));o.onsuccess=()=>i(o.result),o.onerror=()=>a(o.error)}))}function T(e,t){return w(b,`readwrite`,n=>n.put(t,e)).then(()=>void 0)}function E(e){return w(b,`readonly`,t=>t.get(e)).then(e=>e??null)}function D(e){return w(b,`readwrite`,t=>t.delete(e)).then(()=>void 0)}function O(e,t={}){let n={completed:!0,...t,id:e,updatedAt:Date.now()};return w(x,`readwrite`,e=>e.put(n)).then(()=>n)}function k(e){return w(x,`readonly`,t=>t.get(e)).then(e=>e??null)}function A(){return w(x,`readonly`,e=>e.getAll())}var j=`code-playpad`;function M(e){let t=e.replace(/\t/g,` `).split(`
|
|
303
303
|
`);for(;t.length&&t[0].trim()===``;)t.shift();for(;t.length&&t[t.length-1].trim()===``;)t.pop();let n=t.filter(e=>e.trim()!==``).reduce((e,t)=>Math.min(e,t.match(/^ */)[0].length),1/0);return(n===1/0?t:t.map(e=>e.slice(n))).join(`
|
|
304
|
-
`)}var N=2e5,P=`playpad-file`,F={python:`main.py`,javascript:`main.js`,typescript:`main.ts`};function I(e){return F[e]??`main.txt`}function L(e,t){let n={};for(let t of e.children){if(t.tagName.toLowerCase()!==P)continue;let e=t.getAttribute(`name`);e&&(n[e]=M(t.textContent??``))}if(Object.keys(n).length)return n;let r=[...e.childNodes].filter(e=>e.nodeType!==Node.ELEMENT_NODE||e.tagName.toLowerCase()!==P),i=r.some(e=>e.nodeType===Node.ELEMENT_NODE),a=M(r.filter(e=>!(i&&e.nodeType===Node.TEXT_NODE&&!(e.textContent??``).trim())).map(e=>e.textContent??``).join(``));return{[t]:a}}function R(e,t){try{let t=JSON.parse(e);if(t&&typeof t==`object`&&!Array.isArray(t)){let e=t;return Object.keys(e).length?e:null}}catch{}return e?{[t]:e}:null}var z=typeof HTMLElement>`u`?class{}:HTMLElement,B=class extends z{static get observedAttributes(){return[`language`,`height`,`min-height`,`max-height`,`theme`,`readonly`,`autorun`,`stdin`,`persist`,`entry`]}#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u=null;#d=null;#f=null;#p=null;#m={};#h={};#g=`main.py`;#_=null;#v;#y=!1;#b=!1;#x=null;#S=null;#C=()=>this.#
|
|
304
|
+
`)}var N=2e5,P=`playpad-file`,F={python:`main.py`,javascript:`main.js`,typescript:`main.ts`};function I(e){return F[e]??`main.txt`}function L(e,t){let n={};for(let t of e.children){if(t.tagName.toLowerCase()!==P)continue;let e=t.getAttribute(`name`);e&&(n[e]=M(t.textContent??``))}if(Object.keys(n).length)return n;let r=[...e.childNodes].filter(e=>e.nodeType!==Node.ELEMENT_NODE||e.tagName.toLowerCase()!==P),i=r.some(e=>e.nodeType===Node.ELEMENT_NODE),a=M(r.filter(e=>!(i&&e.nodeType===Node.TEXT_NODE&&!(e.textContent??``).trim())).map(e=>e.textContent??``).join(``));return{[t]:a}}function R(e,t){try{let t=JSON.parse(e);if(t&&typeof t==`object`&&!Array.isArray(t)){let e=t;return Object.keys(e).length?e:null}}catch{}return e?{[t]:e}:null}var z=typeof HTMLElement>`u`?class{}:HTMLElement,B=class extends z{static get observedAttributes(){return[`language`,`height`,`min-height`,`max-height`,`theme`,`readonly`,`autorun`,`stdin`,`persist`,`entry`]}#e;#t;#n;#r;#i;#a;#o;#s;#c;#l;#u=null;#d=null;#f=null;#p=null;#m={};#h={};#g=`main.py`;#_=null;#v;#y=!1;#b=!1;#x=null;#S=null;#C=null;#w=()=>this.#W();#T=null;#E=!0;#D=[];#O=null;#k=null;#A=null;#j=0;#M=!1;constructor(){super(),this.#e=this.attachShadow({mode:`open`})}get code(){return this.#u?this.#u.getCode():this.#m[this.#g]??``}set code(e){this.#m[this.#g]=e,this.#R()}get files(){return this.#B()}set files(e){let t=Object.keys(e);if(!t.length)return;let n=Object.keys(this.#h);n.length===1&&!this.#h[n[0]]&&(this.#h={...e}),this.#m={...e},t.includes(this.#g)||(this.#g=this.#z()),this.#u?.forgetFiles([]),this.#R(),this.#V(),this.#L()}get entry(){return this.#z()}set entry(e){this.setAttribute(`entry`,e)}openFile(e){e in this.#m&&(this.#u&&(this.#m[this.#g]=this.#u.getCode(),this.#u.openFile(e,this.#m[e]??``)),this.#g=e,this.#V(),this.#L())}get stdin(){return this.#l?this.#l.value:``}set stdin(e){this.#l&&(this.#l.value=e,this.#c.hidden=!1)}get language(){return this.getAttribute(`language`)||`python`}set language(e){this.setAttribute(`language`,e)}getCode(){return this.code}getFiles(){return this.#B()}reset(){this.#m={...this.#h},this.#g=this.#z(),this.#u?.forgetFiles([]),this.#R(),this.#V(),this.#q(),this.#s.hidden=!0,this.#K(this.#f?.ready?`${this.#f.label} ready`:``,`idle`)}async run(){if(this.#y)return;await this.#d;let e=this.#B(),t=this.#z(),n=e[t]??``;this.#y=!0,this.#n.disabled=!0,this.#r.hidden=!1,this.#q(),this.#s.hidden=!1,this.#ee(`py-run`,{code:n,entry:t,files:e,language:this.language});try{let r=await this.#I();this.#K(`Queued…`,`busy`),this.#o.hidden=!1;let i=await r.run(n,{stdin:this.#l.value,files:e,entry:t,onStart:()=>this.#K(`Running…`,`busy`),onStdout:e=>this.#J(e),onStderr:e=>this.#J(e,`cp-err`),onEcho:e=>this.#J(e,`cp-in`),onStdinRequest:()=>this.#X()});return i.table&&this.#$(i.table),i.value!==void 0&&this.#J(`${i.value}\n`,`cp-value`),i.hint&&this.#Q(i.hint),i.stderr?(this.#K(`Error · ${i.durationMs} ms`,`error`),this.#ee(`py-error`,{message:i.stderr,hint:i.hint,language:this.language})):this.#K(`Done in ${i.durationMs} ms`,`ok`),this.#ee(`py-output`,{...i,language:this.language}),i}catch(e){if(e instanceof i)this.#J(`
|
|
305
305
|
[stopped]
|
|
306
|
-
`,`cp-err`),this.#
|
|
307
|
-
`,`cp-err`),this.#
|
|
306
|
+
`,`cp-err`),this.#K(`Stopped`,`error`);else{let t=e instanceof Error?e.message:String(e);this.#J(t+`
|
|
307
|
+
`,`cp-err`),this.#K(`Failed to start`,`error`),this.#ee(`py-error`,{message:t,language:this.language})}return}finally{this.#Z(null),this.#Y(),this.#y=!1,this.#n.disabled=!1,this.#r.hidden=!0,this.#o.hidden=!0}}stop(){this.#f?.stop?.()}prewarm(){return this.#I()}connectedCallback(){if(this.#M){this.#F();return}if(!this.textContent?.trim()&&document.readyState===`loading`){document.addEventListener(`DOMContentLoaded`,()=>this.isConnected&&this.#N(),{once:!0});return}this.#N()}disconnectedCallback(){this.#x?.disconnect(),this.#x=null,this.#S?.removeEventListener(`change`,this.#w),this.#S=null,this.#C?.disconnect(),this.#C=null,this.#T&&clearTimeout(this.#T),this.#Z(null),setTimeout(()=>{this.isConnected||!this.#u||(this.#_=this.#B(),this.#u.destroy(),this.#u=null,this.#d=null,this.#M=!1)},0)}attributeChangedCallback(e,t,n){if(this.#M)switch(e){case`theme`:this.#W();break;case`height`:case`min-height`:case`max-height`:this.#G();break;case`readonly`:this.#u?.setReadOnly(n!==null);break;case`stdin`:this.#c.hidden=n===null&&!this.#l.value;break;case`entry`:this.#V();break;case`language`:this.#f=null,this.#p=null,this.#b=!1,this.#I().catch(()=>{})}}#N(){this.#M=!0,this.#h=L(this,I(this.language)),this.#m=this.#_??{...this.#h},this.#g=this.#z(),this.#P(),this.#W(),this.#G();let e=this.getAttribute(`persist`);this.#d=(async()=>{if(e&&this.#_==null){let t=await E(e).catch(()=>null),n=t==null?null:R(t,I(this.language));if(n){let e={};for(let t of Object.keys(this.#h))e[t]=n[t]??this.#h[t];this.#m=e,this.#g=this.#z()}}this.#V();let t=await p(this.language).catch(()=>null),n=t?await t.editorLanguage().catch(()=>null):null;this.#u=await o({parent:this.#t,doc:this.#m[this.#g]??``,name:this.#g,dark:this.#U(),readOnly:this.hasAttribute(`readonly`),language:n,onRun:()=>void this.run(),onChange:e=>this.#H(e)}),this.#_=null,this.#L()})(),this.#F()}#P(){let e=document.createElement(`style`);e.textContent=m;let t=document.createElement(`div`);t.className=`cp-root`,t.innerHTML=`
|
|
308
308
|
<div class="cp-toolbar">
|
|
309
309
|
<button class="cp-primary" part="run-button" data-action="run">Run</button>
|
|
310
310
|
<button class="cp-danger" part="stop-button" data-action="stop" hidden>Stop</button>
|
|
@@ -319,10 +319,10 @@ table.cp-table th { background: var(--cp-bg); }
|
|
|
319
319
|
<label>Standard input — one value per line</label>
|
|
320
320
|
<textarea rows="2" spellcheck="false"></textarea>
|
|
321
321
|
</div>
|
|
322
|
-
<pre class="cp-output" part="output" hidden></pre>`,this.#e.replaceChildren(e,t),this.#n=t.querySelector(`[data-action="run"]`),this.#r=t.querySelector(`[data-action="stop"]`),this.#i=t.querySelector(`[data-action="reset"]`),this.#a=t.querySelector(`.cp-status`),this.#o=t.querySelector(`.cp-progress`),this.#v=t.querySelector(`.cp-tabs`),this.#t=t.querySelector(`.cp-editor`),this.#c=t.querySelector(`.cp-stdin`),this.#l=t.querySelector(`.cp-stdin textarea`),this.#s=t.querySelector(`.cp-output`),this.#c.hidden=!this.hasAttribute(`stdin`),this.#l.addEventListener(`input`,()=>this.#c.classList.remove(`cp-attention`)),this.#v.addEventListener(`click`,e=>{let t=e.target.closest(`button[data-file]`);t&&this.openFile(t.getAttribute(`data-file`))}),this.#n.addEventListener(`click`,()=>void this.run()),this.#r.addEventListener(`click`,()=>this.stop()),this.#i.addEventListener(`click`,()=>this.reset());let n=()=>void this.#
|
|
323
|
-
`,`cp-err`),this.#s.hidden=!1,e}finally{this.#o.hidden=!0}return this.#
|
|
324
|
-
`),this.#
|
|
322
|
+
<pre class="cp-output" part="output" hidden></pre>`,this.#e.replaceChildren(e,t),this.#n=t.querySelector(`[data-action="run"]`),this.#r=t.querySelector(`[data-action="stop"]`),this.#i=t.querySelector(`[data-action="reset"]`),this.#a=t.querySelector(`.cp-status`),this.#o=t.querySelector(`.cp-progress`),this.#v=t.querySelector(`.cp-tabs`),this.#t=t.querySelector(`.cp-editor`),this.#c=t.querySelector(`.cp-stdin`),this.#l=t.querySelector(`.cp-stdin textarea`),this.#s=t.querySelector(`.cp-output`),this.#c.hidden=!this.hasAttribute(`stdin`),this.#l.addEventListener(`input`,()=>this.#c.classList.remove(`cp-attention`)),this.#v.addEventListener(`click`,e=>{let t=e.target.closest(`button[data-file]`);t&&this.openFile(t.getAttribute(`data-file`))}),this.#n.addEventListener(`click`,()=>void this.run()),this.#r.addEventListener(`click`,()=>this.stop()),this.#i.addEventListener(`click`,()=>this.reset());let n=()=>void this.#I().catch(()=>{});this.#t.addEventListener(`pointerdown`,n,{once:!0}),this.#t.addEventListener(`focusin`,n,{once:!0})}#F(){this.#x||typeof IntersectionObserver>`u`||(this.#x=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&(this.#x?.disconnect(),this.#x=null,this.#I().then(()=>{this.hasAttribute(`autorun`)&&this.run()}).catch(()=>{}))},{rootMargin:`300px`}),this.#x.observe(this))}#I(){return this.#p||=(async()=>{let e=await p(this.language);this.#f=e,e.ready||(this.#o.hidden=!1,this.#K(`Loading ${e.label}…`,`busy`));try{await e.load(e=>{this.#y||this.#K(e.message,e.phase===`ready`?`ok`:`busy`)})}catch(e){this.#p=null,this.#K(`Runtime unavailable`,`error`);let t=e instanceof Error?e.message:String(e);throw this.#J(t+`
|
|
323
|
+
`,`cp-err`),this.#s.hidden=!1,e}finally{this.#o.hidden=!0}return this.#L(),this.#b||(this.#b=!0,this.#K(`${e.label} ready`,`ok`),this.#ee(`py-ready`,{language:e.id,label:e.label})),e})(),this.#p}async#L(){if(!this.#u)return;let e=this.#g,t=await _(e);if(t){this.#g===e&&this.#u?.setLanguage(t);return}let n=e.includes(`.`)?null:await(this.#f??await p(this.language).catch(()=>null))?.editorLanguage().catch(()=>null);this.#g===e&&this.#u?.setLanguage(n??[])}#R(){if(this.#u){this.#u.setCode(this.#m[this.#g]??``);return}this.#d?.then(()=>{this.#u?.setCode(this.#m[this.#g]??``),this.#V()})}#z(){let e=Object.keys(this.#m),t=this.getAttribute(`entry`);if(t&&e.includes(t))return t;let n=I(this.language);return e.includes(n)?n:e[0]??n}#B(){let e={...this.#m};if(this.#u)for(let t of Object.keys(e))e[t]=this.#u.fileCode(t,e[t]);return e}#V(){let e=Object.keys(this.#m);if(this.#v.hidden=e.length<2,this.#v.hidden){this.#v.replaceChildren();return}let t=this.#z();this.#v.replaceChildren(...e.map(e=>{let n=document.createElement(`button`);return n.dataset.file=e,n.textContent=e,n.setAttribute(`aria-pressed`,String(e===this.#g)),e===t&&(n.classList.add(`cp-entry`),n.title=`entry point — this is the file Run executes`),n}))}#H(e){this.#m[this.#g]=e;let t=this.getAttribute(`persist`);if(!t)return;this.#T&&clearTimeout(this.#T);let n=JSON.stringify(this.#B());this.#T=setTimeout(()=>void T(t,n).catch(()=>{}),400)}#U(){let e=this.getAttribute(`theme`)??`auto`;if(e===`dark`)return!0;if(e===`light`)return!1;let t=typeof document>`u`?``:getComputedStyle(document.documentElement).colorScheme,n=t.includes(`dark`);return n===t.includes(`light`)?!!this.#S?.matches:n}#W(){!this.#S&&typeof matchMedia==`function`&&(this.#S=matchMedia(`(prefers-color-scheme: dark)`),this.#S.addEventListener(`change`,this.#w)),!this.#C&&typeof MutationObserver==`function`&&typeof document<`u`&&(this.#C=new MutationObserver(this.#w),this.#C.observe(document.documentElement,{attributes:!0,attributeFilter:[`class`,`style`,`data-theme`,`data-color-scheme`,`color-scheme`]}));let e=this.#U();this.setAttribute(`data-resolved-theme`,e?`dark`:`light`),this.#u?.setTheme(e)}#G(){for(let[e,t]of[[`height`,`--cp-height`],[`min-height`,`--cp-min-height`],[`max-height`,`--cp-max-height`]]){let n=this.getAttribute(e);n===null||n===``?this.style.removeProperty(t):this.style.setProperty(t,/^\d+$/.test(n)?`${n}px`:n)}}#K(e,t){this.#a.textContent=e,this.#a.dataset.state=t}#q(){this.#A&&clearTimeout(this.#A),this.#A=null,this.#D=[],this.#j=0,this.#s.replaceChildren(),this.#E=!0}#J(e,t){if(!e)return;let n=t===`cp-err`&&!this.#E?`\n${e}`:e;this.#E=n.endsWith(`
|
|
324
|
+
`),this.#D.push({text:n,className:t}),this.#A??=setTimeout(()=>this.#Y(),16)}#Y(){if(this.#A&&clearTimeout(this.#A),this.#A=null,!this.#D.length)return;let e=this.#D;this.#D=[];let t=document.createDocumentFragment(),n=``,r,i=()=>{if(!n)return;let e=document.createElement(`span`);r&&(e.className=r),e.textContent=n,t.appendChild(e),n=``};for(let t of e){if(this.#j>=N)break;let e=t.text;this.#j+e.length>N&&(e=e.slice(0,N-this.#j)+`
|
|
325
325
|
… output truncated for display — the full text is in the py-output event
|
|
326
|
-
`),this.#
|
|
327
|
-
`,`cp-in`):this.#
|
|
328
|
-
`,`cp-in`),this.#
|
|
326
|
+
`),this.#j+=t.text.length,r!==t.className&&(i(),r=t.className),n+=e}i(),this.#s.hidden=!1;let a=this.#s.querySelector(`.cp-prompt`);a?this.#s.insertBefore(t,a):this.#s.appendChild(t),this.#s.scrollTop=this.#s.scrollHeight}#X(){return this.#Y(),this.#Z(null),new Promise(e=>{let t=document.createElement(`form`);t.className=`cp-prompt`;let n=document.createElement(`input`);n.type=`text`,n.setAttribute(`aria-label`,`Program input — press Enter to send`),n.autocapitalize=`off`,n.autocomplete=`off`,n.spellcheck=!1;let r=document.createElement(`button`);r.type=`submit`,r.className=`cp-send`,r.textContent=`⏎`,r.title=`Send this line (Enter)`;let i=document.createElement(`button`);i.type=`button`,i.className=`cp-eof`,i.textContent=`EOF`,i.title=`End of input (Ctrl-D) — makes input() raise EOFError`;let a=n=>{this.#O===e&&(this.#O=null,this.#k=null,t.remove(),n===null?this.#J(`
|
|
327
|
+
`,`cp-in`):this.#J(n+`
|
|
328
|
+
`,`cp-in`),this.#Y(),e(n))};t.addEventListener(`submit`,e=>{e.preventDefault(),a(n.value)}),i.addEventListener(`click`,()=>a(null)),n.addEventListener(`keydown`,e=>{(e.key===`Escape`||e.key===`d`&&e.ctrlKey)&&(e.preventDefault(),a(null))}),t.append(n,r,i),this.#s.hidden=!1,this.#s.appendChild(t),this.#s.scrollTop=this.#s.scrollHeight,this.#O=e,this.#k=a,n.focus({preventScroll:!0})})}#Z(e){this.#k?.(e),this.#k=null,this.#O=null}#Q(e){this.#Y();let t=document.createElement(`div`);t.className=`cp-hint`,t.textContent=e,this.#s.hidden=!1,this.#s.appendChild(t),this.#s.scrollTop=this.#s.scrollHeight,this.#c.hidden=!1,this.#c.classList.add(`cp-attention`)}#$(e){this.#Y();let t=document.createElement(`table`);t.className=`cp-table`;let n=t.createTHead().insertRow();for(let t of e.columns){let e=document.createElement(`th`);e.textContent=t,n.appendChild(e)}let r=t.createTBody();for(let t of e.rows){let e=r.insertRow();for(let n of t)e.insertCell().textContent=n==null?``:String(n)}this.#s.hidden=!1,this.#s.appendChild(t)}#ee(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}};function V(e=j){typeof window>`u`||typeof customElements>`u`||customElements.get(e)||customElements.define(e,B)}V();export{B as CodePlayPadElement,a as PYODIDE_VERSION,i as RunStopped,e as WorkerUnavailable,A as allProgress,D as clearSnippet,t as configurePython,r as configureTypeScript,o as createEditor,V as defineCodePlayPad,k as getProgress,p as getRuntime,f as listRuntimes,E as loadSnippet,n as pyodideAssetUrls,d as registerRuntime,T as saveSnippet,O as setProgress};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{l as e,u as t}from"./element-
|
|
1
|
+
import{l as e,u as t}from"./element-VP5lhHx6.js";var n=1,r=class{#e=null;#t=null;#n=`module`;#r=null;#i=!1;#a=!1;#o=``;#s=new Set;#c=[];#l=null;#u=!1;initPayload(){return{}}get ready(){return this.#i}get interactiveStdin(){return this.#a}get version(){return this.#o}load(e){e&&this.#s.add(e);let n=()=>{e&&this.#s.delete(e)};if(!this.#r){let e=new Promise((e,n)=>{let r;try{if(typeof Worker>`u`)throw Error(`Worker constructor missing`);let e=new Blob([this.source],{type:`text/javascript`});this.#t=URL.createObjectURL(e),r=new Worker(this.#t,{type:this.#n})}catch(e){n(new t(`Could not start a Web Worker: ${e.message}`));return}this.#e=r,r.onmessage=t=>{let r=t.data;if(r.type===`ready`){this.#o=r.version,this.#a=r.interactiveStdin===!0,this.#i=!0,this.#p({phase:`ready`,message:`${this.label} ready`}),e();return}if(r.type===`fatal`&&!this.#i){this.#d(r.message,e,n);return}this.#m(r)},r.onerror=t=>{let r=t.message||`Worker failed to start`;this.#i?this.#l&&this.#g(this.#l,Error(r)):this.#d(r,e,n)},r.postMessage({type:`init`,...this.initPayload()})});this.#r=e,e.catch(()=>{this.#r===e&&(this.#r=null)})}return this.#r.then(n,e=>{throw n(),e})}run(e,t={}){let r,i=new Promise(e=>{r=e});return new Promise((a,o)=>{this.#c.push({id:n++,code:e,options:t,stdout:``,stderr:``,resolve:a,reject:o,done:i,markDone:r}),this.#_()})}stop(){let t=this.#e!==null;this.#f(),this.#l&&this.#g(this.#l,new e),t&&this.load().catch(()=>{})}#d(e,t,n){if(this.#f(),this.#n===`module`){this.#n=`classic`,this.load().then(t,n);return}n(Error(e))}#f(){this.#e?.terminate(),this.#e=null,this.#t&&=(URL.revokeObjectURL(this.#t),null),this.#r=null,this.#i=!1}#p(e){for(let t of this.#s)t(e)}#m(e){if(e.type===`progress`){this.#p({phase:e.phase,message:e.message});return}let t=this.#l;if(!(!t||!(`id`in e)||e.id!==t.id))switch(e.type){case`stdout`:t.stdout+=e.text,t.options.onStdout?.(e.text);break;case`echo`:t.options.onEcho?.(e.text);break;case`stdin-request`:this.#h(t,e.request);break;case`stderr`:t.stderr+=e.text,t.options.onStderr?.(e.text);break;case`result`:t.resolve({stdout:t.stdout,stderr:t.stderr||void 0,value:e.value,durationMs:Math.round(e.durationMs)}),t.markDone();break;case`error`:case`fatal`:{let n=(t.stderr&&!t.stderr.endsWith(`
|
|
2
2
|
`)?`
|
|
3
3
|
`:``)+e.message+`
|
|
4
4
|
`;t.stderr+=n,t.options.onStderr?.(n),t.resolve({stdout:t.stdout,stderr:t.stderr,hint:`hint`in e?e.hint:void 0,durationMs:Math.round(`durationMs`in e?e.durationMs:0)}),t.markDone();break}}}#h(e,t){let n=this.#e,r=r=>{this.#e===n&&this.#l===e&&n?.postMessage({type:`stdin-response`,request:t,text:r})},i=e.options.onStdinRequest;if(!i){r(null);return}i().then(r,()=>r(null))}#g(e,t){e.reject(t),e.markDone()}async#_(){if(!this.#u){this.#u=!0;try{for(;this.#c.length;){let e=this.#c.shift();this.#l=e;try{await this.load(),e.options.onStart?.(),this.#e.postMessage({type:`run`,id:e.id,code:e.code,stdin:e.options.stdin,files:e.options.files,entry:e.options.entry}),await e.done}catch(t){this.#g(e,t)}finally{this.#l=null}}}finally{this.#u=!1}}}};export{r as t};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "code-playpad",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Embeddable browser code playground: a CodeMirror editor that runs Python, JavaScript and TypeScript in the browser. No backend. One shared worker per page, blocking input(), multi-file projects, lazy per-language loading.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|