@mcp-b/interactive-components 0.2.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.
Files changed (36) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +99 -0
  3. package/dist/code-editor-B94tewLC.js +425 -0
  4. package/dist/code-preview-gKk0nBKP.js +269 -0
  5. package/dist/components/code-editor/code-editor.d.ts +65 -0
  6. package/dist/components/code-editor/code-editor.js +2 -0
  7. package/dist/components/code-preview/code-preview.d.ts +58 -0
  8. package/dist/components/code-preview/code-preview.js +3 -0
  9. package/dist/components/interactive-editor/interactive-editor.d.ts +77 -0
  10. package/dist/components/interactive-editor/interactive-editor.js +3 -0
  11. package/dist/components/r-editor/r-editor.d.ts +68 -0
  12. package/dist/components/r-editor/r-editor.js +2 -0
  13. package/dist/components/sql-editor/sql-editor.d.ts +78 -0
  14. package/dist/components/sql-editor/sql-editor.js +2 -0
  15. package/dist/decorate-DcF3lt7P.js +9 -0
  16. package/dist/docs/custom-elements.json +2807 -0
  17. package/dist/index.d.ts +8 -0
  18. package/dist/index.js +8 -0
  19. package/dist/interactive-editor-WfTWxJGF.js +1454 -0
  20. package/dist/lib/runner-channel.d.ts +25 -0
  21. package/dist/lib/runner-channel.js +55 -0
  22. package/dist/lib/runner-url.d.ts +15 -0
  23. package/dist/lib/runner-url.js +19 -0
  24. package/dist/lib/transform.d.ts +2 -0
  25. package/dist/lib/transform.js +210 -0
  26. package/dist/r-editor-DwSyDo2i.js +743 -0
  27. package/dist/runners/ephemeral-indexeddb.js +114 -0
  28. package/dist/runners/pglite-runner.js +175 -0
  29. package/dist/runners/pyscript-runner.html +189 -0
  30. package/dist/runners/webr-runner.html +282 -0
  31. package/dist/sql-editor-CsNrjJ2_.js +968 -0
  32. package/dist/themes/interactive.css +79 -0
  33. package/dist/transform-DWhHlnkw.d.ts +53 -0
  34. package/dist/vite.d.ts +14 -0
  35. package/dist/vite.js +62 -0
  36. package/package.json +96 -0
@@ -0,0 +1,1454 @@
1
+ import "./code-editor-B94tewLC.js";
2
+ import { t as __decorate } from "./decorate-DcF3lt7P.js";
3
+ import { detectLanguage } from "./lib/transform.js";
4
+ import "./code-preview-gKk0nBKP.js";
5
+ import { css, html } from "lit";
6
+ import { property, query, state } from "lit/decorators.js";
7
+ import { SigveloElement } from "@mcp-b/wc-support/base/sigvelo-element";
8
+ import hostStyles from "@mcp-b/wc-support/styles/host.styles";
9
+ import { classMap } from "lit/directives/class-map.js";
10
+ import { ifDefined } from "lit/directives/if-defined.js";
11
+ import { repeat } from "lit/directives/repeat.js";
12
+ import "@mcp-b/web-components/components/splitter/splitter.js";
13
+ import "@mcp-b/web-components/components/button/button.js";
14
+ import "@mcp-b/web-components/components/button-group/button-group.js";
15
+ import "@mcp-b/web-components/components/tooltip/tooltip.js";
16
+ //#region src/components/interactive-editor/interactive-editor.styles.ts
17
+ var interactive_editor_styles_default = css`
18
+ /* ── Host ── */
19
+ :host {
20
+ --sigvelo-interactive-editor-height: 30rem;
21
+
22
+ display: flex;
23
+ flex-direction: column;
24
+ font-family: var(
25
+ --sigvelo-font-mono,
26
+ "Fira Code",
27
+ "Cascadia Code",
28
+ "JetBrains Mono",
29
+ monospace
30
+ );
31
+ font-size: 13px;
32
+ overflow: hidden;
33
+ background: var(--sigvelo-color-neutral-bg-subtle);
34
+ height: var(--sigvelo-interactive-editor-height);
35
+ }
36
+
37
+ /* ═══════════════════════════════════════════
38
+ CONTENT WRAPPER
39
+ ═══════════════════════════════════════════ */
40
+
41
+ .content {
42
+ flex: 1;
43
+ min-height: 0;
44
+ display: flex;
45
+ flex-direction: column;
46
+ }
47
+
48
+ sigvelo-splitter {
49
+ flex: 1;
50
+ min-height: 0;
51
+ border-radius: 0;
52
+ border: none;
53
+ }
54
+
55
+ /* ═══════════════════════════════════════════
56
+ EDITOR PANE — sidebar + code, side by side
57
+ ═══════════════════════════════════════════ */
58
+
59
+ .editor-pane {
60
+ display: flex;
61
+ flex-direction: row;
62
+ height: 100%;
63
+ overflow: hidden;
64
+ background: var(--sigvelo-interactive-editor-bg, #f8f8f8);
65
+ }
66
+
67
+ .editor-pane__code {
68
+ flex: 1;
69
+ min-width: 0;
70
+ display: flex;
71
+ flex-direction: column;
72
+ }
73
+
74
+ sigvelo-code-editor {
75
+ flex: 1;
76
+ min-height: 0;
77
+ }
78
+
79
+ /* ═══════════════════════════════════════════
80
+ SIDEBAR
81
+ Uses --sigvelo-interactive-editor-* tokens for the
82
+ editor-adjacent surface, and --sigvelo-*
83
+ semantic tokens for text/strokes so it
84
+ adapts to light and dark mode.
85
+ ═══════════════════════════════════════════ */
86
+
87
+ .sidebar {
88
+ display: flex;
89
+ flex-direction: column;
90
+ width: 160px;
91
+ min-width: 160px;
92
+ background: var(--sigvelo-interactive-editor-gutter-bg);
93
+ border-right: 1px solid var(--sigvelo-color-neutral-border-subtle);
94
+ overflow: hidden;
95
+ flex-shrink: 0;
96
+ transition:
97
+ width var(--sigvelo-motion-duration-interaction) ease,
98
+ min-width var(--sigvelo-motion-duration-interaction) ease;
99
+ }
100
+
101
+ .sidebar--collapsed {
102
+ width: 36px;
103
+ min-width: 36px;
104
+ }
105
+
106
+ /* ── Sidebar header ── */
107
+ .sidebar__header {
108
+ display: flex;
109
+ align-items: center;
110
+ gap: 2px;
111
+ padding: 0 6px;
112
+ height: 32px;
113
+ flex-shrink: 0;
114
+ border-bottom: 1px solid var(--sigvelo-color-neutral-border-subtle);
115
+ }
116
+
117
+ .sidebar__title {
118
+ flex: 1;
119
+ font-size: 10px;
120
+ font-weight: 600;
121
+ text-transform: uppercase;
122
+ letter-spacing: 0.08em;
123
+ color: var(--sigvelo-color-neutral-text);
124
+ white-space: nowrap;
125
+ overflow: hidden;
126
+ user-select: none;
127
+ }
128
+
129
+ .sidebar__icon-btn {
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ width: 20px;
134
+ height: 20px;
135
+ padding: 0;
136
+ border: none;
137
+ border-radius: var(--sigvelo-radius-sm);
138
+ background: transparent;
139
+ color: var(--sigvelo-color-text-muted);
140
+ cursor: pointer;
141
+ flex-shrink: 0;
142
+ transition:
143
+ background var(--sigvelo-motion-duration-interaction),
144
+ color var(--sigvelo-motion-duration-interaction);
145
+ }
146
+
147
+ .sidebar__icon-btn:hover {
148
+ background: var(--sigvelo-color-neutral-bg-muted);
149
+ color: var(--sigvelo-color-text);
150
+ }
151
+
152
+ .sidebar__collapse-btn {
153
+ margin-left: 1px;
154
+ }
155
+
156
+ /* ── Collapsed expand button ── */
157
+ .sidebar__expand-btn {
158
+ display: flex;
159
+ align-items: center;
160
+ justify-content: center;
161
+ width: 36px;
162
+ height: 36px;
163
+ padding: 0;
164
+ border: none;
165
+ background: transparent;
166
+ color: var(--sigvelo-color-text-muted);
167
+ cursor: pointer;
168
+ flex-shrink: 0;
169
+ transition: color var(--sigvelo-motion-duration-interaction);
170
+ }
171
+
172
+ .sidebar__expand-btn:hover {
173
+ color: var(--sigvelo-color-text);
174
+ }
175
+
176
+ /* ── File list ── */
177
+ .sidebar__list {
178
+ flex: 1;
179
+ overflow-y: auto;
180
+ overflow-x: hidden;
181
+ list-style: none;
182
+ margin: 0;
183
+ padding: 4px 0;
184
+ scrollbar-width: none;
185
+ }
186
+
187
+ .sidebar__list::-webkit-scrollbar {
188
+ display: none;
189
+ }
190
+
191
+ .sidebar__item-row {
192
+ position: relative;
193
+ }
194
+
195
+ .sidebar__item {
196
+ display: flex;
197
+ align-items: center;
198
+ gap: 6px;
199
+ width: 100%;
200
+ height: 28px;
201
+ padding: 0 10px;
202
+ border: none;
203
+ border-radius: 0;
204
+ background: transparent;
205
+ color: var(--sigvelo-color-neutral-text);
206
+ font-family: inherit;
207
+ font-size: 12px;
208
+ cursor: pointer;
209
+ text-align: left;
210
+ white-space: nowrap;
211
+ overflow: hidden;
212
+ border-left: 2px solid transparent;
213
+ transition:
214
+ background var(--sigvelo-motion-duration-interaction),
215
+ color var(--sigvelo-motion-duration-interaction);
216
+ }
217
+
218
+ .sidebar__item:hover {
219
+ background: var(--sigvelo-color-neutral-bg-muted);
220
+ color: var(--sigvelo-color-text);
221
+ }
222
+
223
+ .sidebar__item--active {
224
+ background: var(--sigvelo-color-neutral-bg-muted);
225
+ color: var(--sigvelo-color-text);
226
+ border-left-color: var(--sigvelo-color-primary-bg);
227
+ }
228
+
229
+ .sidebar__item--removable {
230
+ padding-inline-end: 32px;
231
+ }
232
+
233
+ .sidebar__item-name {
234
+ flex: 1;
235
+ overflow: hidden;
236
+ text-overflow: ellipsis;
237
+ }
238
+
239
+ .sidebar__item-remove {
240
+ position: absolute;
241
+ inset-block-start: 2px;
242
+ inset-inline-end: 4px;
243
+ display: flex;
244
+ align-items: center;
245
+ justify-content: center;
246
+ width: 24px;
247
+ height: 24px;
248
+ padding: 0;
249
+ border: 0;
250
+ border-radius: var(--sigvelo-radius-xs);
251
+ background: transparent;
252
+ font-size: 13px;
253
+ line-height: 1;
254
+ color: var(--sigvelo-color-text-muted);
255
+ opacity: 0;
256
+ flex-shrink: 0;
257
+ transition:
258
+ opacity var(--sigvelo-motion-duration-interaction),
259
+ background var(--sigvelo-motion-duration-interaction);
260
+ }
261
+
262
+ .sidebar__item-row:hover .sidebar__item-remove,
263
+ .sidebar__item-row:focus-within .sidebar__item-remove {
264
+ opacity: 1;
265
+ }
266
+
267
+ .sidebar__item-remove:hover {
268
+ background: var(--sigvelo-color-neutral-bg);
269
+ color: var(--sigvelo-color-text);
270
+ }
271
+
272
+ .sidebar__item:focus-visible,
273
+ .sidebar__item-remove:focus-visible,
274
+ .mobile-file:focus-visible,
275
+ .mobile-file__close:focus-visible {
276
+ outline: var(--sigvelo-focus-ring);
277
+ outline-offset: calc(-1 * var(--sigvelo-focus-offset));
278
+ }
279
+
280
+ /* ── Add file inline form ── */
281
+ .sidebar__add-form {
282
+ padding: 6px 8px;
283
+ border-top: 1px solid var(--sigvelo-color-neutral-border-subtle);
284
+ flex-shrink: 0;
285
+ }
286
+
287
+ .sidebar__add-input {
288
+ width: 100%;
289
+ height: 24px;
290
+ border: 1px solid var(--sigvelo-color-neutral-border-muted);
291
+ border-radius: var(--sigvelo-radius-sm);
292
+ padding: 0 6px;
293
+ font-size: 11px;
294
+ font-family: inherit;
295
+ background: var(--sigvelo-color-neutral-bg-subtle);
296
+ color: var(--sigvelo-color-text);
297
+ outline: none;
298
+ box-sizing: border-box;
299
+ }
300
+
301
+ .sidebar__add-input:focus {
302
+ border-color: var(--sigvelo-color-primary-bg);
303
+ }
304
+
305
+ .sidebar__add-input--error,
306
+ .sidebar__add-input--error:focus {
307
+ border-color: var(--sigvelo-color-danger-bg);
308
+ }
309
+
310
+ .sidebar__add-error {
311
+ font-size: 10px;
312
+ color: var(--sigvelo-color-danger-text);
313
+ margin-top: 3px;
314
+ padding: 0 1px;
315
+ line-height: 1.3;
316
+ }
317
+
318
+ /* ── Folder tree ── */
319
+ .sidebar__subtree {
320
+ list-style: none;
321
+ margin: 0;
322
+ padding: 0;
323
+ }
324
+
325
+ .sidebar__item--folder {
326
+ font-weight: 500;
327
+ }
328
+
329
+ .sidebar__folder-chevron {
330
+ display: inline-flex;
331
+ align-items: center;
332
+ justify-content: center;
333
+ width: 12px;
334
+ flex-shrink: 0;
335
+ color: var(--sigvelo-color-text-muted);
336
+ opacity: 0.6;
337
+ }
338
+
339
+ /* ═══════════════════════════════════════════
340
+ LANGUAGE BADGE COLORS
341
+ ═══════════════════════════════════════════ */
342
+
343
+ .file-icon {
344
+ font-size: 8px;
345
+ font-weight: 700;
346
+ padding: 1px 3px;
347
+ border-radius: var(--sigvelo-radius-xs);
348
+ line-height: 1.2;
349
+ display: inline-block;
350
+ flex-shrink: 0;
351
+ }
352
+
353
+ .file-icon--js {
354
+ background: #f7df1e;
355
+ color: #000;
356
+ }
357
+ .file-icon--ts {
358
+ background: #3178c6;
359
+ color: #fff;
360
+ }
361
+ .file-icon--tsx {
362
+ background: #3178c6;
363
+ color: #fff;
364
+ }
365
+ .file-icon--jsx {
366
+ background: #61dafb;
367
+ color: #000;
368
+ }
369
+ .file-icon--html {
370
+ background: #c13b18;
371
+ color: #fff;
372
+ }
373
+ .file-icon--css {
374
+ background: #1e3eb8;
375
+ color: #fff;
376
+ }
377
+ .file-icon--py {
378
+ background: #3776ab;
379
+ /* Python's brand yellow (#ffd43b) is only 3.39:1 on its brand blue. The
380
+ badge keeps the blue and takes white, matching every other file icon. */
381
+ color: #fff;
382
+ }
383
+ .file-icon--r {
384
+ background: #2166b8;
385
+ color: #fff;
386
+ }
387
+
388
+ /* ═══════════════════════════════════════════
389
+ PREVIEW PANE (right side)
390
+ ═══════════════════════════════════════════ */
391
+
392
+ .preview-pane {
393
+ display: flex;
394
+ flex-direction: column;
395
+ height: 100%;
396
+ background: var(--sigvelo-color-surface);
397
+ }
398
+
399
+ .preview-pane__content {
400
+ flex: 1;
401
+ min-height: 0;
402
+ display: flex;
403
+ flex-direction: column;
404
+ }
405
+
406
+ sigvelo-code-preview {
407
+ flex: 1;
408
+ min-height: 0;
409
+ }
410
+
411
+ /* ── Right pane header ── */
412
+ .right-pane__header {
413
+ display: flex;
414
+ align-items: center;
415
+ gap: 4px;
416
+ padding: 2px 6px;
417
+ height: 32px;
418
+ flex-shrink: 0;
419
+ border-bottom: 1px solid var(--sigvelo-color-neutral-border-subtle);
420
+ background: var(--sigvelo-color-neutral-bg-subtle);
421
+ }
422
+
423
+ .right-pane__header sigvelo-button-group {
424
+ flex: 1;
425
+ }
426
+
427
+ .console-badge {
428
+ display: inline-flex;
429
+ align-items: center;
430
+ justify-content: center;
431
+ min-width: 16px;
432
+ height: 16px;
433
+ padding: 0 4px;
434
+ border-radius: var(--sigvelo-radius-full);
435
+ background: var(--sigvelo-color-danger-bg);
436
+ color: var(--sigvelo-color-danger-text-on-bg);
437
+ font-size: 10px;
438
+ font-weight: 700;
439
+ margin-left: 4px;
440
+ line-height: 1;
441
+ }
442
+
443
+ /* ═══════════════════════════════════════════
444
+ CONSOLE VIEW
445
+ ═══════════════════════════════════════════ */
446
+
447
+ .console-view {
448
+ flex: 1;
449
+ overflow-y: auto;
450
+ background: var(--sigvelo-color-surface);
451
+ font-family: var(
452
+ --sigvelo-font-mono,
453
+ "Fira Code",
454
+ "Cascadia Code",
455
+ "JetBrains Mono",
456
+ monospace
457
+ );
458
+ font-size: 12px;
459
+ scrollbar-width: thin;
460
+ }
461
+
462
+ .console-empty {
463
+ padding: 16px;
464
+ color: var(--sigvelo-color-text-muted);
465
+ font-style: italic;
466
+ text-align: center;
467
+ }
468
+
469
+ .console-entry {
470
+ display: flex;
471
+ gap: 6px;
472
+ padding: 3px 8px;
473
+ border-bottom: 1px solid var(--sigvelo-color-neutral-border-subtle);
474
+ align-items: baseline;
475
+ }
476
+
477
+ .console-entry--log {
478
+ color: var(--sigvelo-color-text);
479
+ }
480
+ .console-entry--info {
481
+ color: var(--sigvelo-color-primary-text);
482
+ background: var(--sigvelo-color-primary-bg-subtle);
483
+ }
484
+ .console-entry--warn {
485
+ color: var(--sigvelo-color-warning-text, #92400e);
486
+ background: var(--sigvelo-color-warning-bg-subtle);
487
+ }
488
+ .console-entry--error {
489
+ color: var(--sigvelo-color-danger-text);
490
+ background: var(--sigvelo-color-danger-bg-subtle);
491
+ }
492
+
493
+ .console-entry__level {
494
+ font-size: 9px;
495
+ font-weight: 700;
496
+ text-transform: uppercase;
497
+ opacity: 0.7;
498
+ flex-shrink: 0;
499
+ width: 32px;
500
+ }
501
+
502
+ .console-entry__text {
503
+ white-space: pre-wrap;
504
+ word-break: break-word;
505
+ flex: 1;
506
+ }
507
+
508
+ /* ═══════════════════════════════════════════
509
+ COMPACT / MOBILE LAYOUT
510
+ ═══════════════════════════════════════════ */
511
+
512
+ :host(.compact) {
513
+ flex-direction: column;
514
+ }
515
+
516
+ .mobile-nav {
517
+ display: flex;
518
+ align-items: center;
519
+ gap: 4px;
520
+ padding: 4px 6px;
521
+ background: var(--sigvelo-color-neutral-bg-subtle);
522
+ border-bottom: 1px solid var(--sigvelo-color-neutral-border-subtle);
523
+ flex-shrink: 0;
524
+ }
525
+
526
+ .mobile-nav sigvelo-button-group {
527
+ flex: 1;
528
+ }
529
+
530
+ .single-pane {
531
+ flex: 1;
532
+ min-height: 0;
533
+ display: flex;
534
+ flex-direction: column;
535
+ }
536
+
537
+ .mobile-files {
538
+ flex: 1;
539
+ overflow-y: auto;
540
+ list-style: none;
541
+ margin: 0;
542
+ padding: 6px;
543
+ background: var(--sigvelo-color-neutral-bg-subtle);
544
+ }
545
+
546
+ .mobile-file-row {
547
+ position: relative;
548
+ }
549
+
550
+ .mobile-file {
551
+ display: flex;
552
+ align-items: center;
553
+ gap: 8px;
554
+ width: 100%;
555
+ height: 40px;
556
+ padding: 0 10px;
557
+ margin-bottom: 2px;
558
+ border: none;
559
+ border-radius: var(--sigvelo-radius-md);
560
+ border-left: 3px solid transparent;
561
+ background: transparent;
562
+ color: var(--sigvelo-color-text-muted);
563
+ font-family: inherit;
564
+ font-size: 13px;
565
+ cursor: pointer;
566
+ text-align: left;
567
+ touch-action: manipulation;
568
+ -webkit-tap-highlight-color: transparent;
569
+ transition: background var(--sigvelo-motion-duration-interaction);
570
+ }
571
+
572
+ .mobile-file--active {
573
+ background: var(--sigvelo-color-surface);
574
+ color: var(--sigvelo-color-text);
575
+ border-left-color: var(--sigvelo-color-primary-bg);
576
+ }
577
+
578
+ .mobile-file--removable {
579
+ padding-inline-end: 42px;
580
+ }
581
+
582
+ .mobile-file__name {
583
+ flex: 1;
584
+ overflow: hidden;
585
+ text-overflow: ellipsis;
586
+ white-space: nowrap;
587
+ }
588
+
589
+ .mobile-file__close {
590
+ position: absolute;
591
+ inset-block-start: 8px;
592
+ inset-inline-end: 8px;
593
+ display: flex;
594
+ align-items: center;
595
+ justify-content: center;
596
+ width: 24px;
597
+ height: 24px;
598
+ padding: 0;
599
+ border: 0;
600
+ border-radius: var(--sigvelo-radius-sm);
601
+ background: transparent;
602
+ font-size: 16px;
603
+ color: var(--sigvelo-color-text-muted);
604
+ flex-shrink: 0;
605
+ opacity: 0.5;
606
+ }
607
+
608
+ .mobile-file__close:hover {
609
+ opacity: 1;
610
+ background: var(--sigvelo-color-neutral-bg-muted);
611
+ }
612
+
613
+ .mobile-add-btn {
614
+ display: flex;
615
+ align-items: center;
616
+ justify-content: center;
617
+ gap: 6px;
618
+ width: 100%;
619
+ height: 40px;
620
+ margin-top: 4px;
621
+ border: 1px dashed var(--sigvelo-color-neutral-border-subtle);
622
+ border-radius: var(--sigvelo-radius-md);
623
+ background: transparent;
624
+ color: var(--sigvelo-color-text-muted);
625
+ font-family: inherit;
626
+ font-size: 12px;
627
+ cursor: pointer;
628
+ opacity: 0.7;
629
+ }
630
+
631
+ .mobile-add-btn:hover {
632
+ opacity: 1;
633
+ border-color: var(--sigvelo-color-primary-bg);
634
+ color: var(--sigvelo-color-primary-text);
635
+ }
636
+
637
+ .add-file-form {
638
+ display: flex;
639
+ flex-direction: column;
640
+ gap: 4px;
641
+ }
642
+
643
+ .add-file-row {
644
+ display: flex;
645
+ gap: 4px;
646
+ }
647
+
648
+ .add-file-input {
649
+ flex: 1;
650
+ min-width: 0;
651
+ height: 22px;
652
+ border: 1px solid var(--sigvelo-color-neutral-border-subtle);
653
+ border-radius: var(--sigvelo-radius-sm);
654
+ padding: 0 6px;
655
+ font-size: 11px;
656
+ font-family: inherit;
657
+ background: var(--sigvelo-color-surface);
658
+ color: var(--sigvelo-color-text);
659
+ outline: none;
660
+ }
661
+
662
+ .add-file-input:focus {
663
+ border-color: var(--sigvelo-color-primary-bg);
664
+ }
665
+
666
+ .add-file-input--error,
667
+ .add-file-input--error:focus {
668
+ border-color: var(--sigvelo-color-danger-bg);
669
+ }
670
+
671
+ .add-file-error {
672
+ font-size: 10px;
673
+ color: var(--sigvelo-color-danger-text);
674
+ padding: 0 2px;
675
+ }
676
+ `;
677
+ //#endregion
678
+ //#region src/components/interactive-editor/interactive-editor.ts
679
+ function langIcon(lang) {
680
+ return {
681
+ javascript: "JS",
682
+ typescript: "TS",
683
+ jsx: "JSX",
684
+ tsx: "TSX",
685
+ html: "HTML",
686
+ css: "CSS",
687
+ python: "PY",
688
+ r: "R",
689
+ sql: "SQL"
690
+ }[lang];
691
+ }
692
+ function langIconClass(lang) {
693
+ return {
694
+ javascript: "file-icon--js",
695
+ typescript: "file-icon--ts",
696
+ jsx: "file-icon--jsx",
697
+ tsx: "file-icon--tsx",
698
+ html: "file-icon--html",
699
+ css: "file-icon--css",
700
+ python: "file-icon--py",
701
+ r: "file-icon--r",
702
+ sql: "file-icon--sql"
703
+ }[lang];
704
+ }
705
+ const JS_EXTENSIONS = /* @__PURE__ */ new Set([
706
+ "js",
707
+ "mjs",
708
+ "ts",
709
+ "tsx",
710
+ "jsx",
711
+ "html",
712
+ "htm",
713
+ "css",
714
+ "json"
715
+ ]);
716
+ const PY_EXTENSIONS = /* @__PURE__ */ new Set([
717
+ "py",
718
+ "html",
719
+ "htm",
720
+ "css",
721
+ "json"
722
+ ]);
723
+ const R_EXTENSIONS = /* @__PURE__ */ new Set([
724
+ "r",
725
+ "html",
726
+ "htm",
727
+ "css",
728
+ "json"
729
+ ]);
730
+ const VALID_EXTENSIONS = /* @__PURE__ */ new Set([
731
+ ...JS_EXTENSIONS,
732
+ ...PY_EXTENSIONS,
733
+ ...R_EXTENSIONS
734
+ ]);
735
+ const FILE_DEFAULTS = {
736
+ html: "<!DOCTYPE html>\n<html>\n<head>\n <meta charset=\"utf-8\">\n</head>\n<body>\n\n</body>\n</html>",
737
+ css: "/* styles */\n",
738
+ javascript: "// JavaScript\n",
739
+ typescript: "// TypeScript\n",
740
+ jsx: "export default function Component() {\n return <div>Hello</div>;\n}\n",
741
+ tsx: "export default function Component() {\n return <div>Hello</div>;\n}\n",
742
+ python: "# Python\n",
743
+ r: "# R Script\n",
744
+ sql: "-- SQL\n"
745
+ };
746
+ const COMPACT_BREAKPOINT = 560;
747
+ const ICON_PLAY = html`
748
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor" style="display: block">
749
+ <path d="M5 3l14 9-14 9V3z" />
750
+ </svg>
751
+ `;
752
+ const ICON_PLUS = html`
753
+ <svg
754
+ width="12"
755
+ height="12"
756
+ viewBox="0 0 24 24"
757
+ fill="none"
758
+ stroke="currentColor"
759
+ stroke-width="2.5"
760
+ stroke-linecap="round"
761
+ style="display: block"
762
+ >
763
+ <path d="M12 5v14M5 12h14" />
764
+ </svg>
765
+ `;
766
+ const ICON_TRASH = html`
767
+ <svg
768
+ width="12"
769
+ height="12"
770
+ viewBox="0 0 24 24"
771
+ fill="none"
772
+ stroke="currentColor"
773
+ stroke-width="2"
774
+ stroke-linecap="round"
775
+ stroke-linejoin="round"
776
+ style="display: block"
777
+ >
778
+ <path
779
+ d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"
780
+ />
781
+ </svg>
782
+ `;
783
+ const ICON_CHEVRON_LEFT = html`
784
+ <svg
785
+ width="10"
786
+ height="10"
787
+ viewBox="0 0 24 24"
788
+ fill="none"
789
+ stroke="currentColor"
790
+ stroke-width="2.5"
791
+ stroke-linecap="round"
792
+ stroke-linejoin="round"
793
+ style="display: block"
794
+ >
795
+ <path d="M15 18l-6-6 6-6" />
796
+ </svg>
797
+ `;
798
+ const ICON_CHEVRON_DOWN = html`
799
+ <svg
800
+ width="10"
801
+ height="10"
802
+ viewBox="0 0 24 24"
803
+ fill="none"
804
+ stroke="currentColor"
805
+ stroke-width="2.5"
806
+ stroke-linecap="round"
807
+ stroke-linejoin="round"
808
+ style="display: block"
809
+ >
810
+ <path d="M6 9l6 6 6-6" />
811
+ </svg>
812
+ `;
813
+ const ICON_CHEVRON_RIGHT = html`
814
+ <svg
815
+ width="10"
816
+ height="10"
817
+ viewBox="0 0 24 24"
818
+ fill="none"
819
+ stroke="currentColor"
820
+ stroke-width="2.5"
821
+ stroke-linecap="round"
822
+ stroke-linejoin="round"
823
+ style="display: block"
824
+ >
825
+ <path d="M9 18l6-6-6-6" />
826
+ </svg>
827
+ `;
828
+ const ICON_FOLDER = html`
829
+ <svg
830
+ width="14"
831
+ height="14"
832
+ viewBox="0 0 24 24"
833
+ fill="none"
834
+ stroke="currentColor"
835
+ stroke-width="2"
836
+ stroke-linecap="round"
837
+ stroke-linejoin="round"
838
+ style="display: block"
839
+ >
840
+ <path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />
841
+ </svg>
842
+ `;
843
+ function sortTree(nodes) {
844
+ nodes.sort((a, b) => {
845
+ if (a.isFolder !== b.isFolder) return a.isFolder ? -1 : 1;
846
+ return a.name.localeCompare(b.name);
847
+ });
848
+ for (const n of nodes) if (n.isFolder) sortTree(n.children);
849
+ }
850
+ function buildFileTree(files) {
851
+ const root = {
852
+ name: "",
853
+ fullPath: "",
854
+ isFolder: true,
855
+ children: []
856
+ };
857
+ for (const file of files) {
858
+ const parts = file.name.split("/");
859
+ let current = root;
860
+ for (let i = 0; i < parts.length; i++) {
861
+ const segment = parts[i];
862
+ const isLast = i === parts.length - 1;
863
+ const pathSoFar = parts.slice(0, i + 1).join("/");
864
+ if (isLast) current.children.push({
865
+ name: segment,
866
+ fullPath: file.name,
867
+ isFolder: false,
868
+ children: [],
869
+ file
870
+ });
871
+ else {
872
+ let folder = current.children.find((c) => c.isFolder && c.name === segment);
873
+ if (!folder) {
874
+ folder = {
875
+ name: segment,
876
+ fullPath: pathSoFar,
877
+ isFolder: true,
878
+ children: []
879
+ };
880
+ current.children.push(folder);
881
+ }
882
+ current = folder;
883
+ }
884
+ }
885
+ }
886
+ sortTree(root.children);
887
+ return root.children;
888
+ }
889
+ /**
890
+ * `<sigvelo-interactive-editor>`
891
+ *
892
+ * @summary A multi-file code playground with a collapsible file explorer
893
+ * sidebar, code editor, and live preview/console pane. The sidebar sits
894
+ * beside the editor only and does not extend into the preview pane.
895
+ *
896
+ * @tag sigvelo-interactive-editor
897
+ * @status experimental
898
+ * @since 0.1
899
+ * @cssproperty [--sigvelo-interactive-editor-height=30rem] - The editor's block size.
900
+ */
901
+ var SigveloInteractiveEditor = class extends SigveloElement {
902
+ constructor(..._args) {
903
+ super(..._args);
904
+ this.files = [];
905
+ this.entryFile = "";
906
+ this.layout = "vertical";
907
+ this.hideEditor = false;
908
+ this.hidePreview = false;
909
+ this.autoRun = true;
910
+ this._activeFileName = "";
911
+ this._fileContents = /* @__PURE__ */ new Map();
912
+ this._showAddFile = false;
913
+ this._newFileName = "";
914
+ this._fileError = "";
915
+ this._compact = false;
916
+ this._compactView = "code";
917
+ this._rightPane = "preview";
918
+ this._consoleLogs = [];
919
+ this._unreadConsoleCount = 0;
920
+ this._sidebarOpen = true;
921
+ this._expandedFolders = /* @__PURE__ */ new Set();
922
+ this._runDebounce = null;
923
+ this._resizeObserver = null;
924
+ }
925
+ static {
926
+ this.styles = [hostStyles, interactive_editor_styles_default];
927
+ }
928
+ connectedCallback() {
929
+ super.connectedCallback();
930
+ this._resizeObserver = new ResizeObserver((entries) => {
931
+ for (const entry of entries) {
932
+ const compact = entry.contentRect.width < COMPACT_BREAKPOINT;
933
+ if (compact !== this._compact) {
934
+ this._compact = compact;
935
+ if (compact) this.classList.add("compact");
936
+ else this.classList.remove("compact");
937
+ }
938
+ }
939
+ });
940
+ this._resizeObserver.observe(this);
941
+ }
942
+ disconnectedCallback() {
943
+ super.disconnectedCallback();
944
+ this._resizeObserver?.disconnect();
945
+ if (this._runDebounce) clearTimeout(this._runDebounce);
946
+ }
947
+ willUpdate(changed) {
948
+ if (changed.has("files")) {
949
+ this._initFiles();
950
+ this._consoleLogs = [];
951
+ this._unreadConsoleCount = 0;
952
+ }
953
+ }
954
+ updated(changed) {
955
+ if (changed.has("files")) this._runCode(false);
956
+ }
957
+ _onConsoleMessage(e) {
958
+ const entry = e.detail;
959
+ this._consoleLogs = [...this._consoleLogs, entry];
960
+ if (this._rightPane !== "console" && !this._compact) this._unreadConsoleCount++;
961
+ if (this._compact && this._compactView !== "console") this._unreadConsoleCount++;
962
+ }
963
+ _clearConsole() {
964
+ this._consoleLogs = [];
965
+ this._unreadConsoleCount = 0;
966
+ }
967
+ _initFiles() {
968
+ const prev = this._fileContents;
969
+ this._fileContents = /* @__PURE__ */ new Map();
970
+ const folders = /* @__PURE__ */ new Set();
971
+ for (const f of this.files) {
972
+ this._fileContents.set(f.name, prev.get(f.name) ?? f.content);
973
+ const parts = f.name.split("/");
974
+ for (let i = 1; i < parts.length; i++) folders.add(parts.slice(0, i).join("/"));
975
+ }
976
+ if (this._expandedFolders.size === 0 && folders.size > 0) this._expandedFolders = folders;
977
+ if (!this._activeFileName || !this._fileContents.has(this._activeFileName)) this._activeFileName = this.files[0]?.name ?? "";
978
+ }
979
+ _activeContent() {
980
+ return this._fileContents.get(this._activeFileName) ?? "";
981
+ }
982
+ _activeLanguage() {
983
+ return this.files.find((f) => f.name === this._activeFileName)?.language ?? detectLanguage(this._activeFileName);
984
+ }
985
+ _onEditorChange(e) {
986
+ const value = e.target.value;
987
+ this._fileContents = new Map(this._fileContents);
988
+ this._fileContents.set(this._activeFileName, value);
989
+ this._scheduleRun();
990
+ }
991
+ _scheduleRun() {
992
+ if (!this.autoRun) return;
993
+ if (this._runDebounce) clearTimeout(this._runDebounce);
994
+ this._runDebounce = setTimeout(() => void this._runCode(), 800);
995
+ }
996
+ async _runCode(clearConsole = true) {
997
+ if (!this._preview) return;
998
+ if (clearConsole) {
999
+ this._consoleLogs = [];
1000
+ this._unreadConsoleCount = 0;
1001
+ }
1002
+ const liveFiles = this.files.map((f) => ({
1003
+ name: f.name,
1004
+ content: this._fileContents.get(f.name) ?? f.content,
1005
+ language: f.language ?? detectLanguage(f.name)
1006
+ }));
1007
+ this._preview.files = liveFiles;
1008
+ this._preview.entryFile = this.entryFile;
1009
+ this._preview.captureConsole = true;
1010
+ await this._preview.run();
1011
+ }
1012
+ /** Returns the runtime language of the project ('python', 'r', or 'js'). */
1013
+ _projectRuntime() {
1014
+ for (const f of this.files) {
1015
+ const lang = f.language ?? detectLanguage(f.name);
1016
+ if (lang === "python") return "python";
1017
+ if (lang === "r") return "r";
1018
+ }
1019
+ return "js";
1020
+ }
1021
+ _confirmAddFile() {
1022
+ const name = this._newFileName.trim();
1023
+ if (!name) return;
1024
+ const ext = name.split(".").pop()?.toLowerCase() ?? "";
1025
+ if (!ext || !VALID_EXTENSIONS.has(ext)) {
1026
+ this._fileError = `Unsupported extension. Use: ${[...VALID_EXTENSIONS].join(", ")}`;
1027
+ return;
1028
+ }
1029
+ const runtime = this._projectRuntime();
1030
+ const isNewPy = ext === "py";
1031
+ const isNewR = ext === "r";
1032
+ if (!(ext === "html" || ext === "htm" || ext === "css" || ext === "json")) {
1033
+ if (runtime === "python" && !isNewPy) {
1034
+ this._fileError = "Cannot add JS/TS/R files to a Python project.";
1035
+ return;
1036
+ }
1037
+ if (runtime === "r" && !isNewR) {
1038
+ this._fileError = "Cannot add JS/TS/Python files to an R project.";
1039
+ return;
1040
+ }
1041
+ if (runtime === "js" && (isNewPy || isNewR) && this.files.some((f) => {
1042
+ const l = f.language ?? detectLanguage(f.name);
1043
+ return l !== "html" && l !== "css";
1044
+ })) {
1045
+ this._fileError = `Cannot add ${isNewPy ? "Python" : "R"} files to a JS/TS project.`;
1046
+ return;
1047
+ }
1048
+ }
1049
+ if (this._fileContents.has(name)) {
1050
+ this._fileError = `"${name}" already exists.`;
1051
+ return;
1052
+ }
1053
+ const lang = detectLanguage(name);
1054
+ const content = FILE_DEFAULTS[lang];
1055
+ this.files = [...this.files, {
1056
+ name,
1057
+ content,
1058
+ language: lang
1059
+ }];
1060
+ this._fileContents = new Map(this._fileContents);
1061
+ this._fileContents.set(name, content);
1062
+ this._dismissAddFile();
1063
+ this._activeFileName = name;
1064
+ }
1065
+ _dismissAddFile() {
1066
+ this._showAddFile = false;
1067
+ this._newFileName = "";
1068
+ this._fileError = "";
1069
+ }
1070
+ _removeFile(name) {
1071
+ if (this.files.length <= 1) return;
1072
+ this.files = this.files.filter((f) => f.name !== name);
1073
+ this._fileContents = new Map(this._fileContents);
1074
+ this._fileContents.delete(name);
1075
+ if (this._activeFileName === name) this._activeFileName = this.files[0]?.name ?? "";
1076
+ this.updateComplete.then(() => this._runCode());
1077
+ }
1078
+ _switchView(view) {
1079
+ this._compactView = view;
1080
+ if (view === "preview") this.updateComplete.then(() => this._runCode());
1081
+ if (view === "console") this._unreadConsoleCount = 0;
1082
+ }
1083
+ _selectFileAndShowCode(name) {
1084
+ this._activeFileName = name;
1085
+ if (this._compact) this._compactView = "code";
1086
+ }
1087
+ _switchRightPane(pane) {
1088
+ this._rightPane = pane;
1089
+ if (pane === "console") this._unreadConsoleCount = 0;
1090
+ }
1091
+ _toggleFolder(path) {
1092
+ const next = new Set(this._expandedFolders);
1093
+ if (next.has(path)) next.delete(path);
1094
+ else next.add(path);
1095
+ this._expandedFolders = next;
1096
+ }
1097
+ _renderTreeNodes(nodes, depth) {
1098
+ return nodes.map((node) => {
1099
+ if (node.isFolder) {
1100
+ const expanded = this._expandedFolders.has(node.fullPath);
1101
+ return html` <li>
1102
+ <button
1103
+ class="sidebar__item sidebar__item--folder"
1104
+ type="button"
1105
+ aria-expanded=${expanded}
1106
+ style="padding-left: ${8 + depth * 12}px"
1107
+ @click=${() => this._toggleFolder(node.fullPath)}
1108
+ >
1109
+ <span class="sidebar__folder-chevron" aria-hidden="true">
1110
+ ${expanded ? ICON_CHEVRON_DOWN : ICON_CHEVRON_RIGHT}
1111
+ </span>
1112
+ <span class="sidebar__item-name">${node.name}</span>
1113
+ </button>
1114
+ ${expanded ? html`
1115
+ <ul class="sidebar__subtree">
1116
+ ${this._renderTreeNodes(node.children, depth + 1)}
1117
+ </ul>
1118
+ ` : ""}
1119
+ </li>`;
1120
+ }
1121
+ const lang = node.file?.language ?? detectLanguage(node.fullPath);
1122
+ const isActive = node.fullPath === this._activeFileName;
1123
+ return html` <li class="sidebar__item-row">
1124
+ <button
1125
+ class=${classMap({
1126
+ sidebar__item: true,
1127
+ "sidebar__item--active": isActive,
1128
+ "sidebar__item--removable": this.files.length > 1
1129
+ })}
1130
+ type="button"
1131
+ aria-current=${ifDefined(isActive ? "page" : void 0)}
1132
+ style="padding-left: ${8 + depth * 12}px"
1133
+ @click=${() => {
1134
+ this._activeFileName = node.fullPath;
1135
+ }}
1136
+ >
1137
+ <span class="file-icon ${langIconClass(lang)}">${langIcon(lang)}</span>
1138
+ <span class="sidebar__item-name">${node.name}</span>
1139
+ </button>
1140
+ ${this.files.length > 1 ? html`
1141
+ <button
1142
+ class="sidebar__item-remove"
1143
+ type="button"
1144
+ aria-label="Remove ${node.name}"
1145
+ @click=${() => this._removeFile(node.fullPath)}
1146
+ >
1147
+ &times;
1148
+ </button>
1149
+ ` : ""}
1150
+ </li>`;
1151
+ });
1152
+ }
1153
+ _renderSidebar() {
1154
+ const tree = buildFileTree(this.files);
1155
+ return html`
1156
+ <div
1157
+ class=${classMap({
1158
+ sidebar: true,
1159
+ "sidebar--collapsed": !this._sidebarOpen
1160
+ })}
1161
+ >
1162
+ ${this._sidebarOpen ? html`
1163
+ <div class="sidebar__header">
1164
+ <span class="sidebar__title">Files</span>
1165
+ <button
1166
+ class="sidebar__icon-btn"
1167
+ aria-label="New file"
1168
+ @click=${() => {
1169
+ this._showAddFile = !this._showAddFile;
1170
+ this._fileError = "";
1171
+ }}
1172
+ >
1173
+ ${ICON_PLUS}
1174
+ </button>
1175
+ <button
1176
+ class="sidebar__icon-btn sidebar__collapse-btn"
1177
+ aria-label="Collapse sidebar"
1178
+ @click=${() => {
1179
+ this._sidebarOpen = false;
1180
+ }}
1181
+ >
1182
+ ${ICON_CHEVRON_LEFT}
1183
+ </button>
1184
+ </div>
1185
+
1186
+ <ul class="sidebar__list">
1187
+ ${this._renderTreeNodes(tree, 0)}
1188
+ </ul>
1189
+
1190
+ ${this._showAddFile ? html`
1191
+ <div class="sidebar__add-form">
1192
+ <input
1193
+ class=${classMap({
1194
+ "sidebar__add-input": true,
1195
+ "sidebar__add-input--error": !!this._fileError
1196
+ })}
1197
+ type="text"
1198
+ placeholder="src/App.tsx"
1199
+ autofocus
1200
+ .value=${this._newFileName}
1201
+ @input=${(e) => {
1202
+ this._newFileName = e.target.value;
1203
+ this._fileError = "";
1204
+ }}
1205
+ @keydown=${(e) => {
1206
+ if (e.key === "Enter") this._confirmAddFile();
1207
+ if (e.key === "Escape") this._dismissAddFile();
1208
+ }}
1209
+ />
1210
+ ${this._fileError ? html`<div class="sidebar__add-error">${this._fileError}</div>` : ""}
1211
+ </div>
1212
+ ` : ""}
1213
+ ` : html`
1214
+ <button
1215
+ id="sidebar-expand-btn"
1216
+ class="sidebar__expand-btn"
1217
+ aria-label="Expand file explorer"
1218
+ @click=${() => {
1219
+ this._sidebarOpen = true;
1220
+ }}
1221
+ >
1222
+ ${ICON_FOLDER}
1223
+ </button>
1224
+ <sigvelo-tooltip for="sidebar-expand-btn" placement="right"> Files </sigvelo-tooltip>
1225
+ `}
1226
+ </div>
1227
+ `;
1228
+ }
1229
+ _renderEditorPane(slot) {
1230
+ return html` <div class="editor-pane" slot=${ifDefined(slot)}>
1231
+ ${this._renderSidebar()}
1232
+ <div class="editor-pane__code">
1233
+ <sigvelo-code-editor
1234
+ .value=${this._activeContent()}
1235
+ .language=${this._activeLanguage()}
1236
+ editor-label="Code editor – ${this._activeFileName}"
1237
+ @sigvelo-change=${this._onEditorChange}
1238
+ >
1239
+ </sigvelo-code-editor>
1240
+ </div>
1241
+ </div>`;
1242
+ }
1243
+ _renderRightPane(slot) {
1244
+ const content = html`
1245
+ <div class="right-pane__header">
1246
+ <sigvelo-button-group label="Right pane toggle">
1247
+ <sigvelo-button
1248
+ size="xs"
1249
+ variant=${this._rightPane === "preview" ? "normal" : "ghost"}
1250
+ color=${this._rightPane === "preview" ? "primary" : "neutral"}
1251
+ @click=${() => this._switchRightPane("preview")}
1252
+ >Preview</sigvelo-button
1253
+ >
1254
+ <sigvelo-button
1255
+ size="xs"
1256
+ variant=${this._rightPane === "console" ? "normal" : "ghost"}
1257
+ color=${this._rightPane === "console" ? "primary" : "neutral"}
1258
+ @click=${() => this._switchRightPane("console")}
1259
+ >Console${this._unreadConsoleCount > 0 ? html`<span class="console-badge">${this._unreadConsoleCount}</span>` : ""}</sigvelo-button
1260
+ >
1261
+ </sigvelo-button-group>
1262
+ ${this._rightPane === "console" ? html` <sigvelo-button
1263
+ size="xs"
1264
+ variant="ghost"
1265
+ icon-label="Clear console"
1266
+ @click=${() => this._clearConsole()}
1267
+ >
1268
+ ${ICON_TRASH}
1269
+ </sigvelo-button>` : ""}
1270
+ ${!this.hidePreview ? html` <sigvelo-button size="xs" color="primary" @click=${() => void this._runCode()}>
1271
+ ${ICON_PLAY} Run
1272
+ </sigvelo-button>` : ""}
1273
+ </div>
1274
+ ${this._rightPane === "preview" ? html`
1275
+ <div class="preview-pane__content">
1276
+ <sigvelo-code-preview
1277
+ capture-console
1278
+ @sigvelo-console=${this._onConsoleMessage}
1279
+ ></sigvelo-code-preview>
1280
+ </div>
1281
+ ` : this._renderConsoleView()}
1282
+ `;
1283
+ return slot ? html`<div class="preview-pane" slot=${slot}>${content}</div>` : html`<div class="preview-pane">${content}</div>`;
1284
+ }
1285
+ _renderConsoleView() {
1286
+ return html`
1287
+ <div class="console-view">
1288
+ ${this._consoleLogs.length === 0 ? html` <div class="console-empty">No console output yet.</div> ` : this._consoleLogs.map((entry) => html`
1289
+ <div class="console-entry console-entry--${entry.level}">
1290
+ <span class="console-entry__level">${entry.level}</span>
1291
+ <span class="console-entry__text">${entry.args.join(" ")}</span>
1292
+ </div>
1293
+ `)}
1294
+ </div>
1295
+ `;
1296
+ }
1297
+ _renderMobileNav() {
1298
+ const btn = (view, label, badge) => html` <sigvelo-button
1299
+ size="xs"
1300
+ variant=${this._compactView === view ? "normal" : "ghost"}
1301
+ color=${this._compactView === view ? "primary" : "neutral"}
1302
+ @click=${() => this._switchView(view)}
1303
+ >${label}${badge && badge > 0 ? html`<span class="console-badge">${badge}</span>` : ""}</sigvelo-button
1304
+ >`;
1305
+ return html`
1306
+ <div class="mobile-nav">
1307
+ <sigvelo-button-group label="View toggle">
1308
+ ${btn("files", "Files")} ${btn("code", "Code")}
1309
+ ${!this.hidePreview ? btn("preview", "Preview") : ""}
1310
+ ${!this.hidePreview ? btn("console", "Console", this._unreadConsoleCount) : ""}
1311
+ </sigvelo-button-group>
1312
+ ${!this.hidePreview ? html` <sigvelo-button
1313
+ size="xs"
1314
+ color="primary"
1315
+ icon-label="Run"
1316
+ @click=${() => void this._runCode()}
1317
+ >
1318
+ ${ICON_PLAY}
1319
+ </sigvelo-button>` : ""}
1320
+ </div>
1321
+ `;
1322
+ }
1323
+ _renderMobileFileList() {
1324
+ return html`
1325
+ <ul class="mobile-files">
1326
+ ${repeat(this.files, (f) => f.name, (f) => {
1327
+ const lang = f.language ?? detectLanguage(f.name);
1328
+ const isActive = f.name === this._activeFileName;
1329
+ return html` <li class="mobile-file-row">
1330
+ <button
1331
+ class=${classMap({
1332
+ "mobile-file": true,
1333
+ "mobile-file--active": isActive,
1334
+ "mobile-file--removable": this.files.length > 1
1335
+ })}
1336
+ type="button"
1337
+ aria-current=${ifDefined(isActive ? "page" : void 0)}
1338
+ @click=${() => this._selectFileAndShowCode(f.name)}
1339
+ >
1340
+ <span
1341
+ class="file-icon file-icon--${lang === "javascript" ? "js" : lang === "typescript" ? "ts" : lang}"
1342
+ >${langIcon(lang)}</span
1343
+ >
1344
+ <span class="mobile-file__name">${f.name}</span>
1345
+ </button>
1346
+ ${this.files.length > 1 ? html`<button
1347
+ class="mobile-file__close"
1348
+ type="button"
1349
+ aria-label="Remove ${f.name}"
1350
+ @click=${() => this._removeFile(f.name)}
1351
+ >
1352
+ &times;
1353
+ </button>` : ""}
1354
+ </li>`;
1355
+ })}
1356
+ <li>
1357
+ ${this._showAddFile ? html`<div style="padding: 6px;">
1358
+ <div class="add-file-form">
1359
+ <div class="add-file-row">
1360
+ <input
1361
+ class="add-file-input ${this._fileError ? "add-file-input--error" : ""}"
1362
+ type="text"
1363
+ placeholder="filename.ts"
1364
+ .value=${this._newFileName}
1365
+ @input=${(e) => {
1366
+ this._newFileName = e.target.value;
1367
+ this._fileError = "";
1368
+ }}
1369
+ @keydown=${(e) => {
1370
+ if (e.key === "Enter") this._confirmAddFile();
1371
+ if (e.key === "Escape") this._dismissAddFile();
1372
+ }}
1373
+ />
1374
+ <sigvelo-button size="xs" color="primary" @click=${() => this._confirmAddFile()}
1375
+ >Add</sigvelo-button
1376
+ >
1377
+ </div>
1378
+ ${this._fileError ? html`<div class="add-file-error">${this._fileError}</div>` : ""}
1379
+ </div>
1380
+ </div>` : html`<button
1381
+ class="mobile-add-btn"
1382
+ @click=${() => {
1383
+ this._showAddFile = true;
1384
+ }}
1385
+ >
1386
+ + New file
1387
+ </button>`}
1388
+ </li>
1389
+ </ul>
1390
+ `;
1391
+ }
1392
+ _renderContent() {
1393
+ if (this.hidePreview) return this._renderEditorPane();
1394
+ if (this.hideEditor) return this._renderRightPane();
1395
+ return html` <sigvelo-splitter
1396
+ orientation=${this.layout === "horizontal" ? "horizontal" : "vertical"}
1397
+ position="0.5"
1398
+ style="--divider-min-position: 20%; --divider-max-position: 80%;"
1399
+ >
1400
+ ${this._renderEditorPane("start")} ${this._renderRightPane("end")}
1401
+ </sigvelo-splitter>`;
1402
+ }
1403
+ render() {
1404
+ if (this._compact) return html`
1405
+ ${this._renderMobileNav()}
1406
+ <div class="single-pane">
1407
+ ${this._compactView === "files" ? this._renderMobileFileList() : this._compactView === "code" ? this._renderEditorPane() : this._compactView === "console" ? this._renderConsoleView() : html`
1408
+ <div class="preview-pane">
1409
+ <div class="preview-pane__content">
1410
+ <sigvelo-code-preview
1411
+ capture-console
1412
+ @sigvelo-console=${this._onConsoleMessage}
1413
+ ></sigvelo-code-preview>
1414
+ </div>
1415
+ </div>
1416
+ `}
1417
+ </div>
1418
+ `;
1419
+ return html` <div class="content">${this._renderContent()}</div> `;
1420
+ }
1421
+ };
1422
+ __decorate([property({ type: Array })], SigveloInteractiveEditor.prototype, "files", void 0);
1423
+ __decorate([property({ attribute: "entry-file" })], SigveloInteractiveEditor.prototype, "entryFile", void 0);
1424
+ __decorate([property({ reflect: true })], SigveloInteractiveEditor.prototype, "layout", void 0);
1425
+ __decorate([property({
1426
+ attribute: "hide-editor",
1427
+ type: Boolean,
1428
+ reflect: true
1429
+ })], SigveloInteractiveEditor.prototype, "hideEditor", void 0);
1430
+ __decorate([property({
1431
+ attribute: "hide-preview",
1432
+ type: Boolean,
1433
+ reflect: true
1434
+ })], SigveloInteractiveEditor.prototype, "hidePreview", void 0);
1435
+ __decorate([property({
1436
+ attribute: "auto-run",
1437
+ type: Boolean
1438
+ })], SigveloInteractiveEditor.prototype, "autoRun", void 0);
1439
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_activeFileName", void 0);
1440
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_fileContents", void 0);
1441
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_showAddFile", void 0);
1442
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_newFileName", void 0);
1443
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_fileError", void 0);
1444
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_compact", void 0);
1445
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_compactView", void 0);
1446
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_rightPane", void 0);
1447
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_consoleLogs", void 0);
1448
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_unreadConsoleCount", void 0);
1449
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_sidebarOpen", void 0);
1450
+ __decorate([state()], SigveloInteractiveEditor.prototype, "_expandedFolders", void 0);
1451
+ __decorate([query("sigvelo-code-preview")], SigveloInteractiveEditor.prototype, "_preview", void 0);
1452
+ customElements.define("sigvelo-interactive-editor", SigveloInteractiveEditor);
1453
+ //#endregion
1454
+ export { SigveloInteractiveEditor as t };