tjs-lang 0.5.4 → 0.6.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/CLAUDE.md +33 -13
- package/README.md +4 -4
- package/bin/dev.ts +5 -1
- package/demo/docs.json +14 -2
- package/demo/index.html +2 -2
- package/demo/src/capabilities.ts +109 -2
- package/demo/src/demo-nav.ts +137 -203
- package/demo/src/imports.ts +43 -9
- package/demo/src/index.ts +179 -29
- package/demo/src/playground-shared.ts +11 -4
- package/demo/src/playground.ts +2 -2
- package/demo/src/tjs-playground.ts +294 -11
- package/demo/src/ts-playground.ts +239 -0
- package/dist/index.js +135 -127
- package/dist/index.js.map +6 -5
- package/dist/src/cli/commands/emit.d.ts +3 -0
- package/dist/src/lang/emitters/dts.d.ts +48 -0
- package/dist/src/lang/emitters/from-ts.d.ts +2 -0
- package/dist/src/lang/index.d.ts +1 -0
- package/dist/tjs-batteries.js +3 -3
- package/dist/tjs-batteries.js.map +2 -2
- package/dist/tjs-full.js +135 -127
- package/dist/tjs-full.js.map +6 -5
- package/dist/tjs-transpiler.js +2 -349
- package/dist/tjs-transpiler.js.map +4 -19
- package/package.json +1 -1
- package/src/cli/commands/emit.ts +26 -0
- package/src/cli/tjs.ts +4 -1
- package/src/lang/codegen.test.ts +55 -0
- package/src/lang/emitters/dts.test.ts +406 -0
- package/src/lang/emitters/dts.ts +588 -0
- package/src/lang/emitters/from-ts.ts +244 -20
- package/src/lang/index.ts +5 -0
- package/src/lang/typescript-syntax.test.ts +358 -0
package/demo/src/index.ts
CHANGED
|
@@ -9,14 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { elements, tosi, bindings, StyleSheet, bind, observe } from 'tosijs'
|
|
11
11
|
|
|
12
|
-
import {
|
|
13
|
-
icons,
|
|
14
|
-
sideNav,
|
|
15
|
-
SideNav,
|
|
16
|
-
sizeBreak,
|
|
17
|
-
popMenu,
|
|
18
|
-
markdownViewer,
|
|
19
|
-
} from 'tosijs-ui'
|
|
12
|
+
import { icons, sideNav, SideNav, popMenu, markdownViewer } from 'tosijs-ui'
|
|
20
13
|
|
|
21
14
|
import { styleSpec } from './style'
|
|
22
15
|
StyleSheet('demo-style', styleSpec)
|
|
@@ -92,14 +85,6 @@ Object.assign(window, { agent, tosijs, tosijsui, demoRuntime, runAgent })
|
|
|
92
85
|
import docs from '../docs.json'
|
|
93
86
|
|
|
94
87
|
// Add playgrounds as special pages
|
|
95
|
-
const ajsPlaygroundDoc = {
|
|
96
|
-
title: '▶ AJS Playground',
|
|
97
|
-
filename: 'playground',
|
|
98
|
-
text: '',
|
|
99
|
-
isPlayground: 'ajs',
|
|
100
|
-
pin: 'top',
|
|
101
|
-
}
|
|
102
|
-
|
|
103
88
|
const tjsPlaygroundDoc = {
|
|
104
89
|
title: '▶ TJS Playground',
|
|
105
90
|
filename: 'tjs-playground',
|
|
@@ -108,8 +93,16 @@ const tjsPlaygroundDoc = {
|
|
|
108
93
|
pin: 'top',
|
|
109
94
|
}
|
|
110
95
|
|
|
96
|
+
const ajsPlaygroundDoc = {
|
|
97
|
+
title: '▶ AJS Playground',
|
|
98
|
+
filename: 'playground',
|
|
99
|
+
text: '',
|
|
100
|
+
isPlayground: 'ajs',
|
|
101
|
+
pin: 'top',
|
|
102
|
+
}
|
|
103
|
+
|
|
111
104
|
// Insert playgrounds at top
|
|
112
|
-
const allDocs = [
|
|
105
|
+
const allDocs = [tjsPlaygroundDoc, ajsPlaygroundDoc, ...docs]
|
|
113
106
|
|
|
114
107
|
const PROJECT = 'tjs-lang'
|
|
115
108
|
declare const __VERSION__: string
|
|
@@ -138,6 +131,8 @@ const { app, prefs, auth } = tosi({
|
|
|
138
131
|
currentView: 'home' as 'home' | 'ajs' | 'tjs' | 'ts',
|
|
139
132
|
currentExample: null as any,
|
|
140
133
|
openSection: null as string | null,
|
|
134
|
+
splitMode: null as null | 'code' | 'output',
|
|
135
|
+
splitSessionId: '' as string,
|
|
141
136
|
},
|
|
142
137
|
auth: {
|
|
143
138
|
user: null as {
|
|
@@ -266,6 +261,11 @@ function syncURLToState() {
|
|
|
266
261
|
app.currentView.value = view
|
|
267
262
|
}
|
|
268
263
|
if (section) app.openSection.value = section
|
|
264
|
+
const mode = params.get('mode')
|
|
265
|
+
const newSplitMode = mode === 'code' || mode === 'output' ? mode : null
|
|
266
|
+
const sid = params.get('sid') || ''
|
|
267
|
+
app.splitSessionId.value = sid
|
|
268
|
+
app.splitMode.value = newSplitMode
|
|
269
269
|
|
|
270
270
|
if (example) {
|
|
271
271
|
if (view === 'ts') {
|
|
@@ -302,19 +302,26 @@ syncURLToState()
|
|
|
302
302
|
window.addEventListener('hashchange', syncURLToState)
|
|
303
303
|
|
|
304
304
|
// State → URL (observe changes, update hash)
|
|
305
|
-
observe(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
305
|
+
observe(
|
|
306
|
+
/^app\.(currentView|currentExample|openSection|splitMode|splitSessionId)/,
|
|
307
|
+
() => {
|
|
308
|
+
if (_suppressHashUpdate) return
|
|
309
|
+
const params = new URLSearchParams()
|
|
310
|
+
const view = app.currentView.valueOf() as string
|
|
311
|
+
params.set('view', view)
|
|
312
|
+
const example = app.currentExample.valueOf() as any
|
|
313
|
+
if (example) {
|
|
314
|
+
params.set('example', example.name || example.title || '')
|
|
315
|
+
}
|
|
316
|
+
const section = app.openSection.valueOf() as string | null
|
|
317
|
+
if (section) params.set('section', section)
|
|
318
|
+
const splitMode = app.splitMode.valueOf() as string | null
|
|
319
|
+
if (splitMode) params.set('mode', splitMode)
|
|
320
|
+
const sid = app.splitSessionId.valueOf() as string
|
|
321
|
+
if (sid) params.set('sid', sid)
|
|
322
|
+
window.history.replaceState(null, '', `#${params.toString()}`)
|
|
313
323
|
}
|
|
314
|
-
|
|
315
|
-
if (section) params.set('section', section)
|
|
316
|
-
window.history.replaceState(null, '', `#${params.toString()}`)
|
|
317
|
-
})
|
|
324
|
+
)
|
|
318
325
|
|
|
319
326
|
// Main app
|
|
320
327
|
const main = document.querySelector('main') as HTMLElement
|
|
@@ -387,6 +394,28 @@ if (main) {
|
|
|
387
394
|
icons.npm()
|
|
388
395
|
),
|
|
389
396
|
|
|
397
|
+
// tosijs link
|
|
398
|
+
a(
|
|
399
|
+
{
|
|
400
|
+
class: 'iconic',
|
|
401
|
+
title: 'tosijs',
|
|
402
|
+
target: '_blank',
|
|
403
|
+
href: 'https://tosijs.net',
|
|
404
|
+
},
|
|
405
|
+
icons.tosi()
|
|
406
|
+
),
|
|
407
|
+
|
|
408
|
+
// tosijs-ui link
|
|
409
|
+
a(
|
|
410
|
+
{
|
|
411
|
+
class: 'iconic',
|
|
412
|
+
title: 'tosijs-ui',
|
|
413
|
+
target: '_blank',
|
|
414
|
+
href: 'https://ui.tosijs.net',
|
|
415
|
+
},
|
|
416
|
+
icons.tosiUi()
|
|
417
|
+
),
|
|
418
|
+
|
|
390
419
|
// Settings menu
|
|
391
420
|
button(
|
|
392
421
|
{
|
|
@@ -572,6 +601,16 @@ if (main) {
|
|
|
572
601
|
icon: 'npm',
|
|
573
602
|
action: () => window.open(app.npmUrl.valueOf(), '_blank'),
|
|
574
603
|
},
|
|
604
|
+
{
|
|
605
|
+
caption: 'tosijs',
|
|
606
|
+
icon: 'tosi',
|
|
607
|
+
action: () => window.open('https://tosijs.net', '_blank'),
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
caption: 'tosijs-ui',
|
|
611
|
+
icon: 'tosiUi',
|
|
612
|
+
action: () => window.open('https://ui.tosijs.net', '_blank'),
|
|
613
|
+
},
|
|
575
614
|
],
|
|
576
615
|
})
|
|
577
616
|
},
|
|
@@ -724,6 +763,47 @@ if (main) {
|
|
|
724
763
|
},
|
|
725
764
|
})
|
|
726
765
|
|
|
766
|
+
// Apply split mode from URL on load (for output windows)
|
|
767
|
+
// Ping the partner window — if no pong, revert to normal
|
|
768
|
+
const initialMode = app.splitMode.valueOf() as string | null
|
|
769
|
+
const initialSid = app.splitSessionId.valueOf() as string
|
|
770
|
+
if (
|
|
771
|
+
initialMode &&
|
|
772
|
+
(initialMode === 'code' || initialMode === 'output') &&
|
|
773
|
+
app.currentView.valueOf() === 'tjs'
|
|
774
|
+
) {
|
|
775
|
+
const probe = new BroadcastChannel('tjs-playground')
|
|
776
|
+
let gotPong = false
|
|
777
|
+
probe.onmessage = (e: MessageEvent) => {
|
|
778
|
+
if (e.data?.type === 'pong' && e.data.sid === initialSid) {
|
|
779
|
+
gotPong = true
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
probe.postMessage({ type: 'ping', sid: initialSid })
|
|
783
|
+
setTimeout(() => {
|
|
784
|
+
probe.close()
|
|
785
|
+
if (gotPong) {
|
|
786
|
+
pg.setSplitMode(initialMode, initialSid || undefined)
|
|
787
|
+
} else {
|
|
788
|
+
// Partner is gone — revert to normal
|
|
789
|
+
app.splitMode.value = null
|
|
790
|
+
app.splitSessionId.value = ''
|
|
791
|
+
}
|
|
792
|
+
}, 300)
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Listen for split-mode-change from playground
|
|
796
|
+
// The component handles its own setSplitMode; this just syncs URL state
|
|
797
|
+
pg.addEventListener('split-mode-change', ((e: CustomEvent) => {
|
|
798
|
+
if (e.detail?.mode === 'code') {
|
|
799
|
+
app.splitSessionId.value = e.detail.sid
|
|
800
|
+
app.splitMode.value = 'code'
|
|
801
|
+
} else {
|
|
802
|
+
app.splitSessionId.value = ''
|
|
803
|
+
app.splitMode.value = null
|
|
804
|
+
}
|
|
805
|
+
}) as EventListener)
|
|
806
|
+
|
|
727
807
|
return pg
|
|
728
808
|
})(),
|
|
729
809
|
|
|
@@ -762,13 +842,83 @@ if (main) {
|
|
|
762
842
|
},
|
|
763
843
|
})
|
|
764
844
|
|
|
845
|
+
// Apply split mode from URL on load (for output windows)
|
|
846
|
+
// Ping the partner window — if no pong, revert to normal
|
|
847
|
+
const tsInitialMode = app.splitMode.valueOf() as string | null
|
|
848
|
+
const tsInitialSid = app.splitSessionId.valueOf() as string
|
|
849
|
+
if (
|
|
850
|
+
tsInitialMode &&
|
|
851
|
+
(tsInitialMode === 'code' || tsInitialMode === 'output') &&
|
|
852
|
+
app.currentView.valueOf() === 'ts'
|
|
853
|
+
) {
|
|
854
|
+
const probe = new BroadcastChannel('tjs-playground')
|
|
855
|
+
let gotPong = false
|
|
856
|
+
probe.onmessage = (e: MessageEvent) => {
|
|
857
|
+
if (e.data?.type === 'pong' && e.data.sid === tsInitialSid) {
|
|
858
|
+
gotPong = true
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
probe.postMessage({ type: 'ping', sid: tsInitialSid })
|
|
862
|
+
setTimeout(() => {
|
|
863
|
+
probe.close()
|
|
864
|
+
if (gotPong) {
|
|
865
|
+
pg.setSplitMode(tsInitialMode, tsInitialSid || undefined)
|
|
866
|
+
} else {
|
|
867
|
+
// Partner is gone — revert to normal
|
|
868
|
+
app.splitMode.value = null
|
|
869
|
+
app.splitSessionId.value = ''
|
|
870
|
+
}
|
|
871
|
+
}, 300)
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
// Listen for split-mode-change from playground
|
|
875
|
+
// The component handles its own setSplitMode; this just syncs URL state
|
|
876
|
+
pg.addEventListener('split-mode-change', ((e: CustomEvent) => {
|
|
877
|
+
if (e.detail?.mode === 'code') {
|
|
878
|
+
app.splitSessionId.value = e.detail.sid
|
|
879
|
+
app.splitMode.value = 'code'
|
|
880
|
+
} else {
|
|
881
|
+
app.splitSessionId.value = ''
|
|
882
|
+
app.splitMode.value = null
|
|
883
|
+
}
|
|
884
|
+
}) as EventListener)
|
|
885
|
+
|
|
765
886
|
return pg
|
|
766
887
|
})()
|
|
767
888
|
)
|
|
768
889
|
)
|
|
769
890
|
)
|
|
891
|
+
|
|
892
|
+
// In output mode, hide header and sidebar — just show the playground full-screen
|
|
893
|
+
if (app.splitMode.valueOf() === 'output') {
|
|
894
|
+
const headerEl = main.querySelector('header')
|
|
895
|
+
if (headerEl) headerEl.style.display = 'none'
|
|
896
|
+
const navEl = main.querySelector(SideNav.tagName!) as SideNav | null
|
|
897
|
+
if (navEl) {
|
|
898
|
+
navEl.style.gridTemplateColumns = '0 1fr'
|
|
899
|
+
const navSlot = navEl.querySelector('[slot="nav"]') as HTMLElement | null
|
|
900
|
+
if (navSlot) navSlot.style.display = 'none'
|
|
901
|
+
}
|
|
902
|
+
}
|
|
770
903
|
}
|
|
771
904
|
|
|
905
|
+
// Notify partner window on close/refresh
|
|
906
|
+
window.addEventListener('beforeunload', () => {
|
|
907
|
+
// Find the active playground and notify it to close its channel
|
|
908
|
+
const view = app.currentView.valueOf()
|
|
909
|
+
if (app.splitMode.valueOf()) {
|
|
910
|
+
if (view === 'tjs') {
|
|
911
|
+
const pg = document.querySelector(
|
|
912
|
+
'tjs-playground'
|
|
913
|
+
) as TJSPlayground | null
|
|
914
|
+
pg?.notifyClose()
|
|
915
|
+
} else if (view === 'ts') {
|
|
916
|
+
const pg = document.querySelector('ts-playground') as TSPlayground | null
|
|
917
|
+
pg?.notifyClose()
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
})
|
|
921
|
+
|
|
772
922
|
// Log welcome message
|
|
773
923
|
console.log(
|
|
774
924
|
`%c tjs-lang %c v${VERSION} `,
|
|
@@ -79,6 +79,8 @@ export interface IframeDocOptions {
|
|
|
79
79
|
parentBindings?: boolean
|
|
80
80
|
/** Auto-find and call TJS-annotated functions, append DOM results */
|
|
81
81
|
autoCallTjsFunction?: boolean
|
|
82
|
+
/** Whether parent is in dark mode — sets color-scheme on iframe */
|
|
83
|
+
darkMode?: boolean
|
|
82
84
|
}
|
|
83
85
|
|
|
84
86
|
/**
|
|
@@ -95,8 +97,11 @@ export function buildIframeDoc(options: IframeDocOptions): string {
|
|
|
95
97
|
importStatements = [],
|
|
96
98
|
parentBindings = false,
|
|
97
99
|
autoCallTjsFunction = false,
|
|
100
|
+
darkMode = false,
|
|
98
101
|
} = options
|
|
99
102
|
|
|
103
|
+
const colorScheme = darkMode ? 'dark' : 'light dark'
|
|
104
|
+
|
|
100
105
|
const parentBindingsScript = parentBindings
|
|
101
106
|
? `
|
|
102
107
|
if (parent.run) window.run = parent.run.bind(parent);
|
|
@@ -146,6 +151,7 @@ export function buildIframeDoc(options: IframeDocOptions): string {
|
|
|
146
151
|
return `<!DOCTYPE html>
|
|
147
152
|
<html>
|
|
148
153
|
<head>
|
|
154
|
+
<style>:root { color-scheme: ${colorScheme} }</style>
|
|
149
155
|
<style>${cssContent}</style>
|
|
150
156
|
${importMapScript}
|
|
151
157
|
</head>
|
|
@@ -158,9 +164,9 @@ ${TJS_RUNTIME_STUB}
|
|
|
158
164
|
${importStatements.join('\n ')}
|
|
159
165
|
${CONSOLE_CAPTURE_SCRIPT}
|
|
160
166
|
|
|
161
|
-
const __childrenBefore = document.body.
|
|
167
|
+
const __childrenBefore = document.body.childNodes.length;
|
|
162
168
|
try {${executionCode}
|
|
163
|
-
if (document.body.
|
|
169
|
+
if (document.body.childNodes.length > __childrenBefore) {
|
|
164
170
|
parent.postMessage({ type: 'hasPreviewContent' }, '*');
|
|
165
171
|
}
|
|
166
172
|
} catch (e) {
|
|
@@ -174,6 +180,7 @@ ${CONSOLE_CAPTURE_SCRIPT}
|
|
|
174
180
|
return `<!DOCTYPE html>
|
|
175
181
|
<html>
|
|
176
182
|
<head>
|
|
183
|
+
<style>:root { color-scheme: ${colorScheme} }</style>
|
|
177
184
|
<style>${cssContent}</style>
|
|
178
185
|
${importMapScript}
|
|
179
186
|
</head>
|
|
@@ -183,9 +190,9 @@ ${CONSOLE_CAPTURE_SCRIPT}
|
|
|
183
190
|
${TJS_RUNTIME_STUB}
|
|
184
191
|
${CONSOLE_CAPTURE_SCRIPT}
|
|
185
192
|
|
|
186
|
-
const __childrenBefore = document.body.
|
|
193
|
+
const __childrenBefore = document.body.childNodes.length;
|
|
187
194
|
try {${executionCode}
|
|
188
|
-
if (document.body.
|
|
195
|
+
if (document.body.childNodes.length > __childrenBefore) {
|
|
189
196
|
parent.postMessage({ type: 'hasPreviewContent' }, '*');
|
|
190
197
|
}
|
|
191
198
|
} catch (e) {
|
package/demo/src/playground.ts
CHANGED
|
@@ -867,11 +867,11 @@ export class Playground extends Component<PlaygroundParts> {
|
|
|
867
867
|
const isHttps = window.location.protocol === 'https:'
|
|
868
868
|
if (isHttps) {
|
|
869
869
|
throw new Error(
|
|
870
|
-
'No LLM configured. Go to Settings (⋮) > API Keys to add an OpenAI
|
|
870
|
+
'No LLM configured. Go to Settings (⋮) > API Keys to add an API key (OpenAI, Anthropic, Gemini, or Deepseek). Note: Local LLM endpoints require HTTP.'
|
|
871
871
|
)
|
|
872
872
|
} else {
|
|
873
873
|
throw new Error(
|
|
874
|
-
'No LLM configured. Go to Settings (⋮) > API Keys to add an
|
|
874
|
+
'No LLM configured. Go to Settings (⋮) > API Keys to add an API key (OpenAI, Anthropic, Gemini, Deepseek) or LM Studio endpoint (default: http://localhost:1234/v1).'
|
|
875
875
|
)
|
|
876
876
|
}
|
|
877
877
|
}
|