@ttsc/playground 0.18.4 → 0.19.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/lib/src/compiler/createWorkerCompiler.js +7 -191
- package/lib/src/compiler/createWorkerCompiler.js.map +1 -1
- package/lib/src/compiler/internal/createWorkerCompilerService.d.ts +18 -0
- package/lib/src/compiler/internal/createWorkerCompilerService.js +272 -0
- package/lib/src/compiler/internal/createWorkerCompilerService.js.map +1 -0
- package/lib/src/npm/collectExternalPackageNames.js +253 -9
- package/lib/src/npm/collectExternalPackageNames.js.map +1 -1
- package/lib/src/react/ConsoleViewer.js +17 -17
- package/lib/src/react/ConsoleViewer.js.map +1 -1
- package/lib/src/react/DependencyProgressModal.js +1 -1
- package/lib/src/react/DependencyProgressModal.js.map +1 -1
- package/lib/src/react/DiagnosticsPanel.js +2 -2
- package/lib/src/react/DiagnosticsPanel.js.map +1 -1
- package/lib/src/react/ExamplePicker.js +2 -2
- package/lib/src/react/ExamplePicker.js.map +1 -1
- package/lib/src/react/LintPane.js +3 -3
- package/lib/src/react/LintPane.js.map +1 -1
- package/lib/src/react/OptionsPanel.js +1 -1
- package/lib/src/react/OptionsPanel.js.map +1 -1
- package/lib/src/react/PlaygroundShell.js +14 -13
- package/lib/src/react/PlaygroundShell.js.map +1 -1
- package/lib/src/react/ResultViewer.d.ts +2 -2
- package/lib/src/react/ResultViewer.js +3 -3
- package/lib/src/react/ResultViewer.js.map +1 -1
- package/lib/src/react/SourceEditor.js +1 -1
- package/lib/src/react/SourceEditor.js.map +1 -1
- package/lib/src/sandbox/createSandboxRequire.js +34 -9
- package/lib/src/sandbox/createSandboxRequire.js.map +1 -1
- package/package.json +3 -3
- package/src/compiler/createWorkerCompiler.ts +8 -236
- package/src/compiler/internal/createWorkerCompilerService.ts +358 -0
- package/src/npm/collectExternalPackageNames.ts +262 -9
- package/src/react/ConsoleViewer.tsx +18 -18
- package/src/react/DependencyProgressModal.tsx +11 -11
- package/src/react/DiagnosticsPanel.tsx +11 -11
- package/src/react/ExamplePicker.tsx +7 -7
- package/src/react/LintPane.tsx +5 -5
- package/src/react/OptionsPanel.tsx +10 -10
- package/src/react/PlaygroundShell.tsx +64 -74
- package/src/react/ResultViewer.tsx +4 -4
- package/src/react/SourceEditor.tsx +2 -3
- package/src/sandbox/createSandboxRequire.ts +32 -9
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { BUILT_IN_PLAYGROUND_PACKAGES } from "./BUILT_IN_PLAYGROUND_PACKAGES";
|
|
2
2
|
import { packageNameFromSpecifier } from "./packageNameFromSpecifier";
|
|
3
3
|
|
|
4
|
-
const MODULE_SPECIFIER_REGEXP =
|
|
5
|
-
/\b(?:import|export)\s+(?:type\s+)?(?:[^"'()]*?\s+from\s*)?["']([^"']+)["']|import\s*\(\s*["']([^"']+)["']\s*\)|require\s*\(\s*["']([^"']+)["']\s*\)/g;
|
|
6
|
-
|
|
7
4
|
/**
|
|
8
5
|
* Scan `source` for `import` / `require` specifiers and return the unique
|
|
9
6
|
* sorted list of bare npm package names that are not in `ignoredPackages`.
|
|
@@ -21,14 +18,270 @@ export function collectExternalPackageNames(
|
|
|
21
18
|
return [...found].sort();
|
|
22
19
|
}
|
|
23
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Collect the string specifiers of every executable module-loading construct in
|
|
23
|
+
* `source`: `import`/`export ... from`, side-effect `import "x"`, dynamic
|
|
24
|
+
* `import("x")`, and `require("x")` calls.
|
|
25
|
+
*
|
|
26
|
+
* The scan tokenizes `source` first so import/export/require lookalikes that
|
|
27
|
+
* live inside comments, string or template contents, or regular-expression
|
|
28
|
+
* literals never become package requests — only real code-level string
|
|
29
|
+
* arguments are returned. Specifiers that cannot be resolved statically (a
|
|
30
|
+
* template-literal or computed argument) are intentionally skipped so inert
|
|
31
|
+
* text cannot drive a network install.
|
|
32
|
+
*/
|
|
24
33
|
function collectModuleSpecifiers(source: string): string[] {
|
|
34
|
+
const tokens = tokenize(source);
|
|
25
35
|
const out: string[] = [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
36
|
+
const asString = (token: Token | undefined): string | null =>
|
|
37
|
+
token && token.kind === "string" ? token.value : null;
|
|
38
|
+
const isOpenParen = (token: Token | undefined): boolean =>
|
|
39
|
+
token !== undefined && token.kind === "punct" && token.value === "(";
|
|
40
|
+
const isMemberAccess = (token: Token | undefined): boolean =>
|
|
41
|
+
token !== undefined && token.kind === "punct" && token.value === ".";
|
|
42
|
+
|
|
43
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
44
|
+
const token = tokens[i];
|
|
45
|
+
if (!token || token.kind !== "word") continue;
|
|
46
|
+
|
|
47
|
+
if (token.value === "require") {
|
|
48
|
+
// `obj.require(...)` is an unrelated method call, not CommonJS require.
|
|
49
|
+
if (isMemberAccess(tokens[i - 1])) continue;
|
|
50
|
+
if (isOpenParen(tokens[i + 1])) {
|
|
51
|
+
const spec = asString(tokens[i + 2]);
|
|
52
|
+
if (spec !== null) out.push(spec);
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (token.value === "import" || token.value === "export") {
|
|
58
|
+
// `foo.import(...)` / `import.meta` are not module-loading imports.
|
|
59
|
+
if (token.value === "import" && isMemberAccess(tokens[i - 1])) continue;
|
|
60
|
+
// Dynamic `import("x")`.
|
|
61
|
+
if (token.value === "import" && isOpenParen(tokens[i + 1])) {
|
|
62
|
+
const spec = asString(tokens[i + 2]);
|
|
63
|
+
if (spec !== null) out.push(spec);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Side-effect `import "x"`.
|
|
67
|
+
if (token.value === "import") {
|
|
68
|
+
const bare = asString(tokens[i + 1]);
|
|
69
|
+
if (bare !== null) {
|
|
70
|
+
out.push(bare);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// `import ... from "x"` / `export ... from "x"`.
|
|
75
|
+
const spec = findFromSpecifier(tokens, i + 1);
|
|
76
|
+
if (spec !== null) out.push(spec);
|
|
77
|
+
}
|
|
32
78
|
}
|
|
33
79
|
return out;
|
|
34
80
|
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* From token index `start`, find the specifier of a `... from "x"` clause,
|
|
84
|
+
* bounded to the current statement. Stops at a `;` terminator or the start of
|
|
85
|
+
* another `import`/`export` so a local `export const x = ...` never borrows a
|
|
86
|
+
* later statement's `from`.
|
|
87
|
+
*/
|
|
88
|
+
function findFromSpecifier(tokens: Token[], start: number): string | null {
|
|
89
|
+
for (let i = start; i < tokens.length; i++) {
|
|
90
|
+
const token = tokens[i];
|
|
91
|
+
if (!token) break;
|
|
92
|
+
if (token.kind === "punct" && token.value === ";") return null;
|
|
93
|
+
if (
|
|
94
|
+
token.kind === "word" &&
|
|
95
|
+
(token.value === "import" || token.value === "export")
|
|
96
|
+
)
|
|
97
|
+
return null;
|
|
98
|
+
if (token.kind === "word" && token.value === "from") {
|
|
99
|
+
const next = tokens[i + 1];
|
|
100
|
+
return next && next.kind === "string" ? next.value : null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
type Token =
|
|
107
|
+
// An identifier or keyword.
|
|
108
|
+
| { kind: "word"; value: string }
|
|
109
|
+
// A single- or double-quoted string literal, with escapes decoded to their
|
|
110
|
+
// literal characters so a specifier survives unchanged.
|
|
111
|
+
| { kind: "string"; value: string }
|
|
112
|
+
// A single punctuation character.
|
|
113
|
+
| { kind: "punct"; value: string }
|
|
114
|
+
// An opaque value token — number, template literal, or regular-expression
|
|
115
|
+
// literal — whose contents can never be a static specifier.
|
|
116
|
+
| { kind: "other" };
|
|
117
|
+
|
|
118
|
+
// Keywords after which a `/` begins a regular-expression literal rather than a
|
|
119
|
+
// division operator. After any other word (an identifier or value keyword such
|
|
120
|
+
// as `this`), `/` is division.
|
|
121
|
+
const REGEX_PRECEDING_KEYWORDS = new Set([
|
|
122
|
+
"return",
|
|
123
|
+
"typeof",
|
|
124
|
+
"instanceof",
|
|
125
|
+
"in",
|
|
126
|
+
"of",
|
|
127
|
+
"new",
|
|
128
|
+
"delete",
|
|
129
|
+
"void",
|
|
130
|
+
"do",
|
|
131
|
+
"else",
|
|
132
|
+
"yield",
|
|
133
|
+
"await",
|
|
134
|
+
"case",
|
|
135
|
+
"throw",
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Lexically tokenize `source` into the coarse token stream the specifier
|
|
140
|
+
* collector needs. Comments are dropped; strings, templates, regex literals,
|
|
141
|
+
* and numbers become single tokens so their contents cannot leak into the
|
|
142
|
+
* grammar match.
|
|
143
|
+
*/
|
|
144
|
+
function tokenize(source: string): Token[] {
|
|
145
|
+
const tokens: Token[] = [];
|
|
146
|
+
const n = source.length;
|
|
147
|
+
const isIdStart = (c: string): boolean =>
|
|
148
|
+
(c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c === "_" || c === "$";
|
|
149
|
+
const isIdPart = (c: string): boolean =>
|
|
150
|
+
isIdStart(c) || (c >= "0" && c <= "9");
|
|
151
|
+
const isDigit = (c: string): boolean => c >= "0" && c <= "9";
|
|
152
|
+
|
|
153
|
+
// A `/` opens a regex only in operator/statement position — never right after
|
|
154
|
+
// a value (identifier, number, string, template, regex, `)` or `]`).
|
|
155
|
+
const regexAllowed = (): boolean => {
|
|
156
|
+
const prev = tokens[tokens.length - 1];
|
|
157
|
+
if (!prev) return true;
|
|
158
|
+
if (prev.kind === "string" || prev.kind === "other") return false;
|
|
159
|
+
if (prev.kind === "word") return REGEX_PRECEDING_KEYWORDS.has(prev.value);
|
|
160
|
+
return prev.value !== ")" && prev.value !== "]";
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
let i = 0;
|
|
164
|
+
while (i < n) {
|
|
165
|
+
const c = source[i]!;
|
|
166
|
+
// Whitespace.
|
|
167
|
+
if (
|
|
168
|
+
c === " " ||
|
|
169
|
+
c === "\t" ||
|
|
170
|
+
c === "\r" ||
|
|
171
|
+
c === "\n" ||
|
|
172
|
+
c === "\f" ||
|
|
173
|
+
c === "\v"
|
|
174
|
+
) {
|
|
175
|
+
i++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
// Line comment.
|
|
179
|
+
if (c === "/" && source[i + 1] === "/") {
|
|
180
|
+
i += 2;
|
|
181
|
+
while (i < n && source[i] !== "\n") i++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
// Block comment.
|
|
185
|
+
if (c === "/" && source[i + 1] === "*") {
|
|
186
|
+
i += 2;
|
|
187
|
+
while (i < n && !(source[i] === "*" && source[i + 1] === "/")) i++;
|
|
188
|
+
i += 2;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
// Regular-expression literal.
|
|
192
|
+
if (c === "/" && regexAllowed()) {
|
|
193
|
+
i++;
|
|
194
|
+
let inClass = false;
|
|
195
|
+
while (i < n) {
|
|
196
|
+
const d = source[i];
|
|
197
|
+
if (d === "\\") {
|
|
198
|
+
i += 2;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (d === "\n") break;
|
|
202
|
+
if (d === "[") inClass = true;
|
|
203
|
+
else if (d === "]") inClass = false;
|
|
204
|
+
else if (d === "/" && !inClass) {
|
|
205
|
+
i++;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
i++;
|
|
209
|
+
}
|
|
210
|
+
while (i < n && isIdPart(source[i]!)) i++; // flags
|
|
211
|
+
tokens.push({ kind: "other" });
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
// String literal.
|
|
215
|
+
if (c === '"' || c === "'") {
|
|
216
|
+
i++;
|
|
217
|
+
let value = "";
|
|
218
|
+
while (i < n) {
|
|
219
|
+
const d = source[i]!;
|
|
220
|
+
if (d === "\\") {
|
|
221
|
+
value += source[i + 1] ?? "";
|
|
222
|
+
i += 2;
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (d === c) {
|
|
226
|
+
i++;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
if (d === "\n") break; // unterminated single-line string
|
|
230
|
+
value += d;
|
|
231
|
+
i++;
|
|
232
|
+
}
|
|
233
|
+
tokens.push({ kind: "string", value });
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// Template literal. Contents (including `${...}` expressions) are treated as
|
|
237
|
+
// opaque: a template specifier is not statically resolvable.
|
|
238
|
+
if (c === "`") {
|
|
239
|
+
i++;
|
|
240
|
+
let depth = 0;
|
|
241
|
+
while (i < n) {
|
|
242
|
+
const d = source[i];
|
|
243
|
+
if (d === "\\") {
|
|
244
|
+
i += 2;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
if (depth === 0 && d === "`") {
|
|
248
|
+
i++;
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if (d === "$" && source[i + 1] === "{") {
|
|
252
|
+
depth++;
|
|
253
|
+
i += 2;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (depth > 0 && d === "}") {
|
|
257
|
+
depth--;
|
|
258
|
+
i++;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
i++;
|
|
262
|
+
}
|
|
263
|
+
tokens.push({ kind: "other" });
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
// Identifier / keyword.
|
|
267
|
+
if (isIdStart(c)) {
|
|
268
|
+
let j = i + 1;
|
|
269
|
+
while (j < n && isIdPart(source[j]!)) j++;
|
|
270
|
+
tokens.push({ kind: "word", value: source.slice(i, j) });
|
|
271
|
+
i = j;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
// Numeric literal.
|
|
275
|
+
if (isDigit(c) || (c === "." && isDigit(source[i + 1] ?? ""))) {
|
|
276
|
+
let j = i + 1;
|
|
277
|
+
while (j < n && /[0-9a-fA-FxXoObBeE._]/.test(source[j]!)) j++;
|
|
278
|
+
tokens.push({ kind: "other" });
|
|
279
|
+
i = j;
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
// Single punctuation character.
|
|
283
|
+
tokens.push({ kind: "punct", value: c });
|
|
284
|
+
i++;
|
|
285
|
+
}
|
|
286
|
+
return tokens;
|
|
287
|
+
}
|
|
@@ -15,7 +15,7 @@ export function ConsoleViewer({
|
|
|
15
15
|
}: ConsoleViewerProps) {
|
|
16
16
|
if (messages.length === 0)
|
|
17
17
|
return (
|
|
18
|
-
<div className="h-full w-full
|
|
18
|
+
<div className="flex h-full w-full items-center justify-center px-4 text-center font-mono text-[11px] text-slate-400">
|
|
19
19
|
{empty}
|
|
20
20
|
</div>
|
|
21
21
|
);
|
|
@@ -24,7 +24,7 @@ export function ConsoleViewer({
|
|
|
24
24
|
{messages.map((msg, i) => (
|
|
25
25
|
<div
|
|
26
26
|
key={i}
|
|
27
|
-
className="
|
|
27
|
+
className="flex gap-2 border-b border-[#d8e7f4] py-1 last:border-b-0"
|
|
28
28
|
>
|
|
29
29
|
<span
|
|
30
30
|
className={`shrink-0 text-[10px] uppercase tracking-wider w-12 ${typeColor(
|
|
@@ -33,7 +33,7 @@ export function ConsoleViewer({
|
|
|
33
33
|
>
|
|
34
34
|
{msg.type}
|
|
35
35
|
</span>
|
|
36
|
-
<span className="flex-1
|
|
36
|
+
<span className="flex-1 whitespace-pre-wrap break-words text-slate-700">
|
|
37
37
|
{(Array.isArray(msg.value) ? msg.value : [msg.value]).map(
|
|
38
38
|
(arg, idx) => (
|
|
39
39
|
<span key={idx}>
|
|
@@ -52,33 +52,33 @@ export function ConsoleViewer({
|
|
|
52
52
|
function typeColor(type: IConsoleMessage["type"]): string {
|
|
53
53
|
switch (type) {
|
|
54
54
|
case "error":
|
|
55
|
-
return "text-red-
|
|
55
|
+
return "text-red-600";
|
|
56
56
|
case "warn":
|
|
57
|
-
return "text-
|
|
57
|
+
return "text-amber-600";
|
|
58
58
|
case "info":
|
|
59
|
-
return "text-sky-
|
|
59
|
+
return "text-sky-700";
|
|
60
60
|
case "debug":
|
|
61
|
-
return "text-fuchsia-
|
|
61
|
+
return "text-fuchsia-700";
|
|
62
62
|
case "dir":
|
|
63
63
|
case "table":
|
|
64
|
-
return "text-cyan-
|
|
64
|
+
return "text-cyan-700";
|
|
65
65
|
default:
|
|
66
|
-
return "text-emerald-
|
|
66
|
+
return "text-emerald-700";
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function formatValue(value: unknown, depth = 0): JSX.Element {
|
|
71
71
|
if (typeof value === "string")
|
|
72
|
-
return <span className="text-amber-
|
|
72
|
+
return <span className="text-amber-700">{JSON.stringify(value)}</span>;
|
|
73
73
|
if (typeof value === "number")
|
|
74
|
-
return <span className="text-purple-
|
|
74
|
+
return <span className="text-purple-700">{String(value)}</span>;
|
|
75
75
|
if (typeof value === "boolean")
|
|
76
|
-
return <span className="text-sky-
|
|
77
|
-
if (value === null) return <span className="text-
|
|
76
|
+
return <span className="text-sky-700">{String(value)}</span>;
|
|
77
|
+
if (value === null) return <span className="text-slate-500">null</span>;
|
|
78
78
|
if (value === undefined)
|
|
79
|
-
return <span className="text-
|
|
79
|
+
return <span className="text-slate-500">undefined</span>;
|
|
80
80
|
if (typeof value === "function")
|
|
81
|
-
return <span className="text-
|
|
81
|
+
return <span className="text-slate-500">[Function]</span>;
|
|
82
82
|
if (Array.isArray(value))
|
|
83
83
|
return (
|
|
84
84
|
<span>
|
|
@@ -94,21 +94,21 @@ function formatValue(value: unknown, depth = 0): JSX.Element {
|
|
|
94
94
|
);
|
|
95
95
|
if (value instanceof Error)
|
|
96
96
|
return (
|
|
97
|
-
<span className="text-red-
|
|
97
|
+
<span className="text-red-700">
|
|
98
98
|
{value.name}: {value.message}
|
|
99
99
|
</span>
|
|
100
100
|
);
|
|
101
101
|
try {
|
|
102
102
|
const entries = Object.entries(value as Record<string, unknown>);
|
|
103
103
|
if (entries.length === 0) return <span>{"{}"}</span>;
|
|
104
|
-
if (depth > 4) return <span className="text-
|
|
104
|
+
if (depth > 4) return <span className="text-slate-500">[...]</span>;
|
|
105
105
|
return (
|
|
106
106
|
<span>
|
|
107
107
|
{"{"}
|
|
108
108
|
{entries.map(([k, v], idx) => (
|
|
109
109
|
<span key={k}>
|
|
110
110
|
{idx > 0 ? ", " : " "}
|
|
111
|
-
<span className="text-
|
|
111
|
+
<span className="text-[#3178c6]">{k}</span>:{" "}
|
|
112
112
|
{formatValue(v, depth + 1)}
|
|
113
113
|
</span>
|
|
114
114
|
))}
|
|
@@ -20,34 +20,34 @@ export function DependencyProgressModal({
|
|
|
20
20
|
: progress.packageName;
|
|
21
21
|
|
|
22
22
|
return (
|
|
23
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-
|
|
24
|
-
<div className="w-full max-w-md rounded-
|
|
23
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[#102a43]/35 px-4 backdrop-blur-sm">
|
|
24
|
+
<div className="w-full max-w-md rounded-2xl border border-[#b9d5ee] bg-white p-5 shadow-[0_28px_70px_rgba(35,90,151,0.25)]">
|
|
25
25
|
<div className="flex items-start justify-between gap-4">
|
|
26
26
|
<div>
|
|
27
|
-
<div className="text-[11px]
|
|
27
|
+
<div className="font-mono text-[11px] uppercase text-[#3178c6]">
|
|
28
28
|
Dependencies
|
|
29
29
|
</div>
|
|
30
|
-
<h2 className="mt-1
|
|
30
|
+
<h2 className="mt-1 font-mono text-base text-[#102a43]">
|
|
31
31
|
Installing npm packages
|
|
32
32
|
</h2>
|
|
33
33
|
</div>
|
|
34
|
-
<div className="text-[11px]
|
|
34
|
+
<div className="font-mono text-[11px] text-slate-500">
|
|
35
35
|
{progress.completed}/{total}
|
|
36
36
|
</div>
|
|
37
37
|
</div>
|
|
38
38
|
|
|
39
|
-
<div className="mt-4 h-2 overflow-hidden rounded-full bg-
|
|
39
|
+
<div className="mt-4 h-2 overflow-hidden rounded-full bg-[#e7f0f8]">
|
|
40
40
|
<div
|
|
41
|
-
className="h-full bg-
|
|
41
|
+
className="h-full bg-[#3178c6] transition-[width]"
|
|
42
42
|
style={{ width: `${Math.max(8, ratio * 100)}%` }}
|
|
43
43
|
/>
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
46
|
<div className="mt-4 space-y-1 font-mono">
|
|
47
47
|
{activePackage && (
|
|
48
|
-
<div className="text-[12px] text-
|
|
48
|
+
<div className="text-[12px] text-slate-700">{activePackage}</div>
|
|
49
49
|
)}
|
|
50
|
-
<div className="text-[11px] text-
|
|
50
|
+
<div className="text-[11px] text-slate-500">{progress.message}</div>
|
|
51
51
|
</div>
|
|
52
52
|
|
|
53
53
|
{packages.length > 0 && (
|
|
@@ -55,13 +55,13 @@ export function DependencyProgressModal({
|
|
|
55
55
|
{packages.slice(0, 8).map((name) => (
|
|
56
56
|
<span
|
|
57
57
|
key={name}
|
|
58
|
-
className="rounded border border-
|
|
58
|
+
className="rounded border border-[#d2e4f4] bg-[#f7fbff] px-2 py-1 font-mono text-[10px] text-slate-600"
|
|
59
59
|
>
|
|
60
60
|
{name}
|
|
61
61
|
</span>
|
|
62
62
|
))}
|
|
63
63
|
{packages.length > 8 && (
|
|
64
|
-
<span className="rounded border border-
|
|
64
|
+
<span className="rounded border border-[#d2e4f4] bg-[#f7fbff] px-2 py-1 font-mono text-[10px] text-slate-500">
|
|
65
65
|
+{packages.length - 8}
|
|
66
66
|
</span>
|
|
67
67
|
)}
|
|
@@ -15,19 +15,19 @@ export function DiagnosticsPanel({
|
|
|
15
15
|
|
|
16
16
|
if (diagnostics.length === 0)
|
|
17
17
|
return (
|
|
18
|
-
<div className="shrink-0
|
|
18
|
+
<div className="flex shrink-0 items-center gap-3 border-t border-[#c7dff4] bg-[#eef6ff] px-4 py-2">
|
|
19
19
|
<span className="text-emerald-400 text-xs">●</span>
|
|
20
|
-
<span className="text-[12px]
|
|
20
|
+
<span className="font-mono text-[12px] text-slate-600">
|
|
21
21
|
0 errors · 0 warnings
|
|
22
22
|
</span>
|
|
23
23
|
</div>
|
|
24
24
|
);
|
|
25
25
|
|
|
26
26
|
return (
|
|
27
|
-
<div className="shrink-0 border-t border-
|
|
27
|
+
<div className="shrink-0 border-t border-[#c7dff4] bg-[#eef6ff]">
|
|
28
28
|
<button
|
|
29
29
|
onClick={() => setExpanded((v) => !v)}
|
|
30
|
-
className="w-full px-4 py-2
|
|
30
|
+
className="flex w-full items-center gap-3 px-4 py-2 text-left transition-colors hover:bg-[#e1effc]"
|
|
31
31
|
>
|
|
32
32
|
<span
|
|
33
33
|
className={`text-xs ${
|
|
@@ -36,20 +36,20 @@ export function DiagnosticsPanel({
|
|
|
36
36
|
>
|
|
37
37
|
●
|
|
38
38
|
</span>
|
|
39
|
-
<span className="text-[12px]
|
|
39
|
+
<span className="font-mono text-[12px] text-slate-700">
|
|
40
40
|
{errorCount} error{errorCount === 1 ? "" : "s"} · {warnCount} warning
|
|
41
41
|
{warnCount === 1 ? "" : "s"}
|
|
42
42
|
</span>
|
|
43
|
-
<span className="ml-auto text-[10px]
|
|
43
|
+
<span className="ml-auto font-mono text-[10px] text-slate-400">
|
|
44
44
|
{expanded ? "▲ collapse" : "▼ expand"}
|
|
45
45
|
</span>
|
|
46
46
|
</button>
|
|
47
47
|
{expanded && (
|
|
48
|
-
<div className="
|
|
48
|
+
<div className="max-h-48 overflow-auto border-t border-[#c7dff4]">
|
|
49
49
|
{diagnostics.map((d, i) => (
|
|
50
50
|
<div
|
|
51
51
|
key={i}
|
|
52
|
-
className="
|
|
52
|
+
className="flex gap-3 border-b border-[#d8e7f4] px-4 py-2 font-mono text-[12px] last:border-b-0"
|
|
53
53
|
>
|
|
54
54
|
<span
|
|
55
55
|
className={`shrink-0 ${
|
|
@@ -58,13 +58,13 @@ export function DiagnosticsPanel({
|
|
|
58
58
|
>
|
|
59
59
|
{d.severity === "error" ? "✗" : "!"}
|
|
60
60
|
</span>
|
|
61
|
-
<span className="shrink-0 text-
|
|
61
|
+
<span className="w-16 shrink-0 text-slate-500">
|
|
62
62
|
{d.line}:{d.column}
|
|
63
63
|
</span>
|
|
64
|
-
<span className="shrink-0 text-
|
|
64
|
+
<span className="w-16 shrink-0 text-slate-400">
|
|
65
65
|
{d.code ?? ""}
|
|
66
66
|
</span>
|
|
67
|
-
<span className="text-
|
|
67
|
+
<span className="text-slate-700">{d.message}</span>
|
|
68
68
|
</div>
|
|
69
69
|
))}
|
|
70
70
|
</div>
|
|
@@ -52,19 +52,19 @@ export function ExamplePicker({
|
|
|
52
52
|
<button
|
|
53
53
|
data-playground-examples-toggle
|
|
54
54
|
onClick={() => setOpen((v) => !v)}
|
|
55
|
-
className="px-3 py-1.5
|
|
55
|
+
className="rounded-md border border-[#b9d5ee] bg-white px-3 py-1.5 font-mono text-xs text-[#235a97] transition-colors hover:border-[#3178c6] hover:bg-[#eaf4ff]"
|
|
56
56
|
title="Cmd/Ctrl+K"
|
|
57
57
|
>
|
|
58
58
|
Examples ▾
|
|
59
59
|
</button>
|
|
60
60
|
{open && (
|
|
61
|
-
<div className="absolute right-0 top-full mt-2 w-80 rounded-
|
|
61
|
+
<div className="absolute right-0 top-full z-10 mt-2 w-80 overflow-hidden rounded-xl border border-[#b9d5ee] bg-white shadow-[0_14px_42px_rgba(49,120,198,0.18)]">
|
|
62
62
|
{Object.entries(grouped).map(([group, items]) => (
|
|
63
63
|
<div
|
|
64
64
|
key={group}
|
|
65
|
-
className="border-b border-
|
|
65
|
+
className="border-b border-[#d8e7f4] last:border-b-0"
|
|
66
66
|
>
|
|
67
|
-
<div className="px-3 py-1.5 text-[10px]
|
|
67
|
+
<div className="bg-[#f7fbff] px-3 py-1.5 font-mono text-[10px] uppercase tracking-wider text-slate-500">
|
|
68
68
|
{groupLabels?.[group] ?? group}
|
|
69
69
|
</div>
|
|
70
70
|
{items.map((item) => (
|
|
@@ -74,12 +74,12 @@ export function ExamplePicker({
|
|
|
74
74
|
onPick(item.id);
|
|
75
75
|
setOpen(false);
|
|
76
76
|
}}
|
|
77
|
-
className="w-full
|
|
77
|
+
className="w-full px-3 py-2 text-left transition-colors hover:bg-[#eaf4ff]"
|
|
78
78
|
>
|
|
79
|
-
<div className="text-[12px]
|
|
79
|
+
<div className="font-mono text-[12px] text-[#102a43]">
|
|
80
80
|
{item.title}
|
|
81
81
|
</div>
|
|
82
|
-
<div className="text-[10px] text-
|
|
82
|
+
<div className="mt-0.5 text-[10px] leading-snug text-slate-500">
|
|
83
83
|
{item.description}
|
|
84
84
|
</div>
|
|
85
85
|
</button>
|
package/src/react/LintPane.tsx
CHANGED
|
@@ -15,10 +15,10 @@ export function LintPane({
|
|
|
15
15
|
}) {
|
|
16
16
|
if (diagnostics.length === 0)
|
|
17
17
|
return (
|
|
18
|
-
<div className="flex flex-col items-center justify-center
|
|
18
|
+
<div className="flex h-full flex-col items-center justify-center gap-2 font-mono text-sm text-slate-500">
|
|
19
19
|
<span className="text-emerald-400 text-xl">✓</span>
|
|
20
20
|
<span>No lint diagnostics.</span>
|
|
21
|
-
<span className="text-[10px] text-
|
|
21
|
+
<span className="max-w-xs text-center text-[10px] text-slate-400">
|
|
22
22
|
{emptyHint}
|
|
23
23
|
</span>
|
|
24
24
|
</div>
|
|
@@ -28,7 +28,7 @@ export function LintPane({
|
|
|
28
28
|
{diagnostics.map((d, i) => (
|
|
29
29
|
<div
|
|
30
30
|
key={i}
|
|
31
|
-
className="flex gap-3
|
|
31
|
+
className="flex gap-3 rounded-lg border border-[#d2e4f4] bg-[#f7fbff] p-3"
|
|
32
32
|
>
|
|
33
33
|
<span
|
|
34
34
|
className={`mt-0.5 text-[10px] font-mono px-1.5 py-0.5 rounded shrink-0 ${
|
|
@@ -40,14 +40,14 @@ export function LintPane({
|
|
|
40
40
|
{d.severity}
|
|
41
41
|
</span>
|
|
42
42
|
<div className="flex-1 min-w-0">
|
|
43
|
-
<div className="flex items-center gap-2 text-[11px]
|
|
43
|
+
<div className="mb-1 flex items-center gap-2 font-mono text-[11px] text-slate-500">
|
|
44
44
|
<span>{d.code}</span>
|
|
45
45
|
<span>·</span>
|
|
46
46
|
<span>
|
|
47
47
|
{d.line}:{d.column}
|
|
48
48
|
</span>
|
|
49
49
|
</div>
|
|
50
|
-
<div className="text-[13px] text-
|
|
50
|
+
<div className="font-mono text-[13px] text-slate-700">
|
|
51
51
|
{d.message}
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
@@ -62,7 +62,7 @@ export function OptionsPanel({
|
|
|
62
62
|
|
|
63
63
|
return (
|
|
64
64
|
<div
|
|
65
|
-
className="fixed inset-0 z-50 bg-
|
|
65
|
+
className="fixed inset-0 z-50 flex items-center justify-center bg-[#102a43]/35 backdrop-blur-sm"
|
|
66
66
|
onClick={onClose}
|
|
67
67
|
>
|
|
68
68
|
<div
|
|
@@ -70,19 +70,19 @@ export function OptionsPanel({
|
|
|
70
70
|
role="dialog"
|
|
71
71
|
aria-modal="true"
|
|
72
72
|
aria-labelledby="playground-options-title"
|
|
73
|
-
className="w-[480px] max-w-[90vw] rounded-2xl border border-
|
|
73
|
+
className="w-[480px] max-w-[90vw] rounded-2xl border border-[#b9d5ee] bg-white shadow-[0_30px_80px_rgba(35,90,151,0.25)]"
|
|
74
74
|
onClick={(e) => e.stopPropagation()}
|
|
75
75
|
>
|
|
76
|
-
<div className="flex items-center justify-between
|
|
76
|
+
<div className="flex items-center justify-between border-b border-[#c7dff4] bg-[#f7fbff] px-6 py-4">
|
|
77
77
|
<h2
|
|
78
78
|
id="playground-options-title"
|
|
79
|
-
className="text-base font-semibold text-
|
|
79
|
+
className="text-base font-semibold text-[#102a43]"
|
|
80
80
|
>
|
|
81
81
|
{title}
|
|
82
82
|
</h2>
|
|
83
83
|
<button
|
|
84
84
|
onClick={onClose}
|
|
85
|
-
className="text-
|
|
85
|
+
className="text-slate-400 transition-colors hover:text-[#235a97]"
|
|
86
86
|
aria-label="Close options"
|
|
87
87
|
>
|
|
88
88
|
✕
|
|
@@ -90,7 +90,7 @@ export function OptionsPanel({
|
|
|
90
90
|
</div>
|
|
91
91
|
<div className="p-6 space-y-6">
|
|
92
92
|
<div>
|
|
93
|
-
<div className="text-[10px]
|
|
93
|
+
<div className="mb-3 font-mono text-[10px] uppercase tracking-wider text-slate-500">
|
|
94
94
|
Plugins
|
|
95
95
|
</div>
|
|
96
96
|
<div className="space-y-3">
|
|
@@ -106,10 +106,10 @@ export function OptionsPanel({
|
|
|
106
106
|
className="mt-1 w-4 h-4 accent-blue-500"
|
|
107
107
|
/>
|
|
108
108
|
<div className="flex-1">
|
|
109
|
-
<div className="
|
|
109
|
+
<div className="font-mono text-sm text-slate-800 transition-colors group-hover:text-[#235a97]">
|
|
110
110
|
{t.label}
|
|
111
111
|
</div>
|
|
112
|
-
<div className="text-[11px] text-
|
|
112
|
+
<div className="text-[11px] leading-snug text-slate-500">
|
|
113
113
|
{t.description}
|
|
114
114
|
</div>
|
|
115
115
|
</div>
|
|
@@ -118,10 +118,10 @@ export function OptionsPanel({
|
|
|
118
118
|
</div>
|
|
119
119
|
</div>
|
|
120
120
|
</div>
|
|
121
|
-
<div className="
|
|
121
|
+
<div className="flex justify-end border-t border-[#c7dff4] bg-[#f7fbff] px-6 py-4">
|
|
122
122
|
<button
|
|
123
123
|
onClick={onClose}
|
|
124
|
-
className="px-4 py-2
|
|
124
|
+
className="rounded-md bg-[#3178c6] px-4 py-2 font-mono text-xs text-white transition-colors hover:bg-[#235a97]"
|
|
125
125
|
>
|
|
126
126
|
Done
|
|
127
127
|
</button>
|