code-playpad 0.3.1 → 0.5.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.5.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 ~105 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,133 @@ 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. Since engine 0.27, other `.java` files in the
148
+ widget are compiled as the JDK's launcher compiles them, when the program first
149
+ uses their class (see [Several files](#several-files)). Packages are not
150
+ supported.
151
+
152
+ **Speed.** In loop-heavy code the engine runs roughly 150–300 million
153
+ bytecode instructions a second on a laptop. A typical practice solution (10⁵–10⁶
154
+ elements, nested loops, maps, sorting with a comparator) finishes in well
155
+ under a second. The engine stops a runaway program after 200 million steps
156
+ (about 0.6–1.5 s of work) and explains why.
157
+
158
+ **Exactly what is supported**, feature by feature and method by method, with
159
+ the evidence behind it and every known difference from the JDK, is in
160
+ [JAVA-COMPATIBILITY.md](https://cdn.jsdelivr.net/npm/code-playpad/JAVA-COMPATIBILITY.md), which ships in the package.
93
161
 
94
162
  ## Download size
95
163
 
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.
164
+ What a page downloads once a reader presses Run, with the CDN build, as
165
+ measured for 0.4.0. Each one-widget page was loaded in a browser, and each
166
+ code-playpad file it fetched was gzipped. The CDN column is what the CDN
167
+ actually sent.
99
168
 
100
- | Page uses | code-playpad (gzip) | Also fetched from a CDN at run time | 0.2.1 (gzip) |
169
+ | Page uses | code-playpad (gzip) | Also fetched from a CDN at run time | 0.3.0 (gzip) |
101
170
  | --- | --- | --- | --- |
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.
171
+ | 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 |
172
+ | JavaScript | **180.4 KB** | nothing | 179.9 KB |
173
+ | TypeScript | **180.4 KB** | Sucrase, 72 KB (≈ 252 KB in total) | 179.9 KB |
174
+ | Java | **267.8 KB** | nothing | 216.4 KB |
175
+ | Java, with collections | **342.4 KB** | nothing | 233.2 KB |
176
+ | All four | 399.9 KB | Pyodide and Sucrase | 273.9 KB |
177
+
178
+ - **Every row includes a 144.5 KB shared base:** the editor (CodeMirror) and
179
+ the widget, the same as in 0.3.0.
180
+ - **Each language adds:**
181
+ - Python: 21.7 KB.
182
+ - JavaScript/TypeScript: 35.9 KB.
183
+ - Java: 123.2 KB (the 104.2 KB engine plus 19 KB of Java syntax
184
+ highlighting).
185
+ - The first Java program that uses collections, `StringBuilder` or another
186
+ library class also adds the 74.6 KB Java library, once per page.
187
+ - **Java grew** from 0.3.0 because the subset did, from basic Java to most of
188
+ what practice programs use (see the version history). The owner treats
189
+ download size as secondary to coverage. The package's size checks still
190
+ catch accidental growth: 128 KiB for the Java chunk and 96 KiB for its
191
+ library.
192
+ - **Languages load lazily.** A Java page never fetches the Python or
193
+ JavaScript runtimes, and Python and JavaScript pages never fetch Java. As a
194
+ result, Python-only and JavaScript-only pages are the same size as in 0.3.0.
195
+ - **Java needs no CDN:** it is self-contained, while Python's first run
196
+ downloads about 6 MB of Pyodide (cached after that).
197
+ - **With npm and a bundler,** you already ship CodeMirror yourself.
198
+ code-playpad's own code then costs about 9 KB, plus the runtime chunk for
199
+ each language used. Java's chunk is 104 KB, and 74 KB more for its library
200
+ when a program uses it, plus `@codemirror/lang-java` if you install it.
120
201
 
121
202
  ## Install
122
203
 
123
204
  ### From a CDN — nothing to install
124
205
 
125
206
  ```html
126
- <script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.3.1/dist/standalone.js"></script>
207
+ <script type="module" src="https://cdn.jsdelivr.net/npm/code-playpad@0.5.0/dist/standalone.js"></script>
127
208
  ```
128
209
 
129
210
  This is the `standalone` build: CodeMirror is bundled in, so a static blog post
@@ -185,6 +266,7 @@ Next.js, Astro and SvelteKit need no dynamic import or `client:only` guard.
185
266
  | `stdin` | shows the standard-input box up front |
186
267
  | `persist` | key under which edits are saved (see [Saving work](#saving-work)) |
187
268
  | `entry` | which file Run executes, for multi-file widgets |
269
+ | `practice` | Java only: test cases for one method, as JSON (see [Practice mode](#practice-mode-java)) |
188
270
 
189
271
  ```html
190
272
  <!-- a worked example nobody should edit -->
@@ -286,6 +368,39 @@ JavaScript and TypeScript work the same way, including `.json` imported as data:
286
368
  </code-playpad>
287
369
  ```
288
370
 
371
+ Java compiles the other files as `java Main.java` does (JDK 25's source
372
+ launcher): `Main.java` runs, and `Session.java` is compiled when `Main` first
373
+ uses `Session`. Each file is named after its class and has its own imports, and
374
+ errors and stack traces name the file. Write `<` as `&lt;` inside the markup
375
+ (`List&lt;String&gt;`), since it is HTML:
376
+
377
+ ```html
378
+ <code-playpad language="java" entry="Main.java" height="auto">
379
+ <playpad-file name="Main.java">
380
+ public class Main {
381
+ public static void main(String[] args) {
382
+ Session s = new Session("loops", 30);
383
+ System.out.println(s.describe());
384
+ }
385
+ }
386
+ </playpad-file>
387
+
388
+ <playpad-file name="Session.java">
389
+ public class Session {
390
+ private final String topic;
391
+ private final int minutes;
392
+
393
+ Session(String topic, int minutes) {
394
+ this.topic = topic;
395
+ this.minutes = minutes;
396
+ }
397
+
398
+ String describe() { return topic + ": " + minutes + " min"; }
399
+ }
400
+ </playpad-file>
401
+ </code-playpad>
402
+ ```
403
+
289
404
  A tab bar appears once there is more than one file, with `▸` marking the entry
290
405
  point, and each file keeps its own undo history. Editing a module takes effect
291
406
  on the next run — the import cache is cleared between runs.
@@ -335,7 +450,26 @@ document.querySelector('code-playpad').stdin = 'Ada\n36';
335
450
  Echoed input is display-only: `stdout` stays exactly what the program printed,
336
451
  so graders are unaffected.
337
452
 
338
- Java does not read input yet; `Scanner` over the input box is planned.
453
+ Java reads the box as its standard input. `new Scanner(System.in)` and
454
+ `IO.readln()` see all of it at once, as when input is piped to `java Main.java`:
455
+ the program never stops to wait, and the input is not echoed into the output.
456
+ A program that reads past the end fails with the JDK's
457
+ `NoSuchElementException`, and the widget opens the box with a hint, as it
458
+ does for Python's `input()`.
459
+
460
+ ```html
461
+ <code-playpad id="times" language="java" height="auto" stdin>
462
+ import java.util.Scanner;
463
+
464
+ void main() {
465
+ Scanner in = new Scanner(System.in);
466
+ IO.println(in.nextInt() * in.nextInt());
467
+ }
468
+ </code-playpad>
469
+ <script>
470
+ document.getElementById('times').stdin = '3 4';
471
+ </script>
472
+ ```
339
473
 
340
474
  ## Controlling it from JavaScript
341
475
 
@@ -369,14 +503,26 @@ they press Run rather than after.
369
503
  ## Events and grading
370
504
 
371
505
  Four events bubble and cross the shadow boundary, so answer-checking lives
372
- outside the widget rather than inside it:
506
+ outside the widget rather than inside it. They fire for every language:
373
507
 
374
508
  | event | `event.detail` |
375
509
  | --- | --- |
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 }` |
510
+ | `playpad-ready` | `{ language, label }` — runtime booted |
511
+ | `playpad-run` | `{ code, entry, files, language }` — a run started |
512
+ | `playpad-output` | `{ stdout, stderr?, value?, table?, durationMs, failed, language }` |
513
+ | `playpad-error` | `{ message, hint?, language }` |
514
+
515
+ Each one is also fired under its original name, `py-ready`, `py-run`,
516
+ `py-output` and `py-error` (first, with the same `detail`), from before the
517
+ widget ran anything but Python. Existing pages keep working; new code should
518
+ listen for the `playpad-*` names.
519
+
520
+ `failed` says whether the run failed, and `playpad-error` fires exactly when it
521
+ did. For Python and JavaScript, any stderr output counts as a failure. For Java,
522
+ the run's own status decides: an exception, a compile error or a non-zero
523
+ `System.exit` fail, while javac's `[unchecked]` warnings and `System.err` output
524
+ from a program that finished normally do not. That is how a terminal reads it;
525
+ the warnings are still printed, and still in `stderr`.
380
526
 
381
527
  A complete exercise check:
382
528
 
@@ -390,11 +536,11 @@ A complete exercise check:
390
536
  ```js
391
537
  const expected = '1\n2\n3\n4\n5\n';
392
538
 
393
- document.addEventListener('py-output', (event) => {
539
+ document.addEventListener('playpad-output', (event) => {
394
540
  if (event.target.id !== 'ex1') return;
395
541
 
396
542
  const el = document.getElementById('ex1-result');
397
- if (event.detail.stderr) {
543
+ if (event.detail.failed) {
398
544
  el.textContent = 'Your code raised an error — read the message above.';
399
545
  } else if (event.detail.stdout === expected) {
400
546
  el.textContent = '✅ Correct';
@@ -404,10 +550,73 @@ document.addEventListener('py-output', (event) => {
404
550
  });
405
551
  ```
406
552
 
407
- `py-error` also carries a `hint` when the runtime can explain the failure in
553
+ `playpad-error` also carries a `hint` when the runtime can explain the failure in
408
554
  plain language — for example when a program calls `input()` more times than the
409
555
  standard-input box has lines.
410
556
 
557
+ ## Practice mode (Java)
558
+
559
+ For LeetCode-style exercises, give a Java widget test cases for one method.
560
+ Run then calls that method once per case, instead of running `main` (no
561
+ `main` is needed), and reports each case. The output shows one line per
562
+ case and the tally; the status line says "2 of 3 passed".
563
+
564
+ ```html
565
+ <code-playpad language="java" height="auto"
566
+ practice='{"method": "lowerBound", "cases": [
567
+ {"args": [[1, 3, 3, 8], 3], "expected": 1},
568
+ {"args": [[1, 3, 3, 8], 9], "expected": 4},
569
+ {"args": [[], 5], "expected": 0}]}'>
570
+ class Solution {
571
+ static int lowerBound(int[] sorted, int target) {
572
+ return 0; // your code here
573
+ }
574
+ }
575
+ </code-playpad>
576
+ ```
577
+
578
+ Or set it from JavaScript (`el.practice = { method, cases }`), or pass the
579
+ `practice` prop to the React component. Each case's result arrives in
580
+ `playpad-output` as `detail.cases`, with `detail.passed` and `detail.total`,
581
+ so grading code can read them instead of comparing text:
582
+
583
+ ```js
584
+ document.addEventListener('playpad-output', (event) => {
585
+ const { cases, passed, total } = event.detail;
586
+ if (cases) console.log(`${passed} of ${total}`, cases.filter((c) => c.status !== 'passed'));
587
+ });
588
+ ```
589
+
590
+ **The spec.** `method` (required); `cases`, each `{ args, expected?, id? }`;
591
+ `className` when several classes have a method of that name; `params`
592
+ (`["int[]", "int"]`) to choose among overloads; `compare: { kind: "argument",
593
+ index: 1 }` to check an argument after the call, for a `void` method that
594
+ fills one in; `tolerance` in `compare` for doubles; `nodes` to describe a
595
+ linked-list or tree class whose fields do not make its shape obvious.
596
+
597
+ **Values** are JSON, the same way for arguments, `expected` and the
598
+ reported `actual`:
599
+
600
+ | Java | JSON |
601
+ | --- | --- |
602
+ | `int`, `boolean`, `String`, wrapper types | number, boolean, string, `null` |
603
+ | `long` | a decimal string, `"4000000000"` |
604
+ | `char` | a one-character string |
605
+ | `double` | a number, or `"NaN"`, `"Infinity"`, `"-Infinity"` |
606
+ | arrays, `List` / `ArrayList` | arrays (nested for `int[][]`, `List<List<Integer>>`) |
607
+ | linked-list nodes (a class with one field of its own type) | the values in order, `[1, 2, 3]` |
608
+ | tree nodes (two fields of its own type) | level order with `null` gaps, `[3, 9, 20, null, null, 15, 7]` |
609
+
610
+ Maps, sets and `Object` cannot be passed or returned yet.
611
+
612
+ **Each case's `status`:** `passed`, `failed` (with `expected` and
613
+ `actual`), `completed` (no `expected` was given), `runtime-error` (with
614
+ `error`, the stack trace), `resource-limit`, `engine-limitation`,
615
+ `invalid-case` (the JSON does not fit the parameters: the exercise's
616
+ mistake, not the learner's) and `not-run` (with a `reason`, such as
617
+ `compile-error`). Each case also has what it printed (`stdout`, `stderr`).
618
+ Every case runs on fresh state: static fields start over.
619
+
411
620
  ## Saving work
412
621
 
413
622
  `persist` stores the whole file map in IndexedDB, so a reader's edits survive
@@ -428,7 +637,7 @@ For a course you usually also want completion state, which the package exports:
428
637
  import { setProgress, getProgress, allProgress, loadSnippet, clearSnippet } from 'code-playpad';
429
638
 
430
639
  // mark a lesson done when its exercise passes
431
- document.addEventListener('py-output', async (event) => {
640
+ document.addEventListener('playpad-output', async (event) => {
432
641
  if (event.detail.stdout === expected) {
433
642
  await setProgress('lesson-3', { completed: true, score: 100 });
434
643
  }
@@ -622,7 +831,98 @@ warms every other page on the site.
622
831
  ## Version history
623
832
 
624
833
  No breaking changes so far: markup and API written against 0.1.3 still work on
625
- 0.3.1.
834
+ 0.5.0.
835
+
836
+ ### 0.5.0
837
+
838
+ - **Java: programs in several files** (engine 0.27.0). `Main.java` can use
839
+ `Session.java` from another tab, as `java Main.java` does on JDK 25 (JEP 458).
840
+ A file is compiled when the program first uses its class, and each file has
841
+ its own imports. Errors and stack traces name the right file and line. See
842
+ [Several files](#several-files).
843
+ - **Java: `new Object()` and `String.intern()`.**
844
+
845
+ ### 0.4.0
846
+
847
+ Java grows from a first subset into most of what practice programs and
848
+ coding-interview solutions use, with its behavior recorded against JDK 25
849
+ throughout. Nothing is removed or changed for Python, JavaScript or
850
+ TypeScript, and existing markup and code keep working.
851
+
852
+ **Java: the language** (engines 0.4.0–0.24.0)
853
+ - **Objects and classes:** several classes per file; nested, inner, local
854
+ and anonymous classes; constructors and `this(...)`; inheritance with
855
+ `super`; `abstract` classes; interfaces with default, static and private
856
+ methods; `Object`'s methods; `instanceof` and casts.
857
+ - **Enums and records:** enums with fields, methods, `switch` and constants
858
+ that have their own bodies. Records with compact constructors.
859
+ - **Exceptions:** `try`/`catch`/`finally`, multi-catch,
860
+ try-with-resources, custom exceptions, and checked exceptions checked as
861
+ javac checks them. Stack traces, `Caused by:` and NullPointerException
862
+ messages match the JDK's.
863
+ - **Numbers and control flow:** `long`, `double`, `float`, `byte` and
864
+ `short`, with Java's overflow, conversions and printing. `switch`
865
+ statements and expressions, labeled `break`/`continue`, and
866
+ multi-dimensional arrays.
867
+ - **Generics and lambdas:** generic classes and methods, raw types, `var`,
868
+ varargs, annotations, and javac's `[unchecked]` warnings. Lambdas and
869
+ method references for any functional interface, with captures.
870
+ - **Modern Java:** sealed types, pattern matching (record patterns, guards,
871
+ `case null`), text blocks, static imports, initializer blocks and
872
+ `Interface.super` calls.
873
+
874
+ **Java: the library**
875
+ - **Collections:** `ArrayList`, `HashMap` and `HashSet` (iterating in the
876
+ JDK's order), `LinkedHashMap`, `TreeMap`, `TreeSet`, `ArrayDeque`,
877
+ `PriorityQueue`, `LinkedList`, `Stack`, `EnumMap` and `EnumSet`, plus
878
+ `List.of`/`Map.of`, `subList` and `Collections`.
879
+ - **Streams:** `Stream`, `IntStream`, `Collectors` and `Optional`, run
880
+ element by element in the JDK's order.
881
+ - **Strings and formatting:** the rest of `String`, with Java's regex syntax;
882
+ `StringBuilder`; `String.format` and `printf` with Java's rounding.
883
+ - **Numbers and system:** `Math`, `Random` (seeded sequences match the
884
+ JDK's), `BigInteger` and `BigDecimal`, and `System.exit`, `arraycopy` and
885
+ `nanoTime`.
886
+ - **Input and output:** `Scanner` and `IO.readln` read the widget's input
887
+ box. `BufferedReader`, `StringTokenizer`, `StreamTokenizer`,
888
+ `PrintWriter` and `BufferedWriter` give fast I/O, and output you never
889
+ flush is lost exactly as on the JDK.
890
+
891
+ **Java: speed** (engines 0.25.0–0.26.0)
892
+ - Loop-heavy programs run 2.3–4.3× faster than in 0.24.0. Library callbacks
893
+ (comparators, `merge`, `computeIfAbsent`, `PriorityQueue` with a lambda)
894
+ run 1.5–4.8× faster than in 0.25.0.
895
+ - The 200-million-step limit now covers about 0.6–1.5 s of work, so typical
896
+ 10⁵–10⁶-element solutions finish well inside it.
897
+
898
+ **New for every language**
899
+ - **`playpad-ready`, `playpad-run`, `playpad-output`, `playpad-error`:**
900
+ language-neutral names for the four events. Each fires right after its
901
+ `py-*` original, with the same `detail`, and the `py-*` names keep firing.
902
+ - **`playpad-output` carries `failed`,** and `playpad-error` fires exactly
903
+ when a run failed. For Java, the run's own status decides (an exception, a
904
+ compile error or a non-zero `System.exit`), so javac's warnings and
905
+ `System.err` output from a program that finished normally do not count as a
906
+ failure. See [Events and grading](#events-and-grading).
907
+
908
+ **Java practice mode**
909
+ - **`practice`:** an attribute, property or React prop that gives a Java
910
+ widget test cases for one method. Run calls the method once per case and
911
+ shows a line per case and the tally.
912
+ - **`playpad-output`** carries `cases`, `passed` and `total`.
913
+ `RunOptions.practice` and `Result.cases` do the same for code that drives a
914
+ runtime directly. See [Practice mode](#practice-mode-java).
915
+
916
+ **Docs and size**
917
+ - **`JAVA-COMPATIBILITY.md`** ships in the package. It is the engine's full
918
+ compatibility table, regenerated with each engine sync: every feature and
919
+ library method, the evidence behind it, and every known difference from
920
+ the JDK.
921
+ - **Download size:** Java pages download more than in 0.3.0, Python and
922
+ JavaScript pages the same; see [Download size](#download-size).
923
+ - **Size checks:** the budgets were raised to 128 KiB for the Java chunk and
924
+ 96 KiB for its library, at the owner's direction that size is not a
925
+ constraint.
626
926
 
627
927
  ### 0.3.1
628
928
 
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.5.0: CDN entry, loads dist/standalone.js of this exact version */
2
+ export * from "https://cdn.jsdelivr.net/npm/code-playpad@0.5.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-C9S4Me6g.js`).then(e=>e.pythonRuntime)),a(`javascript`,()=>import(`./javascript-krS-hdur.js`).then(e=>e.javascriptRuntime)),a(`typescript`,()=>import(`./javascript-krS-hdur.js`).then(e=>e.typescriptRuntime)),a(`java`,()=>import(`./java-BHbRkfsQ.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);