code-playpad 0.3.1 → 0.4.0

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 CHANGED
@@ -9,7 +9,7 @@ entirely in the reader's browser. 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.3.1/dist/standalone.js"></script>
12
+ <script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.4.0/dist/standalone.js"></script>
13
13
 
14
14
  <code-playpad height="auto">
15
15
  print("hello from a static page")
@@ -30,7 +30,7 @@ entirely in the reader's browser. No backend, no iframe, no sign-up.
30
30
  | `python` (default) | Python 3.14 via Pyodide — full standard library, `input()`, real tracebacks, `numpy` on demand | ~6 MB (13.5 MB unpacked), once, cached |
31
31
  | `javascript` | The browser's own engine, in a Worker | nothing |
32
32
  | `typescript` | Same engine, types stripped before running | ~70 KB, once, cached |
33
- | `java` | A documented subset of Java 25, compiled and interpreted in a Worker by an engine built for this package — see [Java](#java) | nothing: the engine is a ~27 KB (gzipped) chunk of this package, loaded with the first Java widget |
33
+ | `java` | A documented subset of Java 25, compiled and interpreted in a Worker by an engine built for this package — see [Java](#java) | nothing: the engine is a ~104 KB (gzipped) chunk of this package, loaded with the first Java widget, and its library a ~75 KB chunk loaded the first time a program uses it |
34
34
 
35
35
  ```html
36
36
  <code-playpad height="auto">print("python is the default")</code-playpad>
@@ -78,52 +78,130 @@ documented subset of Java 25 and checks its behaviour against a real JDK 25:
78
78
 
79
79
  - Java 25 **compact source files** and instance `main`, or a classic
80
80
  `public class Main` with `public static void main(String[] args)`
81
- - `int`, `char`, `boolean`, `String`; one-dimensional arrays; static and
82
- instance methods with overloading and recursion; fields; `if`/`while`/`for`,
83
- `break`/`continue`/`return`; `String.length()`/`charAt()`/`equals()`;
84
- `System.out`/`System.err` and `IO.print`/`IO.println`
81
+ - **Objects:** several classes per file, nested and inner classes,
82
+ constructors and `this(...)`, `new`, fields and methods on any object,
83
+ `private`, and `toString()` with `@Override`
84
+ - **Classes and interfaces:** inheritance with `super`, overriding and
85
+ dispatch, `abstract` classes, interfaces with default and static methods,
86
+ `Comparable`, `equals`/`hashCode` overrides, `instanceof` with patterns,
87
+ casts; **enums** (with fields, methods and `switch`) and **records**
88
+ - **Exceptions:** `try`/`catch`/`finally`, multi-catch, try-with-resources,
89
+ `throw`, custom exception classes, checked exceptions checked as javac
90
+ checks them, `printStackTrace()`, and traces with `Caused by:`
91
+ - **Numbers:** `int`, `char`, `long`, `double` with Java's conversions,
92
+ overflow and `double` printing; `Math` (`pow`, `floorMod`, `addExact`,
93
+ the trigonometric functions, `random`, …) and `java.util.Random`, whose
94
+ seeded sequences are the JDK's
95
+ - `boolean`, `String`; arrays of any dimension (of objects too); static and
96
+ instance methods with overloading and recursion; fields
97
+ - **Control flow:** `if`, `while`, `do`, `for` and enhanced `for`, `switch`
98
+ statements and expressions, labeled `break`/`continue`, `return`, `?:`
99
+ - **Strings:** `substring`, `indexOf`, `split`, `replace`, `replaceAll`,
100
+ `matches`, `trim`/`strip`, case, `repeat`, `join`, `valueOf` and the rest,
101
+ with Java's regex syntax; `String.format` and `System.out.printf` with
102
+ Java's rounding (`%.2f` of 1.005 is `1.01`)
103
+ - `System.out`/`System.err`, `IO.print`/`IO.println`, `System.exit`,
104
+ `System.arraycopy`, `System.nanoTime`
105
+ - **Input:** `Scanner` over `System.in` (the widget's input box) or a
106
+ `String`, with `nextInt`, `nextLine`, `nextDouble` and the rest, and
107
+ `IO.readln`. The classic `nextInt()`-then-`nextLine()` pitfall behaves
108
+ exactly as in Java
109
+ - **Collections and library:** `import`, generics at use sites with the
110
+ diamond, boxing; `ArrayList`, `HashMap`, `HashSet`, `LinkedHashMap`,
111
+ `ArrayDeque`, `PriorityQueue`, `TreeMap`, `TreeSet`, `LinkedList`, `Stack`,
112
+ `EnumMap`, `EnumSet`, their iterators and enhanced `for`, `subList`;
113
+ `List.of`, `Map.of`, `Arrays.asList`, `toArray`; `StringBuilder`, the
114
+ wrapper classes, `Character`, `Arrays` and `Collections`. A `HashMap` or
115
+ `HashSet` iterates and prints in the JDK's order.
116
+ - **Big numbers:** `BigInteger` and `BigDecimal`, with the JDK's rounding
117
+ modes and `MathContext`
118
+ - **Fast input and output**, as competitive programmers write it:
119
+ `BufferedReader`, `InputStreamReader`, `StringTokenizer`,
120
+ `StreamTokenizer`, `PrintWriter` and `BufferedWriter`. Output you forget
121
+ to flush is lost exactly as on the JDK
122
+ - **Lambdas and method references** for any functional interface
123
+ (`Integer::compare`, `Comparator.comparing(...).reversed()`, `merge`,
124
+ `computeIfAbsent`), capturing effectively final locals, with stack traces
125
+ and sorting that match the JDK's (even a comparator that overflows)
126
+ - **Generics:** generic classes, interfaces, records and methods with
127
+ bounds and wildcards; raw types; `var`; varargs; `@Override`,
128
+ `@SuppressWarnings`, `@FunctionalInterface`; javac's `[unchecked]`
129
+ warnings (for `List<Integer>[] g = new ArrayList[n]`, say) printed before
130
+ the output, as the JDK's source launcher prints them
131
+ - **Anonymous and local classes** (`new Comparator<Integer>() { ... }`)
132
+ named and capturing as javac does, and **streams**: `Stream`, `IntStream`,
133
+ `Collectors` (`groupingBy`, `toMap`, `counting`, `joining`, …), `Optional`,
134
+ run element by element in the JDK's order
135
+ - **Modern Java:** `sealed` classes and interfaces, pattern matching in
136
+ `switch` and `instanceof` (record patterns, guards, `case null`), text
137
+ blocks, static imports, initializer blocks, enum constants with their own
138
+ bodies, and `Interface.super.method()`
139
+ - **More types:** `float`, `byte` and `short`, with Java's conversions and
140
+ printing
85
141
  - **Errors read like the JDK's**: compile errors in javac's format, exceptions
86
142
  with Java stack traces and the JDK's NullPointerException messages
87
143
 
88
- Anything outside the subset — `long`/`double`, `?:`, `switch`, objects and
89
- constructors, collections, `Scanner`, imports — is reported as *not supported in
90
- this version* rather than run with a different meaning. The file is `Main.java`
91
- unless `entry` says otherwise. The engine stops runaway programs after 200
92
- million steps and explains why.
144
+ Anything outside the subset (`java.time`, `Pattern` and `Matcher`, parallel
145
+ streams, `outer.new Inner()`) is reported as *not supported in this
146
+ version* rather than run with a different meaning. The file is `Main.java`
147
+ unless `entry` says otherwise.
148
+
149
+ **Speed.** In loop-heavy code the engine runs roughly 150–300 million
150
+ bytecode instructions a second on a laptop. A typical practice solution (10⁵–10⁶
151
+ elements, nested loops, maps, sorting with a comparator) finishes in well
152
+ under a second. The engine stops a runaway program after 200 million steps
153
+ (about 0.6–1.5 s of work) and explains why.
154
+
155
+ **Exactly what is supported**, feature by feature and method by method, with
156
+ the evidence behind it and every known difference from the JDK, is in
157
+ [JAVA-COMPATIBILITY.md](https://cdn.jsdelivr.net/npm/code-playpad/JAVA-COMPATIBILITY.md), which ships in the package.
93
158
 
94
159
  ## Download size
95
160
 
96
- What a page downloads once a reader presses Run, with the CDN build. Measured by
97
- loading one-widget pages in a browser and gzipping each file the page fetched;
98
- CDN downloads are what the CDN actually sent.
161
+ What a page downloads once a reader presses Run, with the CDN build, as
162
+ measured for 0.4.0. Each one-widget page was loaded in a browser, and each
163
+ code-playpad file it fetched was gzipped. The CDN column is what the CDN
164
+ actually sent.
99
165
 
100
- | Page uses | code-playpad (gzip) | Also fetched from a CDN at run time | 0.2.1 (gzip) |
166
+ | Page uses | code-playpad (gzip) | Also fetched from a CDN at run time | 0.3.0 (gzip) |
101
167
  | --- | --- | --- | --- |
102
- | Python | **165.8 KB** | Pyodide, about **6.0 MB** (a 3.3 MB wasm file and a 2.4 MB standard library) | 165.2 KB |
103
- | JavaScript | **179.9 KB** | nothing | 180.5 KB |
104
- | TypeScript | **179.9 KB** | Sucrase, 72 KB (≈ 252 KB in total) | ≈ 252.6 KB |
105
- | Java | **189.7 KB** | nothing | n/a |
106
- | All four | 247.2 KB | Pyodide and Sucrase | n/a |
107
-
108
- - **A shared base of 144 KB is in every row:** the editor (CodeMirror) and the
109
- widget, the same as 0.2.1 within 0.6 KB.
110
- - **Each language adds:** Python 21.7 KB; JavaScript/TypeScript 35.8 KB; Java
111
- 45.6 KB, which is the 26.5 KB engine plus 19 KB of Java syntax highlighting.
112
- - **Languages load lazily.** A Java page never fetches the Python or JavaScript
113
- runtimes, and Python and JavaScript pages never fetch Java, so adding Java left
114
- Python-only and JavaScript-only pages the same size as in 0.2.1.
115
- - **Java is the lightest to run:** it is self-contained, while Python's first run
116
- is dominated by the roughly 6 MB Pyodide download (cached after that).
117
- - **With npm and a bundler** you already ship CodeMirror yourself. code-playpad's
118
- own code then costs about 9 KB, plus the runtime chunk for each language used;
119
- Java's is 27 KB, plus `@codemirror/lang-java` if you install it.
168
+ | Python | **166.3 KB** | Pyodide, about **6.0 MB** (a 3.3 MB wasm file and a 2.4 MB standard library) | 165.8 KB |
169
+ | JavaScript | **180.4 KB** | nothing | 179.9 KB |
170
+ | TypeScript | **180.4 KB** | Sucrase, 72 KB (≈ 252 KB in total) | 179.9 KB |
171
+ | Java | **267.8 KB** | nothing | 216.4 KB |
172
+ | Java, with collections | **342.4 KB** | nothing | 233.2 KB |
173
+ | All four | 399.9 KB | Pyodide and Sucrase | 273.9 KB |
174
+
175
+ - **Every row includes a 144.5 KB shared base:** the editor (CodeMirror) and
176
+ the widget, the same as in 0.3.0.
177
+ - **Each language adds:**
178
+ - Python: 21.7 KB.
179
+ - JavaScript/TypeScript: 35.9 KB.
180
+ - Java: 123.2 KB (the 104.2 KB engine plus 19 KB of Java syntax
181
+ highlighting).
182
+ - The first Java program that uses collections, `StringBuilder` or another
183
+ library class also adds the 74.6 KB Java library, once per page.
184
+ - **Java grew** from 0.3.0 because the subset did, from basic Java to most of
185
+ what practice programs use (see the version history). The owner treats
186
+ download size as secondary to coverage. The package's size checks still
187
+ catch accidental growth: 128 KiB for the Java chunk and 96 KiB for its
188
+ library.
189
+ - **Languages load lazily.** A Java page never fetches the Python or
190
+ JavaScript runtimes, and Python and JavaScript pages never fetch Java. As a
191
+ result, Python-only and JavaScript-only pages are the same size as in 0.3.0.
192
+ - **Java needs no CDN:** it is self-contained, while Python's first run
193
+ downloads about 6 MB of Pyodide (cached after that).
194
+ - **With npm and a bundler,** you already ship CodeMirror yourself.
195
+ code-playpad's own code then costs about 9 KB, plus the runtime chunk for
196
+ each language used. Java's chunk is 104 KB, and 74 KB more for its library
197
+ when a program uses it, plus `@codemirror/lang-java` if you install it.
120
198
 
121
199
  ## Install
122
200
 
123
201
  ### From a CDN — nothing to install
124
202
 
125
203
  ```html
126
- <script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.3.1/dist/standalone.js"></script>
204
+ <script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.4.0/dist/standalone.js"></script>
127
205
  ```
128
206
 
129
207
  This is the `standalone` build: CodeMirror is bundled in, so a static blog post
@@ -185,6 +263,7 @@ Next.js, Astro and SvelteKit need no dynamic import or `client:only` guard.
185
263
  | `stdin` | shows the standard-input box up front |
186
264
  | `persist` | key under which edits are saved (see [Saving work](#saving-work)) |
187
265
  | `entry` | which file Run executes, for multi-file widgets |
266
+ | `practice` | Java only: test cases for one method, as JSON (see [Practice mode](#practice-mode-java)) |
188
267
 
189
268
  ```html
190
269
  <!-- a worked example nobody should edit -->
@@ -335,7 +414,26 @@ document.querySelector('code-playpad').stdin = 'Ada\n36';
335
414
  Echoed input is display-only: `stdout` stays exactly what the program printed,
336
415
  so graders are unaffected.
337
416
 
338
- Java does not read input yet; `Scanner` over the input box is planned.
417
+ Java reads the box as its standard input. `new Scanner(System.in)` and
418
+ `IO.readln()` see all of it at once, as when input is piped to `java Main.java`:
419
+ the program never stops to wait, and the input is not echoed into the output.
420
+ A program that reads past the end fails with the JDK's
421
+ `NoSuchElementException`, and the widget opens the box with a hint, as it
422
+ does for Python's `input()`.
423
+
424
+ ```html
425
+ <code-playpad id="times" language="java" height="auto" stdin>
426
+ import java.util.Scanner;
427
+
428
+ void main() {
429
+ Scanner in = new Scanner(System.in);
430
+ IO.println(in.nextInt() * in.nextInt());
431
+ }
432
+ </code-playpad>
433
+ <script>
434
+ document.getElementById('times').stdin = '3 4';
435
+ </script>
436
+ ```
339
437
 
340
438
  ## Controlling it from JavaScript
341
439
 
@@ -369,14 +467,26 @@ they press Run rather than after.
369
467
  ## Events and grading
370
468
 
371
469
  Four events bubble and cross the shadow boundary, so answer-checking lives
372
- outside the widget rather than inside it:
470
+ outside the widget rather than inside it. They fire for every language:
373
471
 
374
472
  | event | `event.detail` |
375
473
  | --- | --- |
376
- | `py-ready` | `{ language, label }` — runtime booted |
377
- | `py-run` | `{ code, entry, files, language }` — a run started |
378
- | `py-output` | `{ stdout, stderr?, value?, table?, durationMs, language }` |
379
- | `py-error` | `{ message, hint?, language }` |
474
+ | `playpad-ready` | `{ language, label }` — runtime booted |
475
+ | `playpad-run` | `{ code, entry, files, language }` — a run started |
476
+ | `playpad-output` | `{ stdout, stderr?, value?, table?, durationMs, failed, language }` |
477
+ | `playpad-error` | `{ message, hint?, language }` |
478
+
479
+ Each one is also fired under its original name, `py-ready`, `py-run`,
480
+ `py-output` and `py-error` (first, with the same `detail`), from before the
481
+ widget ran anything but Python. Existing pages keep working; new code should
482
+ listen for the `playpad-*` names.
483
+
484
+ `failed` says whether the run failed, and `playpad-error` fires exactly when it
485
+ did. For Python and JavaScript, any stderr output counts as a failure. For Java,
486
+ the run's own status decides: an exception, a compile error or a non-zero
487
+ `System.exit` fail, while javac's `[unchecked]` warnings and `System.err` output
488
+ from a program that finished normally do not. That is how a terminal reads it;
489
+ the warnings are still printed, and still in `stderr`.
380
490
 
381
491
  A complete exercise check:
382
492
 
@@ -390,11 +500,11 @@ A complete exercise check:
390
500
  ```js
391
501
  const expected = '1\n2\n3\n4\n5\n';
392
502
 
393
- document.addEventListener('py-output', (event) => {
503
+ document.addEventListener('playpad-output', (event) => {
394
504
  if (event.target.id !== 'ex1') return;
395
505
 
396
506
  const el = document.getElementById('ex1-result');
397
- if (event.detail.stderr) {
507
+ if (event.detail.failed) {
398
508
  el.textContent = 'Your code raised an error — read the message above.';
399
509
  } else if (event.detail.stdout === expected) {
400
510
  el.textContent = '✅ Correct';
@@ -404,10 +514,73 @@ document.addEventListener('py-output', (event) => {
404
514
  });
405
515
  ```
406
516
 
407
- `py-error` also carries a `hint` when the runtime can explain the failure in
517
+ `playpad-error` also carries a `hint` when the runtime can explain the failure in
408
518
  plain language — for example when a program calls `input()` more times than the
409
519
  standard-input box has lines.
410
520
 
521
+ ## Practice mode (Java)
522
+
523
+ For LeetCode-style exercises, give a Java widget test cases for one method.
524
+ Run then calls that method once per case, instead of running `main` (no
525
+ `main` is needed), and reports each case. The output shows one line per
526
+ case and the tally; the status line says "2 of 3 passed".
527
+
528
+ ```html
529
+ <code-playpad language="java" height="auto"
530
+ practice='{"method": "lowerBound", "cases": [
531
+ {"args": [[1, 3, 3, 8], 3], "expected": 1},
532
+ {"args": [[1, 3, 3, 8], 9], "expected": 4},
533
+ {"args": [[], 5], "expected": 0}]}'>
534
+ class Solution {
535
+ static int lowerBound(int[] sorted, int target) {
536
+ return 0; // your code here
537
+ }
538
+ }
539
+ </code-playpad>
540
+ ```
541
+
542
+ Or set it from JavaScript (`el.practice = { method, cases }`), or pass the
543
+ `practice` prop to the React component. Each case's result arrives in
544
+ `playpad-output` as `detail.cases`, with `detail.passed` and `detail.total`,
545
+ so grading code can read them instead of comparing text:
546
+
547
+ ```js
548
+ document.addEventListener('playpad-output', (event) => {
549
+ const { cases, passed, total } = event.detail;
550
+ if (cases) console.log(`${passed} of ${total}`, cases.filter((c) => c.status !== 'passed'));
551
+ });
552
+ ```
553
+
554
+ **The spec.** `method` (required); `cases`, each `{ args, expected?, id? }`;
555
+ `className` when several classes have a method of that name; `params`
556
+ (`["int[]", "int"]`) to choose among overloads; `compare: { kind: "argument",
557
+ index: 1 }` to check an argument after the call, for a `void` method that
558
+ fills one in; `tolerance` in `compare` for doubles; `nodes` to describe a
559
+ linked-list or tree class whose fields do not make its shape obvious.
560
+
561
+ **Values** are JSON, the same way for arguments, `expected` and the
562
+ reported `actual`:
563
+
564
+ | Java | JSON |
565
+ | --- | --- |
566
+ | `int`, `boolean`, `String`, wrapper types | number, boolean, string, `null` |
567
+ | `long` | a decimal string, `"4000000000"` |
568
+ | `char` | a one-character string |
569
+ | `double` | a number, or `"NaN"`, `"Infinity"`, `"-Infinity"` |
570
+ | arrays, `List` / `ArrayList` | arrays (nested for `int[][]`, `List<List<Integer>>`) |
571
+ | linked-list nodes (a class with one field of its own type) | the values in order, `[1, 2, 3]` |
572
+ | tree nodes (two fields of its own type) | level order with `null` gaps, `[3, 9, 20, null, null, 15, 7]` |
573
+
574
+ Maps, sets and `Object` cannot be passed or returned yet.
575
+
576
+ **Each case's `status`:** `passed`, `failed` (with `expected` and
577
+ `actual`), `completed` (no `expected` was given), `runtime-error` (with
578
+ `error`, the stack trace), `resource-limit`, `engine-limitation`,
579
+ `invalid-case` (the JSON does not fit the parameters: the exercise's
580
+ mistake, not the learner's) and `not-run` (with a `reason`, such as
581
+ `compile-error`). Each case also has what it printed (`stdout`, `stderr`).
582
+ Every case runs on fresh state: static fields start over.
583
+
411
584
  ## Saving work
412
585
 
413
586
  `persist` stores the whole file map in IndexedDB, so a reader's edits survive
@@ -428,7 +601,7 @@ For a course you usually also want completion state, which the package exports:
428
601
  import { setProgress, getProgress, allProgress, loadSnippet, clearSnippet } from 'code-playpad';
429
602
 
430
603
  // mark a lesson done when its exercise passes
431
- document.addEventListener('py-output', async (event) => {
604
+ document.addEventListener('playpad-output', async (event) => {
432
605
  if (event.detail.stdout === expected) {
433
606
  await setProgress('lesson-3', { completed: true, score: 100 });
434
607
  }
@@ -622,7 +795,89 @@ warms every other page on the site.
622
795
  ## Version history
623
796
 
624
797
  No breaking changes so far: markup and API written against 0.1.3 still work on
625
- 0.3.1.
798
+ 0.4.0.
799
+
800
+ ### 0.4.0
801
+
802
+ Java grows from a first subset into most of what practice programs and
803
+ coding-interview solutions use, with its behavior recorded against JDK 25
804
+ throughout. Nothing is removed or changed for Python, JavaScript or
805
+ TypeScript, and existing markup and code keep working.
806
+
807
+ **Java: the language** (engines 0.4.0–0.24.0)
808
+ - **Objects and classes:** several classes per file; nested, inner, local
809
+ and anonymous classes; constructors and `this(...)`; inheritance with
810
+ `super`; `abstract` classes; interfaces with default, static and private
811
+ methods; `Object`'s methods; `instanceof` and casts.
812
+ - **Enums and records:** enums with fields, methods, `switch` and constants
813
+ that have their own bodies. Records with compact constructors.
814
+ - **Exceptions:** `try`/`catch`/`finally`, multi-catch,
815
+ try-with-resources, custom exceptions, and checked exceptions checked as
816
+ javac checks them. Stack traces, `Caused by:` and NullPointerException
817
+ messages match the JDK's.
818
+ - **Numbers and control flow:** `long`, `double`, `float`, `byte` and
819
+ `short`, with Java's overflow, conversions and printing. `switch`
820
+ statements and expressions, labeled `break`/`continue`, and
821
+ multi-dimensional arrays.
822
+ - **Generics and lambdas:** generic classes and methods, raw types, `var`,
823
+ varargs, annotations, and javac's `[unchecked]` warnings. Lambdas and
824
+ method references for any functional interface, with captures.
825
+ - **Modern Java:** sealed types, pattern matching (record patterns, guards,
826
+ `case null`), text blocks, static imports, initializer blocks and
827
+ `Interface.super` calls.
828
+
829
+ **Java: the library**
830
+ - **Collections:** `ArrayList`, `HashMap` and `HashSet` (iterating in the
831
+ JDK's order), `LinkedHashMap`, `TreeMap`, `TreeSet`, `ArrayDeque`,
832
+ `PriorityQueue`, `LinkedList`, `Stack`, `EnumMap` and `EnumSet`, plus
833
+ `List.of`/`Map.of`, `subList` and `Collections`.
834
+ - **Streams:** `Stream`, `IntStream`, `Collectors` and `Optional`, run
835
+ element by element in the JDK's order.
836
+ - **Strings and formatting:** the rest of `String`, with Java's regex syntax;
837
+ `StringBuilder`; `String.format` and `printf` with Java's rounding.
838
+ - **Numbers and system:** `Math`, `Random` (seeded sequences match the
839
+ JDK's), `BigInteger` and `BigDecimal`, and `System.exit`, `arraycopy` and
840
+ `nanoTime`.
841
+ - **Input and output:** `Scanner` and `IO.readln` read the widget's input
842
+ box. `BufferedReader`, `StringTokenizer`, `StreamTokenizer`,
843
+ `PrintWriter` and `BufferedWriter` give fast I/O, and output you never
844
+ flush is lost exactly as on the JDK.
845
+
846
+ **Java: speed** (engines 0.25.0–0.26.0)
847
+ - Loop-heavy programs run 2.3–4.3× faster than in 0.24.0. Library callbacks
848
+ (comparators, `merge`, `computeIfAbsent`, `PriorityQueue` with a lambda)
849
+ run 1.5–4.8× faster than in 0.25.0.
850
+ - The 200-million-step limit now covers about 0.6–1.5 s of work, so typical
851
+ 10⁵–10⁶-element solutions finish well inside it.
852
+
853
+ **New for every language**
854
+ - **`playpad-ready`, `playpad-run`, `playpad-output`, `playpad-error`:**
855
+ language-neutral names for the four events. Each fires right after its
856
+ `py-*` original, with the same `detail`, and the `py-*` names keep firing.
857
+ - **`playpad-output` carries `failed`,** and `playpad-error` fires exactly
858
+ when a run failed. For Java, the run's own status decides (an exception, a
859
+ compile error or a non-zero `System.exit`), so javac's warnings and
860
+ `System.err` output from a program that finished normally do not count as a
861
+ failure. See [Events and grading](#events-and-grading).
862
+
863
+ **Java practice mode**
864
+ - **`practice`:** an attribute, property or React prop that gives a Java
865
+ widget test cases for one method. Run calls the method once per case and
866
+ shows a line per case and the tally.
867
+ - **`playpad-output`** carries `cases`, `passed` and `total`.
868
+ `RunOptions.practice` and `Result.cases` do the same for code that drives a
869
+ runtime directly. See [Practice mode](#practice-mode-java).
870
+
871
+ **Docs and size**
872
+ - **`JAVA-COMPATIBILITY.md`** ships in the package. It is the engine's full
873
+ compatibility table, regenerated with each engine sync: every feature and
874
+ library method, the evidence behind it, and every known difference from
875
+ the JDK.
876
+ - **Download size:** Java pages download more than in 0.3.0, Python and
877
+ JavaScript pages the same; see [Download size](#download-size).
878
+ - **Size checks:** the budgets were raised to 128 KiB for the Java chunk and
879
+ 96 KiB for its library, at the owner's direction that size is not a
880
+ constraint.
626
881
 
627
882
  ### 0.3.1
628
883
 
package/dist/cdn.js CHANGED
@@ -1,2 +1,2 @@
1
- /*! code-playpad 0.3.1: CDN entry, loads dist/standalone.js of this exact version */
2
- export * from "https://cdn.jsdelivr.net/npm/code-playpad@0.3.1/dist/standalone.js";
1
+ /*! code-playpad 0.4.0: CDN entry, loads dist/standalone.js of this exact version */
2
+ export * from "https://cdn.jsdelivr.net/npm/code-playpad@0.4.0/dist/standalone.js";
@@ -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-BTGQ_X_F.js`).then(e=>e.pythonRuntime)),a(`javascript`,()=>import(`./javascript-hfFD1Fg2.js`).then(e=>e.javascriptRuntime)),a(`typescript`,()=>import(`./javascript-hfFD1Fg2.js`).then(e=>e.typescriptRuntime)),a(`java`,()=>import(`./java-1ISFU8hm.js`).then(e=>e.javaRuntime));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=new WeakSet,d=String.raw`
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-CqcU3YGb.js`).then(e=>e.pythonRuntime)),a(`javascript`,()=>import(`./javascript-C1_thu8i.js`).then(e=>e.javascriptRuntime)),a(`typescript`,()=>import(`./javascript-C1_thu8i.js`).then(e=>e.typescriptRuntime)),a(`java`,()=>import(`./java-DrWI9a5Q.js`).then(e=>e.javaRuntime));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=new WeakSet,d=String.raw`
2
2
  :host {
3
3
  --cp-bg: #ffffff;
4
4
  --cp-fg: #1f2328;
@@ -296,10 +296,11 @@ table.cp-table th { background: var(--cp-bg); }
296
296
  }
297
297
  `,f=e=>()=>import(`@codemirror/lang-javascript`).then(t=>t.javascript(e)),p={py:()=>import(`@codemirror/lang-python`).then(e=>e.python()),pyi:()=>import(`@codemirror/lang-python`).then(e=>e.python()),js:f({}),mjs:f({}),cjs:f({}),jsx:f({jsx:!0}),ts:f({typescript:!0}),mts:f({typescript:!0}),cts:f({typescript:!0}),tsx:f({typescript:!0,jsx:!0}),json:()=>import(`@codemirror/lang-json`).then(e=>e.json()),java:()=>import(`@codemirror/lang-java`).then(e=>e.java())};async function m(e){let t=p[e.includes(`.`)?e.slice(e.lastIndexOf(`.`)+1).toLowerCase():``];return t?t().catch(()=>null):null}var h=`code-playpad`,g=1,_=`snippets`,v=`progress`,y=null;function b(){return typeof indexedDB>`u`?Promise.reject(Error(`IndexedDB unavailable`)):(y||=new Promise((e,t)=>{let n=indexedDB.open(h,g);n.onupgradeneeded=()=>{let e=n.result;e.objectStoreNames.contains(_)||e.createObjectStore(_),e.objectStoreNames.contains(v)||e.createObjectStore(v,{keyPath:`id`})},n.onsuccess=()=>e(n.result),n.onerror=()=>t(n.error)}).catch(e=>{throw y=null,e}),y)}function x(e,t,n){return b().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 S(e,t){return x(_,`readwrite`,n=>n.put(t,e)).then(()=>void 0)}function C(e){return x(_,`readonly`,t=>t.get(e)).then(e=>e??null)}function w(e){return x(_,`readwrite`,t=>t.delete(e)).then(()=>void 0)}function T(e,t={}){let n={completed:!0,...t,id:e,updatedAt:Date.now()};return x(v,`readwrite`,e=>e.put(n)).then(()=>n)}function E(e){return x(v,`readonly`,t=>t.get(e)).then(e=>e??null)}function D(){return x(v,`readonly`,e=>e.getAll())}var O=`code-playpad`;function k(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 A=2e5,j=`playpad-file`,M={python:`main.py`,javascript:`main.js`,typescript:`main.ts`,java:`Main.java`};function N(e){return M[e]??`main.txt`}function P(e,t){let n={};for(let t of e.children){if(t.tagName.toLowerCase()!==j)continue;let e=t.getAttribute(`name`);e&&(n[e]=k(t.textContent??``))}if(Object.keys(n).length)return n;let r=[...e.childNodes].filter(e=>e.nodeType!==Node.ELEMENT_NODE||e.tagName.toLowerCase()!==j),i=r.some(e=>e.nodeType===Node.ELEMENT_NODE),a=k(r.filter(e=>!(i&&e.nodeType===Node.TEXT_NODE&&!(e.textContent??``).trim())).map(e=>e.textContent??``).join(``));return{[t]:a}}function F(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 I=typeof HTMLElement>`u`?class{}:HTMLElement,L=class extends I{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=null;#x=!1;#S=!1;#C=null;#w=null;#T=null;#E=()=>this.#K();#D=null;#O=!0;#k=[];#A=null;#j=null;#M=null;#N=0;#P=!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.#B()}get files(){return this.#H()}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.#V()),this.#u?.forgetFiles([]),this.#B(),this.#U(),this.#z()}get entry(){return this.#V()}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.#U(),this.#z())}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.#H()}reset(){this.#m={...this.#h},this.#g=this.#V(),this.#u?.forgetFiles([]),this.#B(),this.#U(),this.#Y(),this.#s.hidden=!0,this.#J(this.#f?.ready?`${this.#f.label} ready`:``,`idle`)}async run(){if(this.#y)return;await this.#d;let e=this.#H(),t=this.#V(),n=e[t]??``;this.#y=!0,this.#x=!1,this.#n.disabled=!0,this.#r.hidden=!1,this.#Y(),this.#s.hidden=!1,this.#ne(`py-run`,{code:n,entry:t,files:e,language:this.language});try{let r=await this.#R();if(this.#x)throw new c;this.#J(`Queued…`,`busy`),this.#o.hidden=!1;let i=r.run(n,{stdin:this.#l.value,files:e,entry:t,onStart:()=>this.#J(`Running…`,`busy`),onStdout:e=>this.#X(e),onStderr:e=>this.#X(e,`cp-err`),onEcho:e=>this.#X(e,`cp-in`),onStdinRequest:()=>this.#Q()});this.#b={runtime:r,run:i};let a=await i;return a.table&&this.#te(a.table),a.value!==void 0&&this.#X(`${a.value}\n`,`cp-value`),a.hint&&this.#ee(a.hint,u.has(a)),a.stderr?(this.#J(`Error · ${a.durationMs} ms`,`error`),this.#ne(`py-error`,{message:a.stderr,hint:a.hint,language:this.language})):this.#J(`Done in ${a.durationMs} ms`,`ok`),this.#ne(`py-output`,{...a,language:this.language}),a}catch(e){if(e instanceof c)this.#X(`
299
+ `)}var A=2e5,j=`playpad-file`,M={python:`main.py`,javascript:`main.js`,typescript:`main.ts`,java:`Main.java`};function N(e){return M[e]??`main.txt`}function P(e,t){let n={};for(let t of e.children){if(t.tagName.toLowerCase()!==j)continue;let e=t.getAttribute(`name`);e&&(n[e]=k(t.textContent??``))}if(Object.keys(n).length)return n;let r=[...e.childNodes].filter(e=>e.nodeType!==Node.ELEMENT_NODE||e.tagName.toLowerCase()!==j),i=r.some(e=>e.nodeType===Node.ELEMENT_NODE),a=k(r.filter(e=>!(i&&e.nodeType===Node.TEXT_NODE&&!(e.textContent??``).trim())).map(e=>e.textContent??``).join(``));return{[t]:a}}function F(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 I=typeof HTMLElement>`u`?class{}:HTMLElement,L=class extends I{static get observedAttributes(){return[`language`,`height`,`min-height`,`max-height`,`theme`,`readonly`,`autorun`,`stdin`,`persist`,`entry`,`practice`]}#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=null;#x=!1;#S=!1;#C=null;#w=null;#T=null;#E=()=>this.#q();#D=null;#O=!0;#k=[];#A=null;#j=null;#M=null;#N=0;#P=!1;#F=null;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.#V()}get practice(){if(this.#F)return this.#F;let e=this.getAttribute(`practice`);if(!e)return null;try{return JSON.parse(e)}catch{return null}}set practice(e){this.#F=e}get files(){return this.#U()}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.#H()),this.#u?.forgetFiles([]),this.#V(),this.#W(),this.#B()}get entry(){return this.#H()}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.#W(),this.#B())}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.#U()}reset(){this.#m={...this.#h},this.#g=this.#H(),this.#u?.forgetFiles([]),this.#V(),this.#W(),this.#X(),this.#s.hidden=!0,this.#Y(this.#f?.ready?`${this.#f.label} ready`:``,`idle`)}async run(){if(this.#y)return;await this.#d;let e=this.#U(),t=this.#H(),n=e[t]??``;this.#y=!0,this.#x=!1,this.#n.disabled=!0,this.#r.hidden=!1,this.#X(),this.#s.hidden=!1;let r=this.practice;this.hasAttribute(`practice`)&&!r&&this.#Z(`The practice attribute is not valid JSON.
300
+ `,`cp-err`),this.#re(`py-run`,{code:n,entry:t,files:e,language:this.language,...r?{practice:r}:{}});try{let i=await this.#z();if(this.#x)throw new c;if(r&&!i.supportsPractice)throw Error(`Practice mode is not available for ${i.label}.`);this.#Y(`Queued…`,`busy`),this.#o.hidden=!1;let a=i.run(n,{stdin:this.#l.value,files:e,entry:t,onStart:()=>this.#Y(`Running…`,`busy`),onStdout:e=>this.#Z(e),onStderr:e=>this.#Z(e,`cp-err`),onEcho:e=>this.#Z(e,`cp-in`),onStdinRequest:()=>this.#$(),...r?{practice:r}:{}});this.#b={runtime:i,run:a};let o=await a;if(o.table&&this.#ne(o.table),o.value!==void 0&&this.#Z(`${o.value}\n`,`cp-value`),o.hint&&this.#te(o.hint,u.has(o)),o.failed=i.statusDecidesFailure?o.failed===!0:!!o.stderr,o.failed)this.#Y(`Error · ${o.durationMs} ms`,`error`),this.#re(`py-error`,{message:o.stderr??``,hint:o.hint,language:this.language});else if(o.cases){let e=o.cases.filter(e=>e.expected!==void 0).length,t=o.passed===e;this.#Y(e?`${o.passed} of ${e} passed · ${o.durationMs} ms`:`Done in ${o.durationMs} ms`,t?`ok`:`error`)}else this.#Y(`Done in ${o.durationMs} ms`,`ok`);return this.#re(`py-output`,{...o,language:this.language}),o}catch(e){if(e instanceof c)this.#Z(`
300
301
  [stopped]
301
- `,`cp-err`),this.#J(`Stopped`,`error`);else{let t=e instanceof Error?e.message:String(e);this.#X(t+`
302
- `,`cp-err`),this.#J(`Failed to start`,`error`),this.#ne(`py-error`,{message:t,language:this.language})}return}finally{this.#b=null,this.#$(null),this.#Z(),this.#y=!1,this.#n.disabled=!1,this.#r.hidden=!0,this.#o.hidden=!0}}stop(){this.#b?this.#b.runtime.stop?.(this.#b.run):this.#y&&(this.#x=!0)}prewarm(){return this.#R()}connectedCallback(){if(this.#P){this.#L();return}if(!this.textContent?.trim()&&document.readyState===`loading`){document.addEventListener(`DOMContentLoaded`,()=>this.isConnected&&this.#F(),{once:!0});return}this.#F()}disconnectedCallback(){this.#C?.disconnect(),this.#C=null,this.#w?.removeEventListener(`change`,this.#E),this.#w=null,this.#T?.disconnect(),this.#T=null,this.#D&&clearTimeout(this.#D),this.#$(null),setTimeout(()=>{this.isConnected||!this.#u||(this.#_=this.#H(),this.#u.destroy(),this.#u=null,this.#d=null,this.#P=!1)},0)}attributeChangedCallback(e,t,n){if(this.#P)switch(e){case`theme`:this.#K();break;case`height`:case`min-height`:case`max-height`:this.#q();break;case`readonly`:this.#u?.setReadOnly(n!==null);break;case`stdin`:this.#c.hidden=n===null&&!this.#l.value;break;case`entry`:this.#U();break;case`language`:this.#f=null,this.#p=null,this.#S=!1,this.#R().catch(()=>{})}}#F(){this.#P=!0,this.#h=P(this,N(this.language)),this.#m=this.#_??{...this.#h},this.#g=this.#V(),this.#I(),this.#K(),this.#q();let t=this.getAttribute(`persist`);this.#d=(async()=>{if(t&&this.#_==null){let e=await C(t).catch(()=>null),n=e==null?null:F(e,N(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.#V()}}this.#U();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.#G(),readOnly:this.hasAttribute(`readonly`),language:r,onRun:()=>void this.run(),onChange:e=>this.#W(e)}),this.#_=null,this.#z()})(),this.#L()}#I(){let e=document.createElement(`style`);e.textContent=d;let t=document.createElement(`div`);t.className=`cp-root`,t.innerHTML=`
302
+ `,`cp-err`),this.#Y(`Stopped`,`error`);else{let t=e instanceof Error?e.message:String(e);this.#Z(t+`
303
+ `,`cp-err`),this.#Y(`Failed to start`,`error`),this.#re(`py-error`,{message:t,language:this.language})}return}finally{this.#b=null,this.#ee(null),this.#Q(),this.#y=!1,this.#n.disabled=!1,this.#r.hidden=!0,this.#o.hidden=!0}}stop(){this.#b?this.#b.runtime.stop?.(this.#b.run):this.#y&&(this.#x=!0)}prewarm(){return this.#z()}connectedCallback(){if(this.#P){this.#R();return}if(!this.textContent?.trim()&&document.readyState===`loading`){document.addEventListener(`DOMContentLoaded`,()=>this.isConnected&&this.#I(),{once:!0});return}this.#I()}disconnectedCallback(){this.#C?.disconnect(),this.#C=null,this.#w?.removeEventListener(`change`,this.#E),this.#w=null,this.#T?.disconnect(),this.#T=null,this.#D&&clearTimeout(this.#D),this.#ee(null),setTimeout(()=>{this.isConnected||!this.#u||(this.#_=this.#U(),this.#u.destroy(),this.#u=null,this.#d=null,this.#P=!1)},0)}attributeChangedCallback(e,t,n){if(this.#P)switch(e){case`theme`:this.#q();break;case`height`:case`min-height`:case`max-height`:this.#J();break;case`readonly`:this.#u?.setReadOnly(n!==null);break;case`stdin`:this.#c.hidden=n===null&&!this.#l.value;break;case`entry`:this.#W();break;case`language`:this.#f=null,this.#p=null,this.#S=!1,this.#z().catch(()=>{})}}#I(){this.#P=!0,this.#h=P(this,N(this.language)),this.#m=this.#_??{...this.#h},this.#g=this.#H(),this.#L(),this.#q(),this.#J();let t=this.getAttribute(`persist`);this.#d=(async()=>{if(t&&this.#_==null){let e=await C(t).catch(()=>null),n=e==null?null:F(e,N(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.#H()}}this.#W();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.#K(),readOnly:this.hasAttribute(`readonly`),language:r,onRun:()=>void this.run(),onChange:e=>this.#G(e)}),this.#_=null,this.#B()})(),this.#R()}#L(){let e=document.createElement(`style`);e.textContent=d;let t=document.createElement(`div`);t.className=`cp-root`,t.innerHTML=`
303
304
  <div class="cp-toolbar">
304
305
  <button class="cp-primary" part="run-button" data-action="run">Run</button>
305
306
  <button class="cp-danger" part="stop-button" data-action="stop" hidden>Stop</button>
@@ -314,10 +315,10 @@ table.cp-table th { background: var(--cp-bg); }
314
315
  <label>Standard input — one value per line</label>
315
316
  <textarea rows="2" spellcheck="false"></textarea>
316
317
  </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.#R().catch(()=>{});this.#t.addEventListener(`pointerdown`,n,{once:!0}),this.#t.addEventListener(`focusin`,n,{once:!0})}#L(){this.#C||typeof IntersectionObserver>`u`||(this.#C=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&(this.#C?.disconnect(),this.#C=null,this.#R().then(()=>{this.hasAttribute(`autorun`)&&this.run()}).catch(()=>{}))},{rootMargin:`300px`}),this.#C.observe(this))}#R(){return this.#p||=(async()=>{let e=await s(this.language);this.#f=e,e.ready||(this.#o.hidden=!1,this.#J(`Loading ${e.label}…`,`busy`));try{await e.load(e=>{this.#y||this.#J(e.message,e.phase===`ready`?`ok`:`busy`)})}catch(e){this.#p=null,this.#J(`Runtime unavailable`,`error`);let t=e instanceof Error?e.message:String(e);throw this.#X(t+`
318
- `,`cp-err`),this.#s.hidden=!1,e}finally{this.#o.hidden=!0}return this.#z(),this.#S||(this.#S=!0,this.#J(`${e.label} ready`,`ok`),this.#ne(`py-ready`,{language:e.id,label:e.label})),e})(),this.#p}async#z(){if(!this.#u)return;let e=this.#g,t=await m(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??[])}#B(){if(this.#u){this.#u.setCode(this.#m[this.#g]??``);return}this.#d?.then(()=>{this.#u?.setCode(this.#m[this.#g]??``),this.#U()})}#V(){let e=Object.keys(this.#m),t=this.getAttribute(`entry`);if(t&&e.includes(t))return t;let n=N(this.language);return e.includes(n)?n:e[0]??n}#H(){let e={...this.#m};if(this.#u)for(let t of Object.keys(e))e[t]=this.#u.fileCode(t,e[t]);return e}#U(){let e=Object.keys(this.#m);if(this.#v.hidden=e.length<2,this.#v.hidden){this.#v.replaceChildren();return}let t=this.#V();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}))}#W(e){this.#m[this.#g]=e;let t=this.getAttribute(`persist`);if(!t)return;this.#D&&clearTimeout(this.#D);let n=JSON.stringify(this.#H());this.#D=setTimeout(()=>void S(t,n).catch(()=>{}),400)}#G(){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.#w?.matches:n}#K(){!this.#w&&typeof matchMedia==`function`&&(this.#w=matchMedia(`(prefers-color-scheme: dark)`),this.#w.addEventListener(`change`,this.#E)),!this.#T&&typeof MutationObserver==`function`&&typeof document<`u`&&(this.#T=new MutationObserver(this.#E),this.#T.observe(document.documentElement,{attributes:!0,attributeFilter:[`class`,`style`,`data-theme`,`data-color-scheme`,`color-scheme`]}));let e=this.#G();this.setAttribute(`data-resolved-theme`,e?`dark`:`light`),this.#u?.setTheme(e)}#q(){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)}}#J(e,t){this.#a.textContent=e,this.#a.dataset.state=t}#Y(){this.#M&&clearTimeout(this.#M),this.#M=null,this.#k=[],this.#N=0,this.#s.replaceChildren(),this.#O=!0}#X(e,t){if(!e)return;let n=t===`cp-err`&&!this.#O?`\n${e}`:e;this.#O=n.endsWith(`
319
- `),this.#k.push({text:n,className:t}),this.#M??=setTimeout(()=>this.#Z(),16)}#Z(){if(this.#M&&clearTimeout(this.#M),this.#M=null,!this.#k.length)return;let e=this.#k;this.#k=[];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.#N>=A)break;let e=t.text;this.#N+e.length>A&&(e=e.slice(0,A-this.#N)+`
320
- … output truncated for display — the full text is in the py-output event
321
- `),this.#N+=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}#Q(){return this.#Z(),this.#$(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.#A===e&&(this.#A=null,this.#j=null,t.remove(),n===null?this.#X(`
322
- `,`cp-in`):this.#X(n+`
323
- `,`cp-in`),this.#Z(),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.#A=e,this.#j=a,n.focus({preventScroll:!0})})}#$(e){this.#j?.(e),this.#j=null,this.#A=null}#ee(e,t){this.#Z();let n=document.createElement(`div`);n.className=`cp-hint`,n.textContent=e,this.#s.hidden=!1,this.#s.appendChild(n),this.#s.scrollTop=this.#s.scrollHeight,t&&(this.#c.hidden=!1,this.#c.classList.add(`cp-attention`))}#te(e){this.#Z();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)}#ne(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0}))}};function R(e=O){typeof window>`u`||typeof customElements>`u`||customElements.get(e)||customElements.define(e,L)}R();export{E as a,T as c,l as d,s as f,e as h,w as i,u as l,a as m,R as n,C as o,o as p,D as r,S as s,L as t,c as u};
318
+ <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.#z().catch(()=>{});this.#t.addEventListener(`pointerdown`,n,{once:!0}),this.#t.addEventListener(`focusin`,n,{once:!0})}#R(){this.#C||typeof IntersectionObserver>`u`||(this.#C=new IntersectionObserver(e=>{e.some(e=>e.isIntersecting)&&(this.#C?.disconnect(),this.#C=null,this.#z().then(()=>{this.hasAttribute(`autorun`)&&this.run()}).catch(()=>{}))},{rootMargin:`300px`}),this.#C.observe(this))}#z(){return this.#p||=(async()=>{let e=await s(this.language);this.#f=e,e.ready||(this.#o.hidden=!1,this.#Y(`Loading ${e.label}…`,`busy`));try{await e.load(e=>{this.#y||this.#Y(e.message,e.phase===`ready`?`ok`:`busy`)})}catch(e){this.#p=null,this.#Y(`Runtime unavailable`,`error`);let t=e instanceof Error?e.message:String(e);throw this.#Z(t+`
319
+ `,`cp-err`),this.#s.hidden=!1,e}finally{this.#o.hidden=!0}return this.#B(),this.#S||(this.#S=!0,this.#Y(`${e.label} ready`,`ok`),this.#re(`py-ready`,{language:e.id,label:e.label})),e})(),this.#p}async#B(){if(!this.#u)return;let e=this.#g,t=await m(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??[])}#V(){if(this.#u){this.#u.setCode(this.#m[this.#g]??``);return}this.#d?.then(()=>{this.#u?.setCode(this.#m[this.#g]??``),this.#W()})}#H(){let e=Object.keys(this.#m),t=this.getAttribute(`entry`);if(t&&e.includes(t))return t;let n=N(this.language);return e.includes(n)?n:e[0]??n}#U(){let e={...this.#m};if(this.#u)for(let t of Object.keys(e))e[t]=this.#u.fileCode(t,e[t]);return e}#W(){let e=Object.keys(this.#m);if(this.#v.hidden=e.length<2,this.#v.hidden){this.#v.replaceChildren();return}let t=this.#H();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}))}#G(e){this.#m[this.#g]=e;let t=this.getAttribute(`persist`);if(!t)return;this.#D&&clearTimeout(this.#D);let n=JSON.stringify(this.#U());this.#D=setTimeout(()=>void S(t,n).catch(()=>{}),400)}#K(){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.#w?.matches:n}#q(){!this.#w&&typeof matchMedia==`function`&&(this.#w=matchMedia(`(prefers-color-scheme: dark)`),this.#w.addEventListener(`change`,this.#E)),!this.#T&&typeof MutationObserver==`function`&&typeof document<`u`&&(this.#T=new MutationObserver(this.#E),this.#T.observe(document.documentElement,{attributes:!0,attributeFilter:[`class`,`style`,`data-theme`,`data-color-scheme`,`color-scheme`]}));let e=this.#K();this.setAttribute(`data-resolved-theme`,e?`dark`:`light`),this.#u?.setTheme(e)}#J(){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)}}#Y(e,t){this.#a.textContent=e,this.#a.dataset.state=t}#X(){this.#M&&clearTimeout(this.#M),this.#M=null,this.#k=[],this.#N=0,this.#s.replaceChildren(),this.#O=!0}#Z(e,t){if(!e)return;let n=t===`cp-err`&&!this.#O?`\n${e}`:e;this.#O=n.endsWith(`
320
+ `),this.#k.push({text:n,className:t}),this.#M??=setTimeout(()=>this.#Q(),16)}#Q(){if(this.#M&&clearTimeout(this.#M),this.#M=null,!this.#k.length)return;let e=this.#k;this.#k=[];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.#N>=A)break;let e=t.text;this.#N+e.length>A&&(e=e.slice(0,A-this.#N)+`
321
+ … output truncated for display — the full text is in the playpad-output event
322
+ `),this.#N+=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}#$(){return this.#Q(),this.#ee(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.#A===e&&(this.#A=null,this.#j=null,t.remove(),n===null?this.#Z(`
323
+ `,`cp-in`):this.#Z(n+`
324
+ `,`cp-in`),this.#Q(),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.#A=e,this.#j=a,n.focus({preventScroll:!0})})}#ee(e){this.#j?.(e),this.#j=null,this.#A=null}#te(e,t){this.#Q();let n=document.createElement(`div`);n.className=`cp-hint`,n.textContent=e,this.#s.hidden=!1,this.#s.appendChild(n),this.#s.scrollTop=this.#s.scrollHeight,t&&(this.#c.hidden=!1,this.#c.classList.add(`cp-attention`))}#ne(e){this.#Q();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)}#re(e,t){this.dispatchEvent(new CustomEvent(e,{detail:t,bubbles:!0,composed:!0})),this.dispatchEvent(new CustomEvent(`playpad-${e.slice(3)}`,{detail:t,bubbles:!0,composed:!0}))}};function R(e=O){typeof window>`u`||typeof customElements>`u`||customElements.get(e)||customElements.define(e,L)}R();export{E as a,T as c,l as d,s as f,e as h,w as i,u as l,a as m,R as n,C as o,o as p,D as r,S as s,L as t,c as u};
package/dist/element.d.ts CHANGED
@@ -1,10 +1,12 @@
1
- import type { Files, Result, Runtime } from './types.js';
1
+ import type { Files, PracticeSpec, Result, Runtime } from './types.js';
2
2
  export interface RunEventDetail {
3
3
  code: string;
4
4
  language: string;
5
5
  /** Path of the file being run, e.g. "main.py". */
6
6
  entry: string;
7
7
  files: Files;
8
+ /** Practice mode, when the widget has a `practice` spec. */
9
+ practice?: PracticeSpec;
8
10
  }
9
11
  export interface OutputEventDetail extends Result {
10
12
  language: string;
@@ -29,6 +31,13 @@ export declare class CodePlayPadElement extends ElementBase {
29
31
  /** Contents of the file currently on screen. */
30
32
  get code(): string;
31
33
  set code(value: string);
34
+ /**
35
+ * Practice mode (Java): Run calls `practice.method` with each of
36
+ * `practice.cases` instead of running main, and `Result.cases` reports
37
+ * them. Set it as a property, or as the `practice` attribute (JSON).
38
+ */
39
+ get practice(): PracticeSpec | null;
40
+ set practice(value: PracticeSpec | null);
32
41
  /** Every file in this widget, entry point included. */
33
42
  get files(): Files;
34
43
  set files(value: Files);
package/dist/element.js CHANGED
@@ -1 +1 @@
1
- import{n as e,t}from"./element-BYcX5Z6z.js";export{t as CodePlayPadElement,e as defineCodePlayPad};
1
+ import{n as e,t}from"./element-Bh4TdnGA.js";export{t as CodePlayPadElement,e as defineCodePlayPad};
package/dist/index.d.ts CHANGED
@@ -5,6 +5,6 @@ export type { EditorHandle, EditorOptions } from './editor.js';
5
5
  export { getRuntime, listRuntimes, registerRuntime } from './runtime/registry.js';
6
6
  export { configurePython, configureTypeScript, pyodideAssetUrls, PYODIDE_VERSION } from './runtime/config.js';
7
7
  export { RunStopped, WorkerUnavailable } from './types.js';
8
- export type { Files, LoadProgress, Result, Runtime, RunOptions, Table } from './types.js';
8
+ export type { CaseResult, CaseStatus, Files, LoadProgress, PracticeCase, PracticeSpec, PracticeValue, Result, Runtime, RunOptions, Table } from './types.js';
9
9
  export { allProgress, clearSnippet, getProgress, loadSnippet, saveSnippet, setProgress } from './progress.js';
10
10
  export type { ProgressRecord } from './progress.js';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{a as e,c as t,d as n,f as r,h as i,i 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-BYcX5Z6z.js";import{n as m,o as h,r as g,t as _}from"./config-DZqcFPEP.js";export{f as CodePlayPadElement,_ as PYODIDE_VERSION,p as RunStopped,n as WorkerUnavailable,u as allProgress,a as clearSnippet,m as configurePython,g as configureTypeScript,i as createEditor,s as defineCodePlayPad,e as getProgress,r as getRuntime,l as listRuntimes,c as loadSnippet,h as pyodideAssetUrls,o as registerRuntime,d as saveSnippet,t as setProgress};
1
+ import{a as e,c as t,d as n,f as r,h as i,i 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-Bh4TdnGA.js";import{n as m,o as h,r as g,t as _}from"./config-DZqcFPEP.js";export{f as CodePlayPadElement,_ as PYODIDE_VERSION,p as RunStopped,n as WorkerUnavailable,u as allProgress,a as clearSnippet,m as configurePython,g as configureTypeScript,i as createEditor,s as defineCodePlayPad,e as getProgress,r as getRuntime,l as listRuntimes,c as loadSnippet,h as pyodideAssetUrls,o as registerRuntime,d as saveSnippet,t as setProgress};