tjs-lang 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.
- package/CONTEXT.md +594 -0
- package/LICENSE +190 -0
- package/README.md +220 -0
- package/bin/benchmarks.ts +351 -0
- package/bin/dev.ts +205 -0
- package/bin/docs.js +170 -0
- package/bin/install-cursor.sh +71 -0
- package/bin/install-vscode.sh +71 -0
- package/bin/select-local-models.d.ts +1 -0
- package/bin/select-local-models.js +28 -0
- package/bin/select-local-models.ts +31 -0
- package/demo/autocomplete.test.ts +232 -0
- package/demo/docs.json +186 -0
- package/demo/examples.test.ts +598 -0
- package/demo/index.html +91 -0
- package/demo/src/autocomplete.ts +482 -0
- package/demo/src/capabilities.ts +859 -0
- package/demo/src/demo-nav.ts +2097 -0
- package/demo/src/examples.test.ts +161 -0
- package/demo/src/examples.ts +476 -0
- package/demo/src/imports.test.ts +196 -0
- package/demo/src/imports.ts +421 -0
- package/demo/src/index.ts +639 -0
- package/demo/src/module-store.ts +635 -0
- package/demo/src/module-sw.ts +132 -0
- package/demo/src/playground.ts +949 -0
- package/demo/src/service-host.ts +389 -0
- package/demo/src/settings.ts +440 -0
- package/demo/src/style.ts +280 -0
- package/demo/src/tjs-playground.ts +1605 -0
- package/demo/src/ts-examples.ts +478 -0
- package/demo/src/ts-playground.ts +1092 -0
- package/demo/static/favicon.svg +30 -0
- package/demo/static/photo-1.jpg +0 -0
- package/demo/static/photo-2.jpg +0 -0
- package/demo/static/texts/ai-history.txt +9 -0
- package/demo/static/texts/coffee-origins.txt +9 -0
- package/demo/static/texts/renewable-energy.txt +9 -0
- package/dist/index.js +256 -0
- package/dist/index.js.map +37 -0
- package/dist/tjs-batteries.js +4 -0
- package/dist/tjs-batteries.js.map +15 -0
- package/dist/tjs-full.js +256 -0
- package/dist/tjs-full.js.map +37 -0
- package/dist/tjs-transpiler.js +220 -0
- package/dist/tjs-transpiler.js.map +21 -0
- package/dist/tjs-vm.js +4 -0
- package/dist/tjs-vm.js.map +14 -0
- package/docs/CNAME +1 -0
- package/docs/favicon.svg +30 -0
- package/docs/index.html +91 -0
- package/docs/index.js +10468 -0
- package/docs/index.js.map +92 -0
- package/docs/photo-1.jpg +0 -0
- package/docs/photo-1.webp +0 -0
- package/docs/photo-2.jpg +0 -0
- package/docs/photo-2.webp +0 -0
- package/docs/texts/ai-history.txt +9 -0
- package/docs/texts/coffee-origins.txt +9 -0
- package/docs/texts/renewable-energy.txt +9 -0
- package/docs/tjs-lang.svg +31 -0
- package/docs/tosijs-agent.svg +31 -0
- package/editors/README.md +325 -0
- package/editors/ace/ajs-mode.js +328 -0
- package/editors/ace/ajs-mode.ts +269 -0
- package/editors/ajs-syntax.ts +212 -0
- package/editors/build-grammars.ts +510 -0
- package/editors/codemirror/ajs-language.js +287 -0
- package/editors/codemirror/ajs-language.ts +1447 -0
- package/editors/codemirror/autocomplete.test.ts +531 -0
- package/editors/codemirror/component.ts +404 -0
- package/editors/monaco/ajs-monarch.js +243 -0
- package/editors/monaco/ajs-monarch.ts +225 -0
- package/editors/tjs-syntax.ts +115 -0
- package/editors/vscode/language-configuration.json +37 -0
- package/editors/vscode/package.json +65 -0
- package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
- package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
- package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
- package/package.json +83 -0
- package/src/cli/commands/check.ts +41 -0
- package/src/cli/commands/convert.ts +133 -0
- package/src/cli/commands/emit.ts +260 -0
- package/src/cli/commands/run.ts +68 -0
- package/src/cli/commands/test.ts +194 -0
- package/src/cli/commands/types.ts +20 -0
- package/src/cli/create-app.ts +236 -0
- package/src/cli/playground.ts +250 -0
- package/src/cli/tjs.ts +166 -0
- package/src/cli/tjsx.ts +160 -0
- package/tjs-lang.svg +31 -0
|
@@ -0,0 +1,1092 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TypeScript Playground - Interactive TypeScript to TJS to JS editor
|
|
3
|
+
*
|
|
4
|
+
* Demonstrates the full transpiler pipeline:
|
|
5
|
+
* 1. TypeScript input (user edits)
|
|
6
|
+
* 2. TJS intermediate output (read-only, with copy button)
|
|
7
|
+
* 3. JavaScript final output (with __tjs metadata)
|
|
8
|
+
* 4. Preview execution
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Component, ElementCreator, PartsMap, elements, vars } from 'tosijs'
|
|
12
|
+
import {
|
|
13
|
+
tabSelector,
|
|
14
|
+
TabSelector,
|
|
15
|
+
icons,
|
|
16
|
+
markdownViewer,
|
|
17
|
+
MarkdownViewer,
|
|
18
|
+
} from 'tosijs-ui'
|
|
19
|
+
import { codeMirror, CodeMirror } from '../../editors/codemirror/component'
|
|
20
|
+
import { fromTS } from '../../src/lang/emitters/from-ts'
|
|
21
|
+
import { tjs } from '../../src/lang'
|
|
22
|
+
import { extractImports, resolveImports } from './imports'
|
|
23
|
+
|
|
24
|
+
const { div, button, span, pre, input } = elements
|
|
25
|
+
|
|
26
|
+
// Default TypeScript example
|
|
27
|
+
const DEFAULT_TS = `// TypeScript Example - Types become runtime validation
|
|
28
|
+
function greet(name: string): string {
|
|
29
|
+
return \`Hello, \${name}!\`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function add(a: number, b: number): number {
|
|
33
|
+
return a + b
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Try calling with wrong types - TJS catches it at runtime!
|
|
37
|
+
console.log(greet('World'))
|
|
38
|
+
console.log(add(2, 3))
|
|
39
|
+
`
|
|
40
|
+
|
|
41
|
+
const DEFAULT_HTML = `<div class="preview-content">
|
|
42
|
+
<h2>Preview</h2>
|
|
43
|
+
<div id="output"></div>
|
|
44
|
+
</div>`
|
|
45
|
+
|
|
46
|
+
const DEFAULT_CSS = `.preview-content {
|
|
47
|
+
padding: 1rem;
|
|
48
|
+
font-family: system-ui, sans-serif;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
h2 {
|
|
52
|
+
color: #3d4a6b;
|
|
53
|
+
margin-top: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#output {
|
|
57
|
+
padding: 0.5rem;
|
|
58
|
+
background: #f5f5f5;
|
|
59
|
+
border-radius: 4px;
|
|
60
|
+
}`
|
|
61
|
+
|
|
62
|
+
interface TSPlaygroundParts extends PartsMap {
|
|
63
|
+
tsEditor: CodeMirror
|
|
64
|
+
htmlEditor: CodeMirror
|
|
65
|
+
cssEditor: CodeMirror
|
|
66
|
+
inputTabs: TabSelector
|
|
67
|
+
outputTabs: TabSelector
|
|
68
|
+
tjsOutput: HTMLElement
|
|
69
|
+
jsOutput: HTMLElement
|
|
70
|
+
previewFrame: HTMLIFrameElement
|
|
71
|
+
docsOutput: MarkdownViewer
|
|
72
|
+
testsOutput: HTMLElement
|
|
73
|
+
consoleHeader: HTMLElement
|
|
74
|
+
console: HTMLElement
|
|
75
|
+
runBtn: HTMLButtonElement
|
|
76
|
+
revertBtn: HTMLButtonElement
|
|
77
|
+
copyTjsBtn: HTMLButtonElement
|
|
78
|
+
openTjsBtn: HTMLButtonElement
|
|
79
|
+
statusBar: HTMLElement
|
|
80
|
+
// Build flags
|
|
81
|
+
testsToggle: HTMLInputElement
|
|
82
|
+
debugToggle: HTMLInputElement
|
|
83
|
+
safetyToggle: HTMLInputElement
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class TSPlayground extends Component<TSPlaygroundParts> {
|
|
87
|
+
private lastTjsCode: string = ''
|
|
88
|
+
private lastJsCode: string = ''
|
|
89
|
+
private consoleMessages: string[] = []
|
|
90
|
+
|
|
91
|
+
// Editor state persistence
|
|
92
|
+
private currentExampleName: string | null = null
|
|
93
|
+
private originalCode: string = DEFAULT_TS
|
|
94
|
+
private editorCache: Map<string, string> = new Map()
|
|
95
|
+
|
|
96
|
+
// Build flags state
|
|
97
|
+
private buildFlags = {
|
|
98
|
+
tests: true, // Run tests at transpile time
|
|
99
|
+
debug: false, // Debug mode (call stack tracking)
|
|
100
|
+
safe: true, // Safe mode (validates inputs)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
content = () => [
|
|
104
|
+
// Toolbar
|
|
105
|
+
div(
|
|
106
|
+
{ class: 'ts-toolbar' },
|
|
107
|
+
button(
|
|
108
|
+
{ part: 'runBtn', class: 'run-btn', onClick: this.run },
|
|
109
|
+
icons.play({ size: 16 }),
|
|
110
|
+
'Run'
|
|
111
|
+
),
|
|
112
|
+
span({ class: 'toolbar-separator' }),
|
|
113
|
+
// Build flags
|
|
114
|
+
div(
|
|
115
|
+
{ class: 'build-flags' },
|
|
116
|
+
elements.label(
|
|
117
|
+
{ class: 'flag-label', title: 'Run tests at transpile time' },
|
|
118
|
+
input({
|
|
119
|
+
part: 'testsToggle',
|
|
120
|
+
type: 'checkbox',
|
|
121
|
+
checked: true,
|
|
122
|
+
onChange: this.toggleTests,
|
|
123
|
+
}),
|
|
124
|
+
'Tests'
|
|
125
|
+
),
|
|
126
|
+
elements.label(
|
|
127
|
+
{ class: 'flag-label', title: 'Debug mode (call stack tracking)' },
|
|
128
|
+
input({
|
|
129
|
+
part: 'debugToggle',
|
|
130
|
+
type: 'checkbox',
|
|
131
|
+
onChange: this.toggleDebug,
|
|
132
|
+
}),
|
|
133
|
+
'Debug'
|
|
134
|
+
),
|
|
135
|
+
elements.label(
|
|
136
|
+
{ class: 'flag-label', title: 'Safe mode (validates inputs)' },
|
|
137
|
+
input({
|
|
138
|
+
part: 'safetyToggle',
|
|
139
|
+
type: 'checkbox',
|
|
140
|
+
checked: true,
|
|
141
|
+
onChange: this.toggleSafety,
|
|
142
|
+
}),
|
|
143
|
+
'Safe'
|
|
144
|
+
)
|
|
145
|
+
),
|
|
146
|
+
span({ class: 'toolbar-separator' }),
|
|
147
|
+
button(
|
|
148
|
+
{
|
|
149
|
+
part: 'revertBtn',
|
|
150
|
+
class: 'revert-btn',
|
|
151
|
+
onClick: this.revertToOriginal,
|
|
152
|
+
title: 'Revert to original example code',
|
|
153
|
+
},
|
|
154
|
+
icons.cornerUpLeft({ size: 16 }),
|
|
155
|
+
'Revert'
|
|
156
|
+
),
|
|
157
|
+
span({ class: 'elastic' }),
|
|
158
|
+
span({ part: 'statusBar', class: 'status-bar' }, 'Ready')
|
|
159
|
+
),
|
|
160
|
+
|
|
161
|
+
// Main area - split into input (left) and output (right)
|
|
162
|
+
div(
|
|
163
|
+
{ class: 'ts-main' },
|
|
164
|
+
|
|
165
|
+
// Input side - TS, HTML, CSS editors
|
|
166
|
+
div(
|
|
167
|
+
{ class: 'ts-input' },
|
|
168
|
+
tabSelector(
|
|
169
|
+
{ part: 'inputTabs' },
|
|
170
|
+
div(
|
|
171
|
+
{ name: 'TS', class: 'editor-wrapper' },
|
|
172
|
+
codeMirror({
|
|
173
|
+
part: 'tsEditor',
|
|
174
|
+
mode: 'typescript',
|
|
175
|
+
})
|
|
176
|
+
),
|
|
177
|
+
div(
|
|
178
|
+
{ name: 'HTML', class: 'editor-wrapper' },
|
|
179
|
+
codeMirror({
|
|
180
|
+
part: 'htmlEditor',
|
|
181
|
+
mode: 'html',
|
|
182
|
+
})
|
|
183
|
+
),
|
|
184
|
+
div(
|
|
185
|
+
{ name: 'CSS', class: 'editor-wrapper' },
|
|
186
|
+
codeMirror({
|
|
187
|
+
part: 'cssEditor',
|
|
188
|
+
mode: 'css',
|
|
189
|
+
})
|
|
190
|
+
)
|
|
191
|
+
)
|
|
192
|
+
),
|
|
193
|
+
|
|
194
|
+
// Output side - TJS, JS, Preview, Docs, Tests
|
|
195
|
+
div(
|
|
196
|
+
{ class: 'ts-output' },
|
|
197
|
+
tabSelector(
|
|
198
|
+
{ part: 'outputTabs' },
|
|
199
|
+
div(
|
|
200
|
+
{ name: 'TJS' },
|
|
201
|
+
div(
|
|
202
|
+
{ class: 'output-toolbar' },
|
|
203
|
+
button(
|
|
204
|
+
{
|
|
205
|
+
part: 'copyTjsBtn',
|
|
206
|
+
class: 'small-btn',
|
|
207
|
+
onClick: this.copyTjs,
|
|
208
|
+
title: 'Copy TJS to clipboard',
|
|
209
|
+
},
|
|
210
|
+
icons.copy({ size: 14 }),
|
|
211
|
+
'Copy'
|
|
212
|
+
),
|
|
213
|
+
button(
|
|
214
|
+
{
|
|
215
|
+
part: 'openTjsBtn',
|
|
216
|
+
class: 'small-btn',
|
|
217
|
+
onClick: this.openInTjsPlayground,
|
|
218
|
+
title: 'Open in TJS Playground',
|
|
219
|
+
},
|
|
220
|
+
icons.externalLink({ size: 14 }),
|
|
221
|
+
'Open in TJS'
|
|
222
|
+
)
|
|
223
|
+
),
|
|
224
|
+
pre(
|
|
225
|
+
{ part: 'tjsOutput', class: 'code-output' },
|
|
226
|
+
'// TJS intermediate will appear here'
|
|
227
|
+
)
|
|
228
|
+
),
|
|
229
|
+
div(
|
|
230
|
+
{ name: 'JS' },
|
|
231
|
+
pre(
|
|
232
|
+
{ part: 'jsOutput', class: 'code-output' },
|
|
233
|
+
'// Final JavaScript will appear here'
|
|
234
|
+
)
|
|
235
|
+
),
|
|
236
|
+
div(
|
|
237
|
+
{ name: 'Preview' },
|
|
238
|
+
div(
|
|
239
|
+
{ class: 'preview-container' },
|
|
240
|
+
elements.iframe({
|
|
241
|
+
part: 'previewFrame',
|
|
242
|
+
class: 'preview-frame',
|
|
243
|
+
sandbox: 'allow-scripts',
|
|
244
|
+
})
|
|
245
|
+
)
|
|
246
|
+
),
|
|
247
|
+
markdownViewer({
|
|
248
|
+
name: 'Docs',
|
|
249
|
+
part: 'docsOutput',
|
|
250
|
+
class: 'docs-output',
|
|
251
|
+
value: '*Documentation will appear here*',
|
|
252
|
+
}),
|
|
253
|
+
div(
|
|
254
|
+
{ name: 'Tests' },
|
|
255
|
+
div(
|
|
256
|
+
{ part: 'testsOutput', class: 'tests-output' },
|
|
257
|
+
'Test results will appear here'
|
|
258
|
+
)
|
|
259
|
+
)
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
),
|
|
263
|
+
|
|
264
|
+
// Console panel at bottom
|
|
265
|
+
div(
|
|
266
|
+
{ class: 'ts-console' },
|
|
267
|
+
div({ part: 'consoleHeader', class: 'console-header' }, 'Console'),
|
|
268
|
+
pre({ part: 'console', class: 'console-output' })
|
|
269
|
+
),
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
connectedCallback(): void {
|
|
273
|
+
super.connectedCallback()
|
|
274
|
+
|
|
275
|
+
// Set default content
|
|
276
|
+
setTimeout(() => {
|
|
277
|
+
this.parts.tsEditor.value = DEFAULT_TS
|
|
278
|
+
this.parts.htmlEditor.value = DEFAULT_HTML
|
|
279
|
+
this.parts.cssEditor.value = DEFAULT_CSS
|
|
280
|
+
|
|
281
|
+
// Auto-transpile on load
|
|
282
|
+
this.transpile()
|
|
283
|
+
}, 0)
|
|
284
|
+
|
|
285
|
+
// Listen for changes (debounced)
|
|
286
|
+
let debounceTimer: ReturnType<typeof setTimeout>
|
|
287
|
+
this.parts.tsEditor.addEventListener('change', () => {
|
|
288
|
+
clearTimeout(debounceTimer)
|
|
289
|
+
debounceTimer = setTimeout(() => {
|
|
290
|
+
this.transpile()
|
|
291
|
+
this.updateRevertButton()
|
|
292
|
+
}, 300)
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
log = (message: string) => {
|
|
297
|
+
this.consoleMessages.push(message)
|
|
298
|
+
this.renderConsole()
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
clearConsole = () => {
|
|
302
|
+
this.consoleMessages = []
|
|
303
|
+
this.parts.console.innerHTML = ''
|
|
304
|
+
this.parts.consoleHeader.textContent = 'Console'
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
private renderConsole() {
|
|
308
|
+
// Parse messages for line references and make them clickable
|
|
309
|
+
// Patterns: "at line X", "line X:", "Line X", ":X:" (line:col)
|
|
310
|
+
const linePattern =
|
|
311
|
+
/(?:at line |line |Line )(\d+)(?:[:,]?\s*(?:column |col )?(\d+))?|:(\d+):(\d+)/g
|
|
312
|
+
|
|
313
|
+
const html = this.consoleMessages
|
|
314
|
+
.map((msg) => {
|
|
315
|
+
// Escape HTML
|
|
316
|
+
const escaped = msg
|
|
317
|
+
.replace(/&/g, '&')
|
|
318
|
+
.replace(/</g, '<')
|
|
319
|
+
.replace(/>/g, '>')
|
|
320
|
+
|
|
321
|
+
// Replace line references with clickable spans
|
|
322
|
+
return escaped.replace(linePattern, (match, l1, c1, l2, c2) => {
|
|
323
|
+
const line = l1 || l2
|
|
324
|
+
const col = c1 || c2 || '1'
|
|
325
|
+
return `<span class="clickable-line" data-line="${line}" data-col="${col}">${match}</span>`
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
.join('\n')
|
|
329
|
+
|
|
330
|
+
this.parts.console.innerHTML = html
|
|
331
|
+
this.parts.console.scrollTop = this.parts.console.scrollHeight
|
|
332
|
+
|
|
333
|
+
// Add click handlers
|
|
334
|
+
this.parts.console.querySelectorAll('.clickable-line').forEach((el) => {
|
|
335
|
+
el.addEventListener('click', (e) => {
|
|
336
|
+
const target = e.currentTarget as HTMLElement
|
|
337
|
+
const line = parseInt(target.dataset.line || '0', 10)
|
|
338
|
+
const col = parseInt(target.dataset.col || '1', 10)
|
|
339
|
+
if (line > 0) {
|
|
340
|
+
this.goToSourceLine(line, col)
|
|
341
|
+
}
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Navigate to a specific line in the source editor
|
|
347
|
+
goToSourceLine(line: number, column: number = 1) {
|
|
348
|
+
this.parts.inputTabs.value = 0 // Switch to TS tab (first tab)
|
|
349
|
+
// Wait for tab switch and editor resize before scrolling
|
|
350
|
+
setTimeout(() => {
|
|
351
|
+
this.parts.tsEditor.goToLine(line, column)
|
|
352
|
+
}, 50)
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Build flag toggle handlers
|
|
356
|
+
toggleTests = () => {
|
|
357
|
+
this.buildFlags.tests = this.parts.testsToggle.checked
|
|
358
|
+
this.transpile()
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
toggleDebug = () => {
|
|
362
|
+
this.buildFlags.debug = this.parts.debugToggle.checked
|
|
363
|
+
this.transpile()
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
toggleSafety = () => {
|
|
367
|
+
this.buildFlags.safe = this.parts.safetyToggle.checked
|
|
368
|
+
this.transpile()
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
lastTranspileTime = 0
|
|
372
|
+
lastTsToTjsTime = 0
|
|
373
|
+
lastTjsToJsTime = 0
|
|
374
|
+
|
|
375
|
+
transpile = () => {
|
|
376
|
+
const tsSource = this.parts.tsEditor.value
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
// Step 1: TS -> TJS (timed)
|
|
380
|
+
const tsStart = performance.now()
|
|
381
|
+
const tjsResult = fromTS(tsSource, { emitTJS: true })
|
|
382
|
+
this.lastTsToTjsTime = performance.now() - tsStart
|
|
383
|
+
|
|
384
|
+
this.lastTjsCode = tjsResult.code
|
|
385
|
+
this.parts.tjsOutput.textContent = tjsResult.code
|
|
386
|
+
|
|
387
|
+
// Show warnings if any
|
|
388
|
+
if (tjsResult.warnings && tjsResult.warnings.length > 0) {
|
|
389
|
+
this.parts.tjsOutput.textContent +=
|
|
390
|
+
'\n\n// Warnings:\n// ' + tjsResult.warnings.join('\n// ')
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Step 2: TJS -> JS (skip signature tests with -!) (timed)
|
|
394
|
+
// Replace -> with -! to avoid signature test failures during transpilation
|
|
395
|
+
let tjsCodeForJs = tjsResult.code.replace(/-> /g, '-! ')
|
|
396
|
+
|
|
397
|
+
// Inject safety directive if unsafe mode is enabled
|
|
398
|
+
if (!this.buildFlags.safe) {
|
|
399
|
+
tjsCodeForJs = 'safety none\n' + tjsCodeForJs
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
try {
|
|
403
|
+
const tjsStart = performance.now()
|
|
404
|
+
// Build transpiler options from flags
|
|
405
|
+
const options: { runTests: boolean | 'report'; debug?: boolean } = {
|
|
406
|
+
runTests: this.buildFlags.tests ? 'report' : false,
|
|
407
|
+
debug: this.buildFlags.debug,
|
|
408
|
+
}
|
|
409
|
+
const jsResult = tjs(tjsCodeForJs, options)
|
|
410
|
+
this.lastTjsToJsTime = performance.now() - tjsStart
|
|
411
|
+
this.lastTranspileTime = this.lastTsToTjsTime + this.lastTjsToJsTime
|
|
412
|
+
|
|
413
|
+
this.lastJsCode = jsResult.code
|
|
414
|
+
this.parts.jsOutput.textContent = jsResult.code
|
|
415
|
+
|
|
416
|
+
// Update test results
|
|
417
|
+
this.updateTestResults(jsResult.testResults || [])
|
|
418
|
+
|
|
419
|
+
// Update docs
|
|
420
|
+
this.updateDocs(jsResult)
|
|
421
|
+
|
|
422
|
+
// Show timing: TS->TJS + TJS->JS = total
|
|
423
|
+
const formatTime = (t: number) =>
|
|
424
|
+
t < 1 ? `${(t * 1000).toFixed(0)}μs` : `${t.toFixed(2)}ms`
|
|
425
|
+
this.parts.statusBar.textContent = `TS→TJS ${formatTime(
|
|
426
|
+
this.lastTsToTjsTime
|
|
427
|
+
)} + TJS→JS ${formatTime(this.lastTjsToJsTime)} = ${formatTime(
|
|
428
|
+
this.lastTranspileTime
|
|
429
|
+
)}`
|
|
430
|
+
this.parts.statusBar.classList.remove('error')
|
|
431
|
+
} catch (jsError: any) {
|
|
432
|
+
this.parts.jsOutput.textContent = `// TJS -> JS Error:\n// ${jsError.message}`
|
|
433
|
+
this.parts.statusBar.textContent = `TJS->JS error: ${jsError.message}`
|
|
434
|
+
this.parts.statusBar.classList.add('error')
|
|
435
|
+
this.lastJsCode = ''
|
|
436
|
+
}
|
|
437
|
+
} catch (tsError: any) {
|
|
438
|
+
// TS -> TJS failed
|
|
439
|
+
const errorInfo = this.formatError(tsError, tsSource)
|
|
440
|
+
this.parts.tjsOutput.textContent = errorInfo
|
|
441
|
+
this.parts.jsOutput.textContent =
|
|
442
|
+
'// Cannot generate JS - TS transpilation failed'
|
|
443
|
+
this.parts.statusBar.textContent = `TS error: ${tsError.message}`
|
|
444
|
+
this.parts.statusBar.classList.add('error')
|
|
445
|
+
this.lastTjsCode = ''
|
|
446
|
+
this.lastJsCode = ''
|
|
447
|
+
|
|
448
|
+
// Set error marker in gutter
|
|
449
|
+
if (tsError.line) {
|
|
450
|
+
this.parts.tsEditor.setMarkers([
|
|
451
|
+
{
|
|
452
|
+
line: tsError.line,
|
|
453
|
+
message: tsError.message || 'Transpilation error',
|
|
454
|
+
},
|
|
455
|
+
])
|
|
456
|
+
} else {
|
|
457
|
+
this.parts.tsEditor.clearMarkers()
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
private formatError(e: any, source: string): string {
|
|
463
|
+
const lines = ['// Transpilation Error', '// ' + '='.repeat(50), '']
|
|
464
|
+
lines.push(`// ${e.message}`)
|
|
465
|
+
if (e.line) {
|
|
466
|
+
lines.push(`// at line ${e.line}, column ${e.column || 0}`)
|
|
467
|
+
}
|
|
468
|
+
return lines.join('\n')
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
private updateTestResults(tests: any[]) {
|
|
472
|
+
if (!tests || tests.length === 0) {
|
|
473
|
+
this.parts.testsOutput.textContent = 'No tests defined'
|
|
474
|
+
this.parts.tsEditor.clearMarkers()
|
|
475
|
+
return
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const passed = tests.filter((t) => t.passed).length
|
|
479
|
+
const failed = tests.filter((t) => !t.passed).length
|
|
480
|
+
|
|
481
|
+
// Set gutter markers for failed tests
|
|
482
|
+
const failedTests = tests.filter((t: any) => !t.passed && t.line)
|
|
483
|
+
if (failedTests.length > 0) {
|
|
484
|
+
this.parts.tsEditor.setMarkers(
|
|
485
|
+
failedTests.map((t: any) => ({
|
|
486
|
+
line: t.line,
|
|
487
|
+
message: t.error || t.description,
|
|
488
|
+
severity: 'error' as const,
|
|
489
|
+
}))
|
|
490
|
+
)
|
|
491
|
+
} else {
|
|
492
|
+
this.parts.tsEditor.clearMarkers()
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
let html = `<div class="test-summary">`
|
|
496
|
+
html += `<strong>${passed} passed</strong>`
|
|
497
|
+
if (failed > 0) {
|
|
498
|
+
html += `, <strong class="test-failed">${failed} failed</strong>`
|
|
499
|
+
}
|
|
500
|
+
html += `</div><ul class="test-list">`
|
|
501
|
+
|
|
502
|
+
for (const test of tests) {
|
|
503
|
+
const icon = test.passed ? '✓' : '✗'
|
|
504
|
+
const cls = test.passed ? 'test-pass' : 'test-fail'
|
|
505
|
+
const sigBadge = test.isSignatureTest
|
|
506
|
+
? ' <span class="sig-badge">signature</span>'
|
|
507
|
+
: ''
|
|
508
|
+
const dataLine = test.line ? ` data-line="${test.line}"` : ''
|
|
509
|
+
html += `<li class="${cls}"${dataLine}>${icon} ${test.description}${sigBadge}`
|
|
510
|
+
if (!test.passed && test.error) {
|
|
511
|
+
html += `<div class="test-error${
|
|
512
|
+
test.line ? ' clickable-error' : ''
|
|
513
|
+
}"${dataLine}>${test.error}</div>`
|
|
514
|
+
}
|
|
515
|
+
html += `</li>`
|
|
516
|
+
}
|
|
517
|
+
html += `</ul>`
|
|
518
|
+
|
|
519
|
+
this.parts.testsOutput.innerHTML = html
|
|
520
|
+
|
|
521
|
+
// Add click handlers for clickable errors
|
|
522
|
+
this.parts.testsOutput
|
|
523
|
+
.querySelectorAll('.clickable-error')
|
|
524
|
+
.forEach((el) => {
|
|
525
|
+
el.addEventListener('click', (e) => {
|
|
526
|
+
const line = parseInt(
|
|
527
|
+
(e.currentTarget as HTMLElement).dataset.line || '0',
|
|
528
|
+
10
|
|
529
|
+
)
|
|
530
|
+
if (line > 0) {
|
|
531
|
+
this.goToSourceLine(line)
|
|
532
|
+
}
|
|
533
|
+
})
|
|
534
|
+
})
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
private updateDocs(result: any) {
|
|
538
|
+
if (!result?.types) {
|
|
539
|
+
this.parts.docsOutput.value = '*No documentation available*'
|
|
540
|
+
return
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
let docs = '# Generated Documentation\n\n'
|
|
544
|
+
|
|
545
|
+
for (const [name, info] of Object.entries(result.types) as any) {
|
|
546
|
+
docs += `## ${name}\n\n`
|
|
547
|
+
|
|
548
|
+
if (info.params) {
|
|
549
|
+
docs += '**Parameters:**\n'
|
|
550
|
+
for (const [paramName, paramInfo] of Object.entries(
|
|
551
|
+
info.params
|
|
552
|
+
) as any) {
|
|
553
|
+
const required = paramInfo.required ? '' : ' *(optional)*'
|
|
554
|
+
const typeStr = paramInfo.type?.kind || 'any'
|
|
555
|
+
docs += `- \`${paramName}\`: ${typeStr}${required}\n`
|
|
556
|
+
}
|
|
557
|
+
docs += '\n'
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
if (info.returns) {
|
|
561
|
+
docs += `**Returns:** ${info.returns.kind || 'void'}\n\n`
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
docs += '---\n\n'
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
this.parts.docsOutput.value = docs
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
copyTjs = () => {
|
|
571
|
+
if (this.lastTjsCode) {
|
|
572
|
+
navigator.clipboard.writeText(this.lastTjsCode)
|
|
573
|
+
this.parts.statusBar.textContent = 'TJS copied to clipboard'
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
openInTjsPlayground = () => {
|
|
578
|
+
if (this.lastTjsCode) {
|
|
579
|
+
// Store the TJS code and navigate to TJS playground
|
|
580
|
+
// For now, we'll use a custom event that the parent can handle
|
|
581
|
+
this.dispatchEvent(
|
|
582
|
+
new CustomEvent('open-tjs', {
|
|
583
|
+
detail: { code: this.lastTjsCode },
|
|
584
|
+
bubbles: true,
|
|
585
|
+
})
|
|
586
|
+
)
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
run = async () => {
|
|
591
|
+
this.clearConsole()
|
|
592
|
+
this.transpile()
|
|
593
|
+
|
|
594
|
+
if (!this.lastJsCode) {
|
|
595
|
+
this.log('Cannot run - transpilation failed')
|
|
596
|
+
return
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
this.parts.statusBar.textContent = 'Running...'
|
|
600
|
+
|
|
601
|
+
try {
|
|
602
|
+
const htmlContent = this.parts.htmlEditor.value
|
|
603
|
+
const cssContent = this.parts.cssEditor.value
|
|
604
|
+
const jsCode = this.lastJsCode
|
|
605
|
+
|
|
606
|
+
// Resolve imports
|
|
607
|
+
const imports = extractImports(jsCode)
|
|
608
|
+
let importMapScript = ''
|
|
609
|
+
|
|
610
|
+
if (imports.length > 0) {
|
|
611
|
+
this.log(`Resolving imports: ${imports.join(', ')}`)
|
|
612
|
+
const { importMap, errors } = await resolveImports(jsCode)
|
|
613
|
+
|
|
614
|
+
if (errors.length > 0) {
|
|
615
|
+
for (const err of errors) {
|
|
616
|
+
this.log(`Import error: ${err}`)
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
if (Object.keys(importMap.imports).length > 0) {
|
|
621
|
+
importMapScript = `<script type="importmap">${JSON.stringify(
|
|
622
|
+
importMap
|
|
623
|
+
)}</script>`
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Create iframe document
|
|
628
|
+
const iframeDoc = `<!DOCTYPE html>
|
|
629
|
+
<html>
|
|
630
|
+
<head>
|
|
631
|
+
<style>${cssContent}</style>
|
|
632
|
+
${importMapScript}
|
|
633
|
+
</head>
|
|
634
|
+
<body>
|
|
635
|
+
${htmlContent}
|
|
636
|
+
<script type="module">
|
|
637
|
+
// TJS Runtime stub for iframe execution
|
|
638
|
+
globalThis.__tjs = {
|
|
639
|
+
version: '0.0.0',
|
|
640
|
+
pushStack: () => {},
|
|
641
|
+
popStack: () => {},
|
|
642
|
+
getStack: () => [],
|
|
643
|
+
typeError: (path, expected, value) => {
|
|
644
|
+
const actual = value === null ? 'null' : typeof value;
|
|
645
|
+
const err = new Error(\`Expected \${expected} for '\${path}', got \${actual}\`);
|
|
646
|
+
err.name = 'MonadicError';
|
|
647
|
+
err.path = path;
|
|
648
|
+
err.expected = expected;
|
|
649
|
+
err.actual = actual;
|
|
650
|
+
return err;
|
|
651
|
+
},
|
|
652
|
+
createRuntime: function() { return this; },
|
|
653
|
+
Is: (a, b) => {
|
|
654
|
+
if (a === b) return true;
|
|
655
|
+
if (a === null || b === null) return a === b;
|
|
656
|
+
if (typeof a !== typeof b) return false;
|
|
657
|
+
if (typeof a !== 'object') return false;
|
|
658
|
+
if (Array.isArray(a) && Array.isArray(b)) {
|
|
659
|
+
if (a.length !== b.length) return false;
|
|
660
|
+
return a.every((v, i) => globalThis.__tjs.Is(v, b[i]));
|
|
661
|
+
}
|
|
662
|
+
const keysA = Object.keys(a);
|
|
663
|
+
const keysB = Object.keys(b);
|
|
664
|
+
if (keysA.length !== keysB.length) return false;
|
|
665
|
+
return keysA.every(k => globalThis.__tjs.Is(a[k], b[k]));
|
|
666
|
+
},
|
|
667
|
+
IsNot: (a, b) => !globalThis.__tjs.Is(a, b),
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// Capture console.log
|
|
671
|
+
const _log = console.log;
|
|
672
|
+
console.log = (...args) => {
|
|
673
|
+
_log(...args);
|
|
674
|
+
parent.postMessage({ type: 'console', message: args.map(a =>
|
|
675
|
+
typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a)
|
|
676
|
+
).join(' ') }, '*');
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
try {
|
|
680
|
+
const __execStart = performance.now();
|
|
681
|
+
${jsCode}
|
|
682
|
+
const __execTime = performance.now() - __execStart;
|
|
683
|
+
parent.postMessage({ type: 'timing', execTime: __execTime }, '*');
|
|
684
|
+
} catch (e) {
|
|
685
|
+
parent.postMessage({ type: 'error', message: e.message }, '*');
|
|
686
|
+
}
|
|
687
|
+
</script>
|
|
688
|
+
</body>
|
|
689
|
+
</html>`
|
|
690
|
+
|
|
691
|
+
// Listen for messages from iframe
|
|
692
|
+
const messageHandler = (event: MessageEvent) => {
|
|
693
|
+
if (event.data?.type === 'console') {
|
|
694
|
+
this.log(event.data.message)
|
|
695
|
+
} else if (event.data?.type === 'timing') {
|
|
696
|
+
// Update console header with execution time
|
|
697
|
+
const execTime = event.data.execTime
|
|
698
|
+
const execStr =
|
|
699
|
+
execTime < 1
|
|
700
|
+
? `${(execTime * 1000).toFixed(0)}μs`
|
|
701
|
+
: `${execTime.toFixed(2)}ms`
|
|
702
|
+
this.parts.consoleHeader.textContent = `Console — executed in ${execStr}`
|
|
703
|
+
} else if (event.data?.type === 'error') {
|
|
704
|
+
this.log(`Error: ${event.data.message}`)
|
|
705
|
+
this.parts.statusBar.textContent = 'Runtime error'
|
|
706
|
+
this.parts.statusBar.classList.add('error')
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
window.addEventListener('message', messageHandler)
|
|
710
|
+
|
|
711
|
+
// Set iframe content
|
|
712
|
+
this.parts.previewFrame.srcdoc = iframeDoc
|
|
713
|
+
|
|
714
|
+
// Wait a bit for execution, then clean up listener
|
|
715
|
+
setTimeout(() => {
|
|
716
|
+
window.removeEventListener('message', messageHandler)
|
|
717
|
+
// Don't overwrite status bar - keep showing transpile time
|
|
718
|
+
}, 1000)
|
|
719
|
+
} catch (e: any) {
|
|
720
|
+
this.log(`Error: ${e.message}`)
|
|
721
|
+
this.parts.statusBar.textContent = 'Error'
|
|
722
|
+
this.parts.statusBar.classList.add('error')
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Public method to set source code (auto-runs when examples are loaded)
|
|
727
|
+
setSource(code: string, exampleName?: string) {
|
|
728
|
+
// Save current edits before switching
|
|
729
|
+
if (this.currentExampleName) {
|
|
730
|
+
this.editorCache.set(this.currentExampleName, this.parts.tsEditor.value)
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
// Update current example tracking
|
|
734
|
+
this.currentExampleName = exampleName || null
|
|
735
|
+
this.originalCode = code
|
|
736
|
+
|
|
737
|
+
// Check if we have cached edits for this example
|
|
738
|
+
const cachedCode = exampleName ? this.editorCache.get(exampleName) : null
|
|
739
|
+
this.parts.tsEditor.value = cachedCode || code
|
|
740
|
+
|
|
741
|
+
// Update revert button visibility
|
|
742
|
+
this.updateRevertButton()
|
|
743
|
+
|
|
744
|
+
this.transpile()
|
|
745
|
+
// Auto-run when source is loaded externally (e.g., from example selection)
|
|
746
|
+
this.run()
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// Revert to the original example code
|
|
750
|
+
revertToOriginal = () => {
|
|
751
|
+
if (this.currentExampleName) {
|
|
752
|
+
this.editorCache.delete(this.currentExampleName)
|
|
753
|
+
}
|
|
754
|
+
this.parts.tsEditor.value = this.originalCode
|
|
755
|
+
this.updateRevertButton()
|
|
756
|
+
this.transpile()
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Update revert button state based on whether code has changed
|
|
760
|
+
private updateRevertButton() {
|
|
761
|
+
const hasChanges = this.parts.tsEditor.value !== this.originalCode
|
|
762
|
+
this.parts.revertBtn.disabled = !hasChanges
|
|
763
|
+
this.parts.revertBtn.style.opacity = hasChanges ? '1' : '0.5'
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
export const tsPlayground = TSPlayground.elementCreator({
|
|
768
|
+
tag: 'ts-playground',
|
|
769
|
+
styleSpec: {
|
|
770
|
+
':host': {
|
|
771
|
+
display: 'flex',
|
|
772
|
+
flexDirection: 'column',
|
|
773
|
+
height: '100%',
|
|
774
|
+
flex: '1 1 auto',
|
|
775
|
+
background: 'var(--background, #fff)',
|
|
776
|
+
color: 'var(--text-color, #1f2937)',
|
|
777
|
+
fontFamily: 'system-ui, sans-serif',
|
|
778
|
+
},
|
|
779
|
+
|
|
780
|
+
':host .ts-toolbar': {
|
|
781
|
+
display: 'flex',
|
|
782
|
+
alignItems: 'center',
|
|
783
|
+
gap: '10px',
|
|
784
|
+
padding: '8px 12px',
|
|
785
|
+
background: 'var(--code-background, #f3f4f6)',
|
|
786
|
+
borderBottom: '1px solid var(--code-border, #e5e7eb)',
|
|
787
|
+
},
|
|
788
|
+
|
|
789
|
+
':host .run-btn': {
|
|
790
|
+
display: 'flex',
|
|
791
|
+
alignItems: 'center',
|
|
792
|
+
gap: '4px',
|
|
793
|
+
padding: '6px 12px',
|
|
794
|
+
background: 'var(--brand-color, #3178c6)', // TypeScript blue
|
|
795
|
+
color: 'var(--brand-text-color, white)',
|
|
796
|
+
border: 'none',
|
|
797
|
+
borderRadius: '6px',
|
|
798
|
+
cursor: 'pointer',
|
|
799
|
+
fontWeight: '500',
|
|
800
|
+
fontSize: '14px',
|
|
801
|
+
},
|
|
802
|
+
|
|
803
|
+
':host .run-btn:hover': {
|
|
804
|
+
filter: 'brightness(1.1)',
|
|
805
|
+
},
|
|
806
|
+
|
|
807
|
+
':host .toolbar-separator': {
|
|
808
|
+
width: '1px',
|
|
809
|
+
height: '20px',
|
|
810
|
+
background: 'var(--code-border, #d1d5db)',
|
|
811
|
+
},
|
|
812
|
+
|
|
813
|
+
':host .build-flags': {
|
|
814
|
+
display: 'flex',
|
|
815
|
+
alignItems: 'center',
|
|
816
|
+
gap: '12px',
|
|
817
|
+
},
|
|
818
|
+
|
|
819
|
+
':host .flag-label': {
|
|
820
|
+
display: 'flex',
|
|
821
|
+
alignItems: 'center',
|
|
822
|
+
gap: '4px',
|
|
823
|
+
fontSize: '13px',
|
|
824
|
+
color: 'var(--text-color, #6b7280)',
|
|
825
|
+
cursor: 'pointer',
|
|
826
|
+
userSelect: 'none',
|
|
827
|
+
},
|
|
828
|
+
|
|
829
|
+
':host .flag-label:hover': {
|
|
830
|
+
color: 'var(--text-color, #374151)',
|
|
831
|
+
},
|
|
832
|
+
|
|
833
|
+
':host .flag-label input[type="checkbox"]': {
|
|
834
|
+
margin: '0',
|
|
835
|
+
cursor: 'pointer',
|
|
836
|
+
accentColor: 'var(--brand-color, #3178c6)',
|
|
837
|
+
},
|
|
838
|
+
|
|
839
|
+
':host .revert-btn': {
|
|
840
|
+
display: 'flex',
|
|
841
|
+
alignItems: 'center',
|
|
842
|
+
gap: '4px',
|
|
843
|
+
padding: '6px 12px',
|
|
844
|
+
background: 'var(--code-background, #e5e7eb)',
|
|
845
|
+
color: 'var(--text-color, #374151)',
|
|
846
|
+
border: '1px solid var(--code-border, #d1d5db)',
|
|
847
|
+
borderRadius: '6px',
|
|
848
|
+
cursor: 'pointer',
|
|
849
|
+
fontWeight: '500',
|
|
850
|
+
fontSize: '14px',
|
|
851
|
+
transition: 'opacity 0.2s',
|
|
852
|
+
},
|
|
853
|
+
|
|
854
|
+
':host .revert-btn:hover:not(:disabled)': {
|
|
855
|
+
background: '#fef3c7',
|
|
856
|
+
borderColor: '#f59e0b',
|
|
857
|
+
color: '#92400e',
|
|
858
|
+
},
|
|
859
|
+
|
|
860
|
+
':host .revert-btn:disabled': {
|
|
861
|
+
cursor: 'default',
|
|
862
|
+
},
|
|
863
|
+
|
|
864
|
+
':host .elastic': {
|
|
865
|
+
flex: '1',
|
|
866
|
+
},
|
|
867
|
+
|
|
868
|
+
':host .status-bar': {
|
|
869
|
+
fontSize: '13px',
|
|
870
|
+
color: 'var(--text-color, #6b7280)',
|
|
871
|
+
opacity: '0.7',
|
|
872
|
+
},
|
|
873
|
+
|
|
874
|
+
':host .status-bar.error': {
|
|
875
|
+
color: '#dc2626',
|
|
876
|
+
opacity: '1',
|
|
877
|
+
},
|
|
878
|
+
|
|
879
|
+
':host .ts-main': {
|
|
880
|
+
display: 'flex',
|
|
881
|
+
flex: '1 1 auto',
|
|
882
|
+
minHeight: '0',
|
|
883
|
+
gap: '1px',
|
|
884
|
+
background: 'var(--code-border, #e5e7eb)',
|
|
885
|
+
},
|
|
886
|
+
|
|
887
|
+
':host .ts-input, :host .ts-output': {
|
|
888
|
+
flex: '1 1 50%',
|
|
889
|
+
minWidth: '0',
|
|
890
|
+
display: 'flex',
|
|
891
|
+
flexDirection: 'column',
|
|
892
|
+
background: 'var(--background, #fff)',
|
|
893
|
+
overflow: 'hidden',
|
|
894
|
+
},
|
|
895
|
+
|
|
896
|
+
':host .ts-input xin-tabs, :host .ts-output xin-tabs': {
|
|
897
|
+
flex: '1 1 auto',
|
|
898
|
+
display: 'flex',
|
|
899
|
+
flexDirection: 'column',
|
|
900
|
+
minHeight: '0',
|
|
901
|
+
},
|
|
902
|
+
|
|
903
|
+
':host xin-tabs > [name]': {
|
|
904
|
+
background: 'var(--background, #fff)',
|
|
905
|
+
color: 'var(--text-color, #1f2937)',
|
|
906
|
+
},
|
|
907
|
+
|
|
908
|
+
':host .editor-wrapper': {
|
|
909
|
+
flex: '1 1 auto',
|
|
910
|
+
height: '100%',
|
|
911
|
+
minHeight: '300px',
|
|
912
|
+
position: 'relative',
|
|
913
|
+
overflow: 'hidden',
|
|
914
|
+
},
|
|
915
|
+
|
|
916
|
+
':host .editor-wrapper code-mirror': {
|
|
917
|
+
display: 'block',
|
|
918
|
+
position: 'absolute',
|
|
919
|
+
top: '0',
|
|
920
|
+
left: '0',
|
|
921
|
+
right: '0',
|
|
922
|
+
bottom: '0',
|
|
923
|
+
},
|
|
924
|
+
|
|
925
|
+
':host .output-toolbar': {
|
|
926
|
+
display: 'flex',
|
|
927
|
+
gap: '8px',
|
|
928
|
+
padding: '8px 12px',
|
|
929
|
+
background: 'var(--code-background, #f3f4f6)',
|
|
930
|
+
borderBottom: '1px solid var(--code-border, #e5e7eb)',
|
|
931
|
+
},
|
|
932
|
+
|
|
933
|
+
':host .small-btn': {
|
|
934
|
+
display: 'flex',
|
|
935
|
+
alignItems: 'center',
|
|
936
|
+
gap: '4px',
|
|
937
|
+
padding: '4px 8px',
|
|
938
|
+
background: 'var(--background, #fff)',
|
|
939
|
+
color: 'var(--text-color, #374151)',
|
|
940
|
+
border: '1px solid var(--code-border, #d1d5db)',
|
|
941
|
+
borderRadius: '4px',
|
|
942
|
+
cursor: 'pointer',
|
|
943
|
+
fontSize: '12px',
|
|
944
|
+
},
|
|
945
|
+
|
|
946
|
+
':host .small-btn:hover': {
|
|
947
|
+
background: 'var(--code-background, #f3f4f6)',
|
|
948
|
+
},
|
|
949
|
+
|
|
950
|
+
':host .code-output': {
|
|
951
|
+
margin: '0',
|
|
952
|
+
padding: '12px',
|
|
953
|
+
background: 'var(--code-background, #f3f4f6)',
|
|
954
|
+
color: 'var(--text-color, #1f2937)',
|
|
955
|
+
fontSize: '13px',
|
|
956
|
+
fontFamily: 'ui-monospace, monospace',
|
|
957
|
+
overflow: 'auto',
|
|
958
|
+
height: '100%',
|
|
959
|
+
whiteSpace: 'pre-wrap',
|
|
960
|
+
},
|
|
961
|
+
|
|
962
|
+
':host .preview-container': {
|
|
963
|
+
height: '100%',
|
|
964
|
+
background: 'var(--background, #fff)',
|
|
965
|
+
},
|
|
966
|
+
|
|
967
|
+
':host .preview-frame': {
|
|
968
|
+
width: '100%',
|
|
969
|
+
height: '100%',
|
|
970
|
+
border: 'none',
|
|
971
|
+
},
|
|
972
|
+
|
|
973
|
+
':host .docs-output': {
|
|
974
|
+
display: 'block',
|
|
975
|
+
padding: '12px 16px',
|
|
976
|
+
fontSize: '14px',
|
|
977
|
+
fontFamily: 'system-ui, sans-serif',
|
|
978
|
+
color: 'var(--text-color, inherit)',
|
|
979
|
+
background: 'var(--background, #fff)',
|
|
980
|
+
height: '100%',
|
|
981
|
+
overflow: 'auto',
|
|
982
|
+
},
|
|
983
|
+
|
|
984
|
+
':host .tests-output': {
|
|
985
|
+
padding: '12px',
|
|
986
|
+
fontSize: '14px',
|
|
987
|
+
fontFamily: 'system-ui, sans-serif',
|
|
988
|
+
color: 'var(--text-color, inherit)',
|
|
989
|
+
background: 'var(--background, #fff)',
|
|
990
|
+
height: '100%',
|
|
991
|
+
overflow: 'auto',
|
|
992
|
+
},
|
|
993
|
+
|
|
994
|
+
':host .test-summary': {
|
|
995
|
+
marginBottom: '12px',
|
|
996
|
+
paddingBottom: '8px',
|
|
997
|
+
borderBottom: '1px solid var(--code-border, #e5e7eb)',
|
|
998
|
+
},
|
|
999
|
+
|
|
1000
|
+
':host .test-failed': {
|
|
1001
|
+
color: '#dc2626',
|
|
1002
|
+
},
|
|
1003
|
+
|
|
1004
|
+
':host .test-list': {
|
|
1005
|
+
listStyle: 'none',
|
|
1006
|
+
padding: 0,
|
|
1007
|
+
margin: 0,
|
|
1008
|
+
},
|
|
1009
|
+
|
|
1010
|
+
':host .test-list li': {
|
|
1011
|
+
padding: '4px 0',
|
|
1012
|
+
},
|
|
1013
|
+
|
|
1014
|
+
':host .test-pass': {
|
|
1015
|
+
color: '#16a34a',
|
|
1016
|
+
},
|
|
1017
|
+
|
|
1018
|
+
':host .test-fail': {
|
|
1019
|
+
color: '#dc2626',
|
|
1020
|
+
},
|
|
1021
|
+
|
|
1022
|
+
':host .test-error': {
|
|
1023
|
+
marginLeft: '20px',
|
|
1024
|
+
marginTop: '4px',
|
|
1025
|
+
padding: '8px',
|
|
1026
|
+
background: 'rgba(220, 38, 38, 0.1)',
|
|
1027
|
+
borderRadius: '4px',
|
|
1028
|
+
fontSize: '13px',
|
|
1029
|
+
fontFamily: 'var(--font-mono, monospace)',
|
|
1030
|
+
},
|
|
1031
|
+
|
|
1032
|
+
':host .clickable-error': {
|
|
1033
|
+
cursor: 'pointer',
|
|
1034
|
+
textDecoration: 'underline',
|
|
1035
|
+
textDecorationStyle: 'dotted',
|
|
1036
|
+
},
|
|
1037
|
+
|
|
1038
|
+
':host .clickable-error:hover': {
|
|
1039
|
+
background: 'rgba(220, 38, 38, 0.2)',
|
|
1040
|
+
},
|
|
1041
|
+
|
|
1042
|
+
':host .sig-badge': {
|
|
1043
|
+
fontSize: '11px',
|
|
1044
|
+
padding: '2px 6px',
|
|
1045
|
+
marginLeft: '8px',
|
|
1046
|
+
background: 'rgba(99, 102, 241, 0.1)',
|
|
1047
|
+
color: '#6366f1',
|
|
1048
|
+
borderRadius: '4px',
|
|
1049
|
+
},
|
|
1050
|
+
|
|
1051
|
+
':host .ts-console': {
|
|
1052
|
+
height: '120px',
|
|
1053
|
+
borderTop: '1px solid var(--code-border, #e5e7eb)',
|
|
1054
|
+
display: 'flex',
|
|
1055
|
+
flexDirection: 'column',
|
|
1056
|
+
},
|
|
1057
|
+
|
|
1058
|
+
':host .console-header': {
|
|
1059
|
+
padding: '4px 12px',
|
|
1060
|
+
background: 'var(--code-background, #f3f4f6)',
|
|
1061
|
+
fontSize: '12px',
|
|
1062
|
+
fontWeight: '500',
|
|
1063
|
+
color: 'var(--text-color, #6b7280)',
|
|
1064
|
+
opacity: '0.7',
|
|
1065
|
+
borderBottom: '1px solid var(--code-border, #e5e7eb)',
|
|
1066
|
+
},
|
|
1067
|
+
|
|
1068
|
+
':host .console-output': {
|
|
1069
|
+
flex: '1',
|
|
1070
|
+
margin: '0',
|
|
1071
|
+
padding: '8px 12px',
|
|
1072
|
+
background: 'var(--code-background, #f3f4f6)',
|
|
1073
|
+
color: 'var(--text-color, #1f2937)',
|
|
1074
|
+
fontSize: '12px',
|
|
1075
|
+
fontFamily: 'ui-monospace, monospace',
|
|
1076
|
+
overflow: 'auto',
|
|
1077
|
+
whiteSpace: 'pre-wrap',
|
|
1078
|
+
},
|
|
1079
|
+
|
|
1080
|
+
':host .clickable-line': {
|
|
1081
|
+
cursor: 'pointer',
|
|
1082
|
+
color: '#2563eb',
|
|
1083
|
+
textDecoration: 'underline',
|
|
1084
|
+
textDecorationStyle: 'dotted',
|
|
1085
|
+
},
|
|
1086
|
+
|
|
1087
|
+
':host .clickable-line:hover': {
|
|
1088
|
+
color: '#1d4ed8',
|
|
1089
|
+
background: 'rgba(37, 99, 235, 0.1)',
|
|
1090
|
+
},
|
|
1091
|
+
},
|
|
1092
|
+
}) as ElementCreator<TSPlayground>
|