@ttsc/playground 0.14.0-dev.20250530.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (235) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +191 -0
  3. package/lib/src/compiler/DEFAULT_ENTRY_FILE.d.ts +2 -0
  4. package/lib/src/compiler/DEFAULT_ENTRY_FILE.js +6 -0
  5. package/lib/src/compiler/DEFAULT_ENTRY_FILE.js.map +1 -0
  6. package/lib/src/compiler/DEFAULT_LINT_PLUGIN_NAME.d.ts +5 -0
  7. package/lib/src/compiler/DEFAULT_LINT_PLUGIN_NAME.js +9 -0
  8. package/lib/src/compiler/DEFAULT_LINT_PLUGIN_NAME.js.map +1 -0
  9. package/lib/src/compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS.d.ts +19 -0
  10. package/lib/src/compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS.js +23 -0
  11. package/lib/src/compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS.js.map +1 -0
  12. package/lib/src/compiler/DEFAULT_TSCONFIG_PATH.d.ts +2 -0
  13. package/lib/src/compiler/DEFAULT_TSCONFIG_PATH.js +6 -0
  14. package/lib/src/compiler/DEFAULT_TSCONFIG_PATH.js.map +1 -0
  15. package/lib/src/compiler/DEFAULT_TYPIA_PLUGIN_NAME.d.ts +6 -0
  16. package/lib/src/compiler/DEFAULT_TYPIA_PLUGIN_NAME.js +10 -0
  17. package/lib/src/compiler/DEFAULT_TYPIA_PLUGIN_NAME.js.map +1 -0
  18. package/lib/src/compiler/DEFAULT_WORK_DIR.d.ts +2 -0
  19. package/lib/src/compiler/DEFAULT_WORK_DIR.js +6 -0
  20. package/lib/src/compiler/DEFAULT_WORK_DIR.js.map +1 -0
  21. package/lib/src/compiler/buildTsconfigJSON.d.ts +9 -0
  22. package/lib/src/compiler/buildTsconfigJSON.js +24 -0
  23. package/lib/src/compiler/buildTsconfigJSON.js.map +1 -0
  24. package/lib/src/compiler/createTypiaSourcePackMount.d.ts +11 -0
  25. package/lib/src/compiler/createTypiaSourcePackMount.js +29 -0
  26. package/lib/src/compiler/createTypiaSourcePackMount.js.map +1 -0
  27. package/lib/src/compiler/createWorkerCompiler.d.ts +25 -0
  28. package/lib/src/compiler/createWorkerCompiler.js +227 -0
  29. package/lib/src/compiler/createWorkerCompiler.js.map +1 -0
  30. package/lib/src/compiler/installDependenciesIntoMemFS.d.ts +12 -0
  31. package/lib/src/compiler/installDependenciesIntoMemFS.js +25 -0
  32. package/lib/src/compiler/installDependenciesIntoMemFS.js.map +1 -0
  33. package/lib/src/compiler/installTypiaSourcePack.d.ts +11 -0
  34. package/lib/src/compiler/installTypiaSourcePack.js +21 -0
  35. package/lib/src/compiler/installTypiaSourcePack.js.map +1 -0
  36. package/lib/src/compiler/internal/joinUnder.d.ts +5 -0
  37. package/lib/src/compiler/internal/joinUnder.js +13 -0
  38. package/lib/src/compiler/internal/joinUnder.js.map +1 -0
  39. package/lib/src/compiler/internal/parseLintDiagnostics.d.ts +6 -0
  40. package/lib/src/compiler/internal/parseLintDiagnostics.js +57 -0
  41. package/lib/src/compiler/internal/parseLintDiagnostics.js.map +1 -0
  42. package/lib/src/compiler/internal/safeParseTypiaTransform.d.ts +11 -0
  43. package/lib/src/compiler/internal/safeParseTypiaTransform.js +21 -0
  44. package/lib/src/compiler/internal/safeParseTypiaTransform.js.map +1 -0
  45. package/lib/src/compiler/lineColumnOf.d.ts +5 -0
  46. package/lib/src/compiler/lineColumnOf.js +15 -0
  47. package/lib/src/compiler/lineColumnOf.js.map +1 -0
  48. package/lib/src/compiler/loadTypiaSourcePack.d.ts +8 -0
  49. package/lib/src/compiler/loadTypiaSourcePack.js +33 -0
  50. package/lib/src/compiler/loadTypiaSourcePack.js.map +1 -0
  51. package/lib/src/compiler/mapDiagnostic.d.ts +9 -0
  52. package/lib/src/compiler/mapDiagnostic.js +22 -0
  53. package/lib/src/compiler/mapDiagnostic.js.map +1 -0
  54. package/lib/src/compiler/normalizeError.d.ts +2 -0
  55. package/lib/src/compiler/normalizeError.js +14 -0
  56. package/lib/src/compiler/normalizeError.js.map +1 -0
  57. package/lib/src/compiler/normalizeNodeModulePath.d.ts +8 -0
  58. package/lib/src/compiler/normalizeNodeModulePath.js +19 -0
  59. package/lib/src/compiler/normalizeNodeModulePath.js.map +1 -0
  60. package/lib/src/compiler/pickEmittedJS.d.ts +6 -0
  61. package/lib/src/compiler/pickEmittedJS.js +21 -0
  62. package/lib/src/compiler/pickEmittedJS.js.map +1 -0
  63. package/lib/src/index.d.ts +47 -0
  64. package/lib/src/index.js +104 -0
  65. package/lib/src/index.js.map +1 -0
  66. package/lib/src/npm/BUILT_IN_PLAYGROUND_PACKAGES.d.ts +6 -0
  67. package/lib/src/npm/BUILT_IN_PLAYGROUND_PACKAGES.js +15 -0
  68. package/lib/src/npm/BUILT_IN_PLAYGROUND_PACKAGES.js.map +1 -0
  69. package/lib/src/npm/collectExternalPackageNames.d.ts +5 -0
  70. package/lib/src/npm/collectExternalPackageNames.js +34 -0
  71. package/lib/src/npm/collectExternalPackageNames.js.map +1 -0
  72. package/lib/src/npm/installPlaygroundDependencies.d.ts +13 -0
  73. package/lib/src/npm/installPlaygroundDependencies.js +121 -0
  74. package/lib/src/npm/installPlaygroundDependencies.js.map +1 -0
  75. package/lib/src/npm/internal/npmRegistry.d.ts +57 -0
  76. package/lib/src/npm/internal/npmRegistry.js +216 -0
  77. package/lib/src/npm/internal/npmRegistry.js.map +1 -0
  78. package/lib/src/npm/packageNameFromSpecifier.d.ts +7 -0
  79. package/lib/src/npm/packageNameFromSpecifier.js +66 -0
  80. package/lib/src/npm/packageNameFromSpecifier.js.map +1 -0
  81. package/lib/src/react/ConsoleViewer.d.ts +7 -0
  82. package/lib/src/react/ConsoleViewer.js +57 -0
  83. package/lib/src/react/ConsoleViewer.js.map +1 -0
  84. package/lib/src/react/DEFAULT_OPTION_TOGGLES.d.ts +3 -0
  85. package/lib/src/react/DEFAULT_OPTION_TOGGLES.js +17 -0
  86. package/lib/src/react/DEFAULT_OPTION_TOGGLES.js.map +1 -0
  87. package/lib/src/react/DependencyProgressModal.d.ts +7 -0
  88. package/lib/src/react/DependencyProgressModal.js +16 -0
  89. package/lib/src/react/DependencyProgressModal.js.map +1 -0
  90. package/lib/src/react/DiagnosticsPanel.d.ts +4 -0
  91. package/lib/src/react/DiagnosticsPanel.js +15 -0
  92. package/lib/src/react/DiagnosticsPanel.js.map +1 -0
  93. package/lib/src/react/ExamplePicker.d.ts +9 -0
  94. package/lib/src/react/ExamplePicker.js +43 -0
  95. package/lib/src/react/ExamplePicker.js.map +1 -0
  96. package/lib/src/react/LintPane.d.ts +9 -0
  97. package/lib/src/react/LintPane.js +17 -0
  98. package/lib/src/react/LintPane.js.map +1 -0
  99. package/lib/src/react/OptionsPanel.d.ts +13 -0
  100. package/lib/src/react/OptionsPanel.js +43 -0
  101. package/lib/src/react/OptionsPanel.js.map +1 -0
  102. package/lib/src/react/PlaygroundShell.d.ts +2 -0
  103. package/lib/src/react/PlaygroundShell.js +609 -0
  104. package/lib/src/react/PlaygroundShell.js.map +1 -0
  105. package/lib/src/react/ResultViewer.d.ts +10 -0
  106. package/lib/src/react/ResultViewer.js +49 -0
  107. package/lib/src/react/ResultViewer.js.map +1 -0
  108. package/lib/src/react/SourceEditor.d.ts +2 -0
  109. package/lib/src/react/SourceEditor.js +74 -0
  110. package/lib/src/react/SourceEditor.js.map +1 -0
  111. package/lib/src/react/createCompilerClient.d.ts +14 -0
  112. package/lib/src/react/createCompilerClient.js +77 -0
  113. package/lib/src/react/createCompilerClient.js.map +1 -0
  114. package/lib/src/sandbox/createSandboxRequire.d.ts +11 -0
  115. package/lib/src/sandbox/createSandboxRequire.js +211 -0
  116. package/lib/src/sandbox/createSandboxRequire.js.map +1 -0
  117. package/lib/src/sandbox/loadTypiaRuntimePack.d.ts +7 -0
  118. package/lib/src/sandbox/loadTypiaRuntimePack.js +35 -0
  119. package/lib/src/sandbox/loadTypiaRuntimePack.js.map +1 -0
  120. package/lib/src/structures/IBuildTsconfigOptions.d.ts +19 -0
  121. package/lib/src/structures/IBuildTsconfigOptions.js +3 -0
  122. package/lib/src/structures/IBuildTsconfigOptions.js.map +1 -0
  123. package/lib/src/structures/ICompilerService.d.ts +84 -0
  124. package/lib/src/structures/ICompilerService.js +3 -0
  125. package/lib/src/structures/ICompilerService.js.map +1 -0
  126. package/lib/src/structures/IConsoleMessage.d.ts +10 -0
  127. package/lib/src/structures/IConsoleMessage.js +3 -0
  128. package/lib/src/structures/IConsoleMessage.js.map +1 -0
  129. package/lib/src/structures/ICreateCompilerClientOptions.d.ts +13 -0
  130. package/lib/src/structures/ICreateCompilerClientOptions.js +3 -0
  131. package/lib/src/structures/ICreateCompilerClientOptions.js.map +1 -0
  132. package/lib/src/structures/ICreateWorkerCompilerOptions.d.ts +35 -0
  133. package/lib/src/structures/ICreateWorkerCompilerOptions.js +3 -0
  134. package/lib/src/structures/ICreateWorkerCompilerOptions.js.map +1 -0
  135. package/lib/src/structures/IInstallTypiaSourcePackOptions.d.ts +18 -0
  136. package/lib/src/structures/IInstallTypiaSourcePackOptions.js +3 -0
  137. package/lib/src/structures/IInstallTypiaSourcePackOptions.js.map +1 -0
  138. package/lib/src/structures/ILintPluginConfig.d.ts +5 -0
  139. package/lib/src/structures/ILintPluginConfig.js +3 -0
  140. package/lib/src/structures/ILintPluginConfig.js.map +1 -0
  141. package/lib/src/structures/IOptionToggle.d.ts +12 -0
  142. package/lib/src/structures/IOptionToggle.js +3 -0
  143. package/lib/src/structures/IOptionToggle.js.map +1 -0
  144. package/lib/src/structures/IPlaygroundDependencyInstallOptions.d.ts +16 -0
  145. package/lib/src/structures/IPlaygroundDependencyInstallOptions.js +3 -0
  146. package/lib/src/structures/IPlaygroundDependencyInstallOptions.js.map +1 -0
  147. package/lib/src/structures/IPlaygroundDependencyInstallResult.d.ts +20 -0
  148. package/lib/src/structures/IPlaygroundDependencyInstallResult.js +3 -0
  149. package/lib/src/structures/IPlaygroundDependencyInstallResult.js.map +1 -0
  150. package/lib/src/structures/IPlaygroundDependencyPackage.d.ts +8 -0
  151. package/lib/src/structures/IPlaygroundDependencyPackage.js +3 -0
  152. package/lib/src/structures/IPlaygroundDependencyPackage.js.map +1 -0
  153. package/lib/src/structures/IPlaygroundDependencyProgress.d.ts +10 -0
  154. package/lib/src/structures/IPlaygroundDependencyProgress.js +3 -0
  155. package/lib/src/structures/IPlaygroundDependencyProgress.js.map +1 -0
  156. package/lib/src/structures/IPlaygroundDependencyProgressPhase.d.ts +5 -0
  157. package/lib/src/structures/IPlaygroundDependencyProgressPhase.js +3 -0
  158. package/lib/src/structures/IPlaygroundDependencyProgressPhase.js.map +1 -0
  159. package/lib/src/structures/IPlaygroundExample.d.ts +16 -0
  160. package/lib/src/structures/IPlaygroundExample.js +3 -0
  161. package/lib/src/structures/IPlaygroundExample.js.map +1 -0
  162. package/lib/src/structures/IPlaygroundShellProps.d.ts +70 -0
  163. package/lib/src/structures/IPlaygroundShellProps.js +3 -0
  164. package/lib/src/structures/IPlaygroundShellProps.js.map +1 -0
  165. package/lib/src/structures/ISourceEditorProps.d.ts +18 -0
  166. package/lib/src/structures/ISourceEditorProps.js +3 -0
  167. package/lib/src/structures/ISourceEditorProps.js.map +1 -0
  168. package/lib/src/structures/ITransformOptions.d.ts +15 -0
  169. package/lib/src/structures/ITransformOptions.js +3 -0
  170. package/lib/src/structures/ITransformOptions.js.map +1 -0
  171. package/lib/src/structures/ITypiaPluginConfig.d.ts +19 -0
  172. package/lib/src/structures/ITypiaPluginConfig.js +3 -0
  173. package/lib/src/structures/ITypiaPluginConfig.js.map +1 -0
  174. package/lib/src/structures/index.d.ts +18 -0
  175. package/lib/src/structures/index.js +35 -0
  176. package/lib/src/structures/index.js.map +1 -0
  177. package/package.json +66 -0
  178. package/src/compiler/DEFAULT_ENTRY_FILE.ts +2 -0
  179. package/src/compiler/DEFAULT_LINT_PLUGIN_NAME.ts +5 -0
  180. package/src/compiler/DEFAULT_PLAYGROUND_COMPILER_OPTIONS.ts +19 -0
  181. package/src/compiler/DEFAULT_TSCONFIG_PATH.ts +2 -0
  182. package/src/compiler/DEFAULT_TYPIA_PLUGIN_NAME.ts +6 -0
  183. package/src/compiler/DEFAULT_WORK_DIR.ts +2 -0
  184. package/src/compiler/buildTsconfigJSON.ts +22 -0
  185. package/src/compiler/createTypiaSourcePackMount.ts +32 -0
  186. package/src/compiler/createWorkerCompiler.ts +273 -0
  187. package/src/compiler/installDependenciesIntoMemFS.ts +28 -0
  188. package/src/compiler/installTypiaSourcePack.ts +24 -0
  189. package/src/compiler/internal/joinUnder.ts +8 -0
  190. package/src/compiler/internal/parseLintDiagnostics.ts +61 -0
  191. package/src/compiler/internal/safeParseTypiaTransform.ts +24 -0
  192. package/src/compiler/lineColumnOf.ts +14 -0
  193. package/src/compiler/loadTypiaSourcePack.ts +37 -0
  194. package/src/compiler/mapDiagnostic.ts +26 -0
  195. package/src/compiler/normalizeError.ts +12 -0
  196. package/src/compiler/normalizeNodeModulePath.ts +13 -0
  197. package/src/compiler/pickEmittedJS.ts +18 -0
  198. package/src/index.ts +57 -0
  199. package/src/npm/BUILT_IN_PLAYGROUND_PACKAGES.ts +11 -0
  200. package/src/npm/collectExternalPackageNames.ts +34 -0
  201. package/src/npm/installPlaygroundDependencies.ts +163 -0
  202. package/src/npm/internal/npmRegistry.ts +292 -0
  203. package/src/npm/packageNameFromSpecifier.ts +61 -0
  204. package/src/react/ConsoleViewer.tsx +121 -0
  205. package/src/react/DEFAULT_OPTION_TOGGLES.ts +15 -0
  206. package/src/react/DependencyProgressModal.tsx +73 -0
  207. package/src/react/DiagnosticsPanel.tsx +74 -0
  208. package/src/react/ExamplePicker.tsx +93 -0
  209. package/src/react/LintPane.tsx +58 -0
  210. package/src/react/OptionsPanel.tsx +132 -0
  211. package/src/react/PlaygroundShell.tsx +864 -0
  212. package/src/react/ResultViewer.tsx +75 -0
  213. package/src/react/SourceEditor.tsx +96 -0
  214. package/src/react/createCompilerClient.ts +80 -0
  215. package/src/sandbox/createSandboxRequire.ts +275 -0
  216. package/src/sandbox/loadTypiaRuntimePack.ts +36 -0
  217. package/src/structures/IBuildTsconfigOptions.ts +19 -0
  218. package/src/structures/ICompilerService.ts +96 -0
  219. package/src/structures/IConsoleMessage.ts +10 -0
  220. package/src/structures/ICreateCompilerClientOptions.ts +13 -0
  221. package/src/structures/ICreateWorkerCompilerOptions.ts +40 -0
  222. package/src/structures/IInstallTypiaSourcePackOptions.ts +18 -0
  223. package/src/structures/ILintPluginConfig.ts +5 -0
  224. package/src/structures/IOptionToggle.ts +12 -0
  225. package/src/structures/IPlaygroundDependencyInstallOptions.ts +17 -0
  226. package/src/structures/IPlaygroundDependencyInstallResult.ts +21 -0
  227. package/src/structures/IPlaygroundDependencyPackage.ts +8 -0
  228. package/src/structures/IPlaygroundDependencyProgress.ts +11 -0
  229. package/src/structures/IPlaygroundDependencyProgressPhase.ts +12 -0
  230. package/src/structures/IPlaygroundExample.ts +16 -0
  231. package/src/structures/IPlaygroundShellProps.ts +83 -0
  232. package/src/structures/ISourceEditorProps.ts +18 -0
  233. package/src/structures/ITransformOptions.ts +15 -0
  234. package/src/structures/ITypiaPluginConfig.ts +20 -0
  235. package/src/structures/index.ts +18 -0
@@ -0,0 +1,121 @@
1
+ "use client";
2
+
3
+ import type { JSX } from "react";
4
+
5
+ import type { IConsoleMessage } from "../structures/IConsoleMessage";
6
+
7
+ interface ConsoleViewerProps {
8
+ messages: IConsoleMessage[];
9
+ empty?: string;
10
+ }
11
+
12
+ export function ConsoleViewer({
13
+ messages,
14
+ empty = "No output yet. Click Execute to run the compiled JavaScript.",
15
+ }: ConsoleViewerProps) {
16
+ if (messages.length === 0)
17
+ return (
18
+ <div className="h-full w-full flex items-center justify-center text-neutral-600 font-mono text-[11px] px-4 text-center">
19
+ {empty}
20
+ </div>
21
+ );
22
+ return (
23
+ <div className="h-full w-full overflow-auto p-3 font-mono text-[12px] leading-snug">
24
+ {messages.map((msg, i) => (
25
+ <div
26
+ key={i}
27
+ className="py-1 border-b border-neutral-900/70 last:border-b-0 flex gap-2"
28
+ >
29
+ <span
30
+ className={`shrink-0 text-[10px] uppercase tracking-wider w-12 ${typeColor(
31
+ msg.type,
32
+ )}`}
33
+ >
34
+ {msg.type}
35
+ </span>
36
+ <span className="flex-1 break-words whitespace-pre-wrap text-neutral-200">
37
+ {(Array.isArray(msg.value) ? msg.value : [msg.value]).map(
38
+ (arg, idx) => (
39
+ <span key={idx}>
40
+ {idx > 0 ? " " : ""}
41
+ {formatValue(arg)}
42
+ </span>
43
+ ),
44
+ )}
45
+ </span>
46
+ </div>
47
+ ))}
48
+ </div>
49
+ );
50
+ }
51
+
52
+ function typeColor(type: IConsoleMessage["type"]): string {
53
+ switch (type) {
54
+ case "error":
55
+ return "text-red-400";
56
+ case "warn":
57
+ return "text-yellow-400";
58
+ case "info":
59
+ return "text-sky-400";
60
+ case "debug":
61
+ return "text-fuchsia-400";
62
+ case "dir":
63
+ case "table":
64
+ return "text-cyan-400";
65
+ default:
66
+ return "text-emerald-400";
67
+ }
68
+ }
69
+
70
+ function formatValue(value: unknown, depth = 0): JSX.Element {
71
+ if (typeof value === "string")
72
+ return <span className="text-amber-200">{JSON.stringify(value)}</span>;
73
+ if (typeof value === "number")
74
+ return <span className="text-purple-300">{String(value)}</span>;
75
+ if (typeof value === "boolean")
76
+ return <span className="text-sky-300">{String(value)}</span>;
77
+ if (value === null) return <span className="text-neutral-500">null</span>;
78
+ if (value === undefined)
79
+ return <span className="text-neutral-500">undefined</span>;
80
+ if (typeof value === "function")
81
+ return <span className="text-neutral-500">[Function]</span>;
82
+ if (Array.isArray(value))
83
+ return (
84
+ <span>
85
+ [
86
+ {value.map((item, idx) => (
87
+ <span key={idx}>
88
+ {idx > 0 ? ", " : ""}
89
+ {formatValue(item, depth + 1)}
90
+ </span>
91
+ ))}
92
+ ]
93
+ </span>
94
+ );
95
+ if (value instanceof Error)
96
+ return (
97
+ <span className="text-red-300">
98
+ {value.name}: {value.message}
99
+ </span>
100
+ );
101
+ try {
102
+ const entries = Object.entries(value as Record<string, unknown>);
103
+ if (entries.length === 0) return <span>{"{}"}</span>;
104
+ if (depth > 4) return <span className="text-neutral-500">[...]</span>;
105
+ return (
106
+ <span>
107
+ {"{"}
108
+ {entries.map(([k, v], idx) => (
109
+ <span key={k}>
110
+ {idx > 0 ? ", " : " "}
111
+ <span className="text-blue-300">{k}</span>:{" "}
112
+ {formatValue(v, depth + 1)}
113
+ </span>
114
+ ))}
115
+ {" }"}
116
+ </span>
117
+ );
118
+ } catch {
119
+ return <span>{String(value)}</span>;
120
+ }
121
+ }
@@ -0,0 +1,15 @@
1
+ import type { IOptionToggle } from "../structures/IOptionToggle";
2
+
3
+ /** Default toggles for sites that wire the bundled typia / lint plugins. */
4
+ export const DEFAULT_OPTION_TOGGLES: readonly IOptionToggle[] = [
5
+ {
6
+ key: "typia",
7
+ label: "typia",
8
+ description: "Generate runtime validators from TypeScript types.",
9
+ },
10
+ {
11
+ key: "lint",
12
+ label: "@ttsc/lint",
13
+ description: "Report a subset of lint rules over the source AST.",
14
+ },
15
+ ];
@@ -0,0 +1,73 @@
1
+ "use client";
2
+
3
+ import type { IPlaygroundDependencyProgress } from "../structures/IPlaygroundDependencyProgress";
4
+
5
+ interface DependencyProgressModalProps {
6
+ progress: IPlaygroundDependencyProgress | null;
7
+ packages: readonly string[];
8
+ }
9
+
10
+ export function DependencyProgressModal({
11
+ progress,
12
+ packages,
13
+ }: DependencyProgressModalProps) {
14
+ if (!progress) return null;
15
+ const total = Math.max(progress.total, 1);
16
+ const ratio = Math.min(1, Math.max(0, progress.completed / total));
17
+ const activePackage =
18
+ progress.packageName && progress.version
19
+ ? `${progress.packageName}@${progress.version}`
20
+ : progress.packageName;
21
+
22
+ return (
23
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70 px-4">
24
+ <div className="w-full max-w-md rounded-lg border border-neutral-700 bg-neutral-950 p-5 shadow-2xl">
25
+ <div className="flex items-start justify-between gap-4">
26
+ <div>
27
+ <div className="text-[11px] font-mono uppercase text-blue-300">
28
+ Dependencies
29
+ </div>
30
+ <h2 className="mt-1 text-base font-mono text-white">
31
+ Installing npm packages
32
+ </h2>
33
+ </div>
34
+ <div className="text-[11px] font-mono text-neutral-500">
35
+ {progress.completed}/{total}
36
+ </div>
37
+ </div>
38
+
39
+ <div className="mt-4 h-2 overflow-hidden rounded-full bg-neutral-800">
40
+ <div
41
+ className="h-full bg-blue-400 transition-[width]"
42
+ style={{ width: `${Math.max(8, ratio * 100)}%` }}
43
+ />
44
+ </div>
45
+
46
+ <div className="mt-4 space-y-1 font-mono">
47
+ {activePackage && (
48
+ <div className="text-[12px] text-neutral-200">{activePackage}</div>
49
+ )}
50
+ <div className="text-[11px] text-neutral-400">{progress.message}</div>
51
+ </div>
52
+
53
+ {packages.length > 0 && (
54
+ <div className="mt-4 flex flex-wrap gap-1.5">
55
+ {packages.slice(0, 8).map((name) => (
56
+ <span
57
+ key={name}
58
+ className="rounded border border-neutral-800 bg-neutral-900 px-2 py-1 text-[10px] font-mono text-neutral-300"
59
+ >
60
+ {name}
61
+ </span>
62
+ ))}
63
+ {packages.length > 8 && (
64
+ <span className="rounded border border-neutral-800 bg-neutral-900 px-2 py-1 text-[10px] font-mono text-neutral-500">
65
+ +{packages.length - 8}
66
+ </span>
67
+ )}
68
+ </div>
69
+ )}
70
+ </div>
71
+ </div>
72
+ );
73
+ }
@@ -0,0 +1,74 @@
1
+ "use client";
2
+
3
+ import { useState } from "react";
4
+
5
+ import type { ICompilerService } from "../structures/ICompilerService";
6
+
7
+ export function DiagnosticsPanel({
8
+ diagnostics,
9
+ }: {
10
+ diagnostics: ICompilerService.IDiagnostic[];
11
+ }) {
12
+ const [expanded, setExpanded] = useState(false);
13
+ const errorCount = diagnostics.filter((d) => d.severity === "error").length;
14
+ const warnCount = diagnostics.filter((d) => d.severity === "warning").length;
15
+
16
+ if (diagnostics.length === 0)
17
+ return (
18
+ <div className="shrink-0 px-4 py-2 border-t border-neutral-800/70 bg-neutral-950 flex items-center gap-3">
19
+ <span className="text-emerald-400 text-xs">●</span>
20
+ <span className="text-[12px] font-mono text-neutral-400">
21
+ 0 errors · 0 warnings
22
+ </span>
23
+ </div>
24
+ );
25
+
26
+ return (
27
+ <div className="shrink-0 border-t border-neutral-800/70 bg-neutral-950">
28
+ <button
29
+ onClick={() => setExpanded((v) => !v)}
30
+ className="w-full px-4 py-2 flex items-center gap-3 hover:bg-neutral-900/50 transition-colors text-left"
31
+ >
32
+ <span
33
+ className={`text-xs ${
34
+ errorCount > 0 ? "text-red-400" : "text-yellow-400"
35
+ }`}
36
+ >
37
+
38
+ </span>
39
+ <span className="text-[12px] font-mono text-neutral-300">
40
+ {errorCount} error{errorCount === 1 ? "" : "s"} · {warnCount} warning
41
+ {warnCount === 1 ? "" : "s"}
42
+ </span>
43
+ <span className="ml-auto text-[10px] font-mono text-neutral-600">
44
+ {expanded ? "▲ collapse" : "▼ expand"}
45
+ </span>
46
+ </button>
47
+ {expanded && (
48
+ <div className="border-t border-neutral-800/70 max-h-48 overflow-auto">
49
+ {diagnostics.map((d, i) => (
50
+ <div
51
+ key={i}
52
+ className="px-4 py-2 flex gap-3 text-[12px] font-mono border-b border-neutral-900 last:border-b-0"
53
+ >
54
+ <span
55
+ className={`shrink-0 ${
56
+ d.severity === "error" ? "text-red-400" : "text-yellow-400"
57
+ }`}
58
+ >
59
+ {d.severity === "error" ? "✗" : "!"}
60
+ </span>
61
+ <span className="shrink-0 text-neutral-500 w-16">
62
+ {d.line}:{d.column}
63
+ </span>
64
+ <span className="shrink-0 text-neutral-600 w-16">
65
+ {d.code ?? ""}
66
+ </span>
67
+ <span className="text-neutral-200">{d.message}</span>
68
+ </div>
69
+ ))}
70
+ </div>
71
+ )}
72
+ </div>
73
+ );
74
+ }
@@ -0,0 +1,93 @@
1
+ "use client";
2
+
3
+ import { useEffect, useMemo, useRef, useState } from "react";
4
+
5
+ import type { IPlaygroundExample } from "../structures/IPlaygroundExample";
6
+
7
+ interface ExamplePickerProps {
8
+ examples: readonly IPlaygroundExample[];
9
+ onPick: (id: string) => void;
10
+ /** Display labels for groups. Maps `group` key → rendered heading. */
11
+ groupLabels?: Record<string, string>;
12
+ }
13
+
14
+ export function ExamplePicker({
15
+ examples,
16
+ onPick,
17
+ groupLabels,
18
+ }: ExamplePickerProps) {
19
+ const [open, setOpen] = useState(false);
20
+ const ref = useRef<HTMLDivElement>(null);
21
+
22
+ useEffect(() => {
23
+ if (!open) return;
24
+ const close = (e: MouseEvent) => {
25
+ if (ref.current && !ref.current.contains(e.target as Node)) {
26
+ setOpen(false);
27
+ }
28
+ };
29
+ const onKey = (e: KeyboardEvent) => {
30
+ if (e.key === "Escape") setOpen(false);
31
+ };
32
+ document.addEventListener("mousedown", close);
33
+ document.addEventListener("keydown", onKey);
34
+ return () => {
35
+ document.removeEventListener("mousedown", close);
36
+ document.removeEventListener("keydown", onKey);
37
+ };
38
+ }, [open]);
39
+
40
+ const grouped = useMemo(() => {
41
+ return examples.reduce<Record<string, IPlaygroundExample[]>>((acc, e) => {
42
+ const key = e.group ?? "Examples";
43
+ (acc[key] ??= []).push(e);
44
+ return acc;
45
+ }, {});
46
+ }, [examples]);
47
+
48
+ if (examples.length === 0) return null;
49
+
50
+ return (
51
+ <div ref={ref} className="relative">
52
+ <button
53
+ data-playground-examples-toggle
54
+ onClick={() => setOpen((v) => !v)}
55
+ className="px-3 py-1.5 text-xs font-mono text-neutral-300 border border-neutral-800 rounded-md hover:border-neutral-600 hover:bg-neutral-900 transition-colors"
56
+ title="Cmd/Ctrl+K"
57
+ >
58
+ Examples ▾
59
+ </button>
60
+ {open && (
61
+ <div className="absolute right-0 top-full mt-2 w-80 rounded-lg border border-neutral-800 bg-neutral-950 shadow-[0_10px_40px_rgba(0,0,0,0.6)] z-10 overflow-hidden">
62
+ {Object.entries(grouped).map(([group, items]) => (
63
+ <div
64
+ key={group}
65
+ className="border-b border-neutral-900 last:border-b-0"
66
+ >
67
+ <div className="px-3 py-1.5 text-[10px] font-mono uppercase tracking-wider text-neutral-600">
68
+ {groupLabels?.[group] ?? group}
69
+ </div>
70
+ {items.map((item) => (
71
+ <button
72
+ key={item.id}
73
+ onClick={() => {
74
+ onPick(item.id);
75
+ setOpen(false);
76
+ }}
77
+ className="w-full text-left px-3 py-2 hover:bg-neutral-900 transition-colors"
78
+ >
79
+ <div className="text-[12px] font-mono text-neutral-100">
80
+ {item.title}
81
+ </div>
82
+ <div className="text-[10px] text-neutral-500 mt-0.5 leading-snug">
83
+ {item.description}
84
+ </div>
85
+ </button>
86
+ ))}
87
+ </div>
88
+ ))}
89
+ </div>
90
+ )}
91
+ </div>
92
+ );
93
+ }
@@ -0,0 +1,58 @@
1
+ "use client";
2
+
3
+ import type { ICompilerService } from "../structures/ICompilerService";
4
+
5
+ /**
6
+ * Renders the lint plugin's findings in a list view. Shown by `PlaygroundShell`
7
+ * when the active tab is "lint". Empty state is the green checkmark.
8
+ */
9
+ export function LintPane({
10
+ diagnostics,
11
+ emptyHint = "Powered by @ttsc/lint inside playground.wasm.",
12
+ }: {
13
+ diagnostics: ICompilerService.IDiagnostic[];
14
+ emptyHint?: string;
15
+ }) {
16
+ if (diagnostics.length === 0)
17
+ return (
18
+ <div className="flex flex-col items-center justify-center h-full text-neutral-500 font-mono text-sm gap-2">
19
+ <span className="text-emerald-400 text-xl">✓</span>
20
+ <span>No lint diagnostics.</span>
21
+ <span className="text-[10px] text-neutral-600 max-w-xs text-center">
22
+ {emptyHint}
23
+ </span>
24
+ </div>
25
+ );
26
+ return (
27
+ <div className="overflow-auto h-full p-4 space-y-2">
28
+ {diagnostics.map((d, i) => (
29
+ <div
30
+ key={i}
31
+ className="flex gap-3 p-3 rounded-md bg-neutral-900/60 border border-neutral-800/80"
32
+ >
33
+ <span
34
+ className={`mt-0.5 text-[10px] font-mono px-1.5 py-0.5 rounded shrink-0 ${
35
+ d.severity === "error"
36
+ ? "text-red-300 bg-red-500/10"
37
+ : "text-yellow-300 bg-yellow-500/10"
38
+ }`}
39
+ >
40
+ {d.severity}
41
+ </span>
42
+ <div className="flex-1 min-w-0">
43
+ <div className="flex items-center gap-2 text-[11px] font-mono text-neutral-500 mb-1">
44
+ <span>{d.code}</span>
45
+ <span>·</span>
46
+ <span>
47
+ {d.line}:{d.column}
48
+ </span>
49
+ </div>
50
+ <div className="text-[13px] text-neutral-200 font-mono">
51
+ {d.message}
52
+ </div>
53
+ </div>
54
+ </div>
55
+ ))}
56
+ </div>
57
+ );
58
+ }
@@ -0,0 +1,132 @@
1
+ "use client";
2
+
3
+ import { useEffect, useRef } from "react";
4
+
5
+ import type { IOptionToggle } from "../structures/IOptionToggle";
6
+ import type { ITransformOptions } from "../structures/ITransformOptions";
7
+ import { DEFAULT_OPTION_TOGGLES } from "./DEFAULT_OPTION_TOGGLES";
8
+
9
+ interface OptionsPanelProps {
10
+ options: ITransformOptions;
11
+ onChange: (next: ITransformOptions) => void;
12
+ onClose: () => void;
13
+ /** Defaults to the typia + lint pair from `DEFAULT_OPTION_TOGGLES`. */
14
+ toggles?: readonly IOptionToggle[];
15
+ /** Dialog heading. Defaults to "Transform Options". */
16
+ title?: string;
17
+ }
18
+
19
+ export function OptionsPanel({
20
+ options,
21
+ onChange,
22
+ onClose,
23
+ toggles = DEFAULT_OPTION_TOGGLES,
24
+ title = "Transform Options",
25
+ }: OptionsPanelProps) {
26
+ const dialogRef = useRef<HTMLDivElement>(null);
27
+
28
+ const toggle = (key: string) =>
29
+ onChange({ ...options, [key]: !options[key] });
30
+
31
+ // Escape closes. Focus traps inside the dialog.
32
+ useEffect(() => {
33
+ const onKey = (e: KeyboardEvent) => {
34
+ if (e.key === "Escape") {
35
+ e.preventDefault();
36
+ onClose();
37
+ return;
38
+ }
39
+ if (e.key !== "Tab" || !dialogRef.current) return;
40
+ const focusable = dialogRef.current.querySelectorAll<HTMLElement>(
41
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
42
+ );
43
+ if (focusable.length === 0) return;
44
+ const first = focusable[0]!;
45
+ const last = focusable[focusable.length - 1]!;
46
+ if (e.shiftKey && document.activeElement === first) {
47
+ e.preventDefault();
48
+ last.focus();
49
+ } else if (!e.shiftKey && document.activeElement === last) {
50
+ e.preventDefault();
51
+ first.focus();
52
+ }
53
+ };
54
+ window.addEventListener("keydown", onKey);
55
+ // Auto-focus the first focusable on mount.
56
+ const focusable = dialogRef.current?.querySelector<HTMLElement>(
57
+ 'button, input, [tabindex]:not([tabindex="-1"])',
58
+ );
59
+ focusable?.focus();
60
+ return () => window.removeEventListener("keydown", onKey);
61
+ }, [onClose]);
62
+
63
+ return (
64
+ <div
65
+ className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm flex items-center justify-center"
66
+ onClick={onClose}
67
+ >
68
+ <div
69
+ ref={dialogRef}
70
+ role="dialog"
71
+ aria-modal="true"
72
+ aria-labelledby="playground-options-title"
73
+ className="w-[480px] max-w-[90vw] rounded-2xl border border-neutral-800 bg-neutral-950 shadow-[0_30px_80px_rgba(0,0,0,0.7)]"
74
+ onClick={(e) => e.stopPropagation()}
75
+ >
76
+ <div className="flex items-center justify-between px-6 py-4 border-b border-neutral-800/70">
77
+ <h2
78
+ id="playground-options-title"
79
+ className="text-base font-semibold text-white"
80
+ >
81
+ {title}
82
+ </h2>
83
+ <button
84
+ onClick={onClose}
85
+ className="text-neutral-500 hover:text-white transition-colors"
86
+ aria-label="Close options"
87
+ >
88
+
89
+ </button>
90
+ </div>
91
+ <div className="p-6 space-y-6">
92
+ <div>
93
+ <div className="text-[10px] font-mono uppercase tracking-wider text-neutral-600 mb-3">
94
+ Plugins
95
+ </div>
96
+ <div className="space-y-3">
97
+ {toggles.map((t) => (
98
+ <label
99
+ key={t.key}
100
+ className="flex items-start gap-3 cursor-pointer group"
101
+ >
102
+ <input
103
+ type="checkbox"
104
+ checked={!!options[t.key]}
105
+ onChange={() => toggle(t.key)}
106
+ className="mt-1 w-4 h-4 accent-blue-500"
107
+ />
108
+ <div className="flex-1">
109
+ <div className="text-sm font-mono text-neutral-100 group-hover:text-white transition-colors">
110
+ {t.label}
111
+ </div>
112
+ <div className="text-[11px] text-neutral-500 leading-snug">
113
+ {t.description}
114
+ </div>
115
+ </div>
116
+ </label>
117
+ ))}
118
+ </div>
119
+ </div>
120
+ </div>
121
+ <div className="px-6 py-4 border-t border-neutral-800/70 flex justify-end">
122
+ <button
123
+ onClick={onClose}
124
+ className="px-4 py-2 text-xs font-mono text-neutral-900 bg-white rounded-md hover:shadow-[0_0_30px_rgba(255,255,255,0.2)] transition-shadow"
125
+ >
126
+ Done
127
+ </button>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ );
132
+ }