tosijs-ui 1.6.21 → 1.6.22
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/CHANGELOG.md +36 -0
- package/dist/code-editor.d.ts +9 -35
- package/dist/code-editor.js +62 -90
- package/dist/doc-system/site/dev-server.js +56 -4
- package/dist/doc-system/site/epub-cli.d.ts +1 -0
- package/dist/doc-system/site/epub-cli.js +22 -0
- package/dist/doc-system/site/epub.js +6 -0
- package/dist/doc-system/site/make-llms-txt.js +0 -16
- package/dist/doc-system/site/orchestrator.js +102 -47
- package/dist/doc-system/site/site-config.d.ts +10 -0
- package/dist/iife.js +109 -153
- package/dist/iife.js.map +6 -34
- package/dist/live-example/component.js +14 -192
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/llms.txt +2 -9
- package/package.json +1 -16
- package/tls/create-dev-certs.sh +9 -25
- package/dist/code-editor-cm.d.ts +0 -48
- package/dist/code-editor-cm.js +0 -219
|
@@ -33,38 +33,6 @@ preview.append('Try editing some code and hitting refresh…')
|
|
|
33
33
|
}
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
## How examples run: one shared page (read this)
|
|
37
|
-
|
|
38
|
-
By default every example on a page runs **inline — in the page's own document and
|
|
39
|
-
JavaScript realm**, not in a sandbox. Each example gets its own `preview` element
|
|
40
|
-
(and its `css` is scoped to that preview), but they all share the one `tosijs`
|
|
41
|
-
module, so **`tosi()` state singletons are shared across every example on the page**.
|
|
42
|
-
|
|
43
|
-
This is a deliberate choice, and it buys two things:
|
|
44
|
-
|
|
45
|
-
1. It's a live demonstration of how clean tosi's isolation is — many independent
|
|
46
|
-
components mount into one page and coexist without stepping on each other's DOM.
|
|
47
|
-
2. You can drive any demo from the console (or from another example) through the same
|
|
48
|
-
global singleton — the state is right there, not walled off in a frame.
|
|
49
|
-
|
|
50
|
-
The trade-off is the gotcha to know about: **examples can see and clobber each
|
|
51
|
-
other's state.** Two examples that both do `tosi({ app: … })` bind to the *same*
|
|
52
|
-
`app` singleton (the tosi registry is keyed by the top-level name), so one overwrites
|
|
53
|
-
the other — and you get "impossible" bugs where an example misbehaves only when some
|
|
54
|
-
*other* example happens to be on the same page. Worth it, but plan for it:
|
|
55
|
-
|
|
56
|
-
- **Namespace your state.** Give each example a unique top-level key
|
|
57
|
-
(`tosi({ ratingDemo: … })`, not a generic `tosi({ app: … })`).
|
|
58
|
-
- Prefer local variables and `preview`-scoped DOM over shared singletons when a demo
|
|
59
|
-
doesn't need to be globally reachable.
|
|
60
|
-
- In `test` blocks, assert with **counts / deltas**, not presence/absence — other
|
|
61
|
-
examples may have left their elements in the DOM.
|
|
62
|
-
|
|
63
|
-
For real isolation of the **DOM and CSS**, add the `iframe` attribute (below). Note
|
|
64
|
-
it does **not** isolate tosijs *state*: the `tosijs`/`tosijs-ui` handed to an iframe
|
|
65
|
-
example are still the host page's module instances, so `tosi()` singletons stay
|
|
66
|
-
shared. Namespacing is the fix for state; `iframe` is the fix for DOM/CSS bleed.
|
|
67
|
-
|
|
68
36
|
## Source dialects: `js`, `tjs`, `ts`
|
|
69
37
|
|
|
70
38
|
The executable block's fence language picks how the source is compiled before it
|
|
@@ -108,157 +76,9 @@ example the equivalent is written inside a comment (so it survives `tsc`) — bu
|
|
|
108
76
|
that comment form can't appear inside a doc comment like this one, since the
|
|
109
77
|
test's own closing delimiter would end the doc comment.
|
|
110
78
|
|
|
111
|
-
## Inline WebAssembly (SIMD)
|
|
112
|
-
|
|
113
|
-
A `tjs` example can drop a hot loop into **WebAssembly** with a `wasm { … } fallback
|
|
114
|
-
{ … }` block — compiled to bytecode *at transpile time*, embedded as base64, and run
|
|
115
|
-
in your browser. No Emscripten, no `.wasm` file, no build step, no server.
|
|
116
|
-
|
|
117
|
-
WASM earns its keep with **SIMD**: scalar WASM roughly ties the JS JIT, but a
|
|
118
|
-
branchless `f32x4` kernel does four values per instruction. Below, a field of a few
|
|
119
|
-
thousand particles is held on a grid by springs and pushed aside by your pointer —
|
|
120
|
-
every particle's position updated four at a time in WASM SIMD. **Click the button to
|
|
121
|
-
toggle WASM ↔ JavaScript** and watch the per-step time — the same code, one path
|
|
122
|
-
compiled to SIMD bytecode:
|
|
123
|
-
|
|
124
|
-
```tjs
|
|
125
|
-
// One physics step for the whole field, 4 particles per iteration with f32x4 SIMD:
|
|
126
|
-
// a spring pulls each particle to its home cell, and an inverse-square push shoves
|
|
127
|
-
// it away from the pointer. Pure arithmetic — no per-lane branches (tjs's f32x4 has
|
|
128
|
-
// no compare/select yet), which is exactly what SIMD wants. `fallback { }` is the
|
|
129
|
-
// scalar-JS twin.
|
|
130
|
-
function step(! px: Float32Array, py: Float32Array, vx: Float32Array, vy: Float32Array, hx: Float32Array, hy: Float32Array, n: 0, tx: 0.0, ty: 0.0) {
|
|
131
|
-
wasm {
|
|
132
|
-
let txv = f32x4_splat(tx)
|
|
133
|
-
let tyv = f32x4_splat(ty)
|
|
134
|
-
let spring = f32x4_splat(0.03)
|
|
135
|
-
let damp = f32x4_splat(0.86)
|
|
136
|
-
let repel = f32x4_splat(1600.0)
|
|
137
|
-
let soft = f32x4_splat(140.0)
|
|
138
|
-
for (let i = 0; i < n; i = i + 4) {
|
|
139
|
-
let off = i * 4
|
|
140
|
-
let x = f32x4_load(px, off)
|
|
141
|
-
let y = f32x4_load(py, off)
|
|
142
|
-
let sx = f32x4_mul(f32x4_sub(f32x4_load(hx, off), x), spring)
|
|
143
|
-
let sy = f32x4_mul(f32x4_sub(f32x4_load(hy, off), y), spring)
|
|
144
|
-
let rx = f32x4_sub(x, txv)
|
|
145
|
-
let ry = f32x4_sub(y, tyv)
|
|
146
|
-
let push = f32x4_div(repel, f32x4_add(f32x4_add(f32x4_mul(rx, rx), f32x4_mul(ry, ry)), soft))
|
|
147
|
-
let nvx = f32x4_mul(f32x4_add(f32x4_load(vx, off), f32x4_add(sx, f32x4_mul(rx, push))), damp)
|
|
148
|
-
let nvy = f32x4_mul(f32x4_add(f32x4_load(vy, off), f32x4_add(sy, f32x4_mul(ry, push))), damp)
|
|
149
|
-
f32x4_store(vx, off, nvx)
|
|
150
|
-
f32x4_store(vy, off, nvy)
|
|
151
|
-
f32x4_store(px, off, f32x4_add(x, nvx))
|
|
152
|
-
f32x4_store(py, off, f32x4_add(y, nvy))
|
|
153
|
-
}
|
|
154
|
-
} fallback {
|
|
155
|
-
for (let i = 0; i < n; i++) {
|
|
156
|
-
const sx = (hx[i] - px[i]) * 0.03, sy = (hy[i] - py[i]) * 0.03
|
|
157
|
-
const rx = px[i] - tx, ry = py[i] - ty
|
|
158
|
-
const push = 1600.0 / (rx * rx + ry * ry + 140.0)
|
|
159
|
-
const nvx = (vx[i] + sx + rx * push) * 0.86, nvy = (vy[i] + sy + ry * push) * 0.86
|
|
160
|
-
vx[i] = nvx; vy[i] = nvy; px[i] += nvx; py[i] += nvy
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const W = 480, H = 300, cols = 100, rows = 60, N = cols * rows
|
|
166
|
-
const px = new Float32Array(N), py = new Float32Array(N)
|
|
167
|
-
const vx = new Float32Array(N), vy = new Float32Array(N)
|
|
168
|
-
const hx = new Float32Array(N), hy = new Float32Array(N)
|
|
169
|
-
for (let i = 0; i < N; i++) {
|
|
170
|
-
const c = i % cols, r = (i / cols) | 0
|
|
171
|
-
hx[i] = (c + 0.5) * W / cols
|
|
172
|
-
hy[i] = (r + 0.5) * H / rows
|
|
173
|
-
px[i] = hx[i]
|
|
174
|
-
py[i] = hy[i]
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const canvas = document.createElement('canvas')
|
|
178
|
-
canvas.width = W
|
|
179
|
-
canvas.height = H
|
|
180
|
-
canvas.style.cssText = 'width:100%;max-width:480px;border-radius:8px;display:block;background:#0b0e14;touch-action:none;cursor:crosshair'
|
|
181
|
-
const ctx = canvas.getContext('2d')
|
|
182
|
-
|
|
183
|
-
let tx = W / 2, ty = H / 2, idle = 0, frame = 0
|
|
184
|
-
canvas.addEventListener('pointermove', (e) => {
|
|
185
|
-
const r = canvas.getBoundingClientRect()
|
|
186
|
-
tx = (e.clientX - r.left) / r.width * W
|
|
187
|
-
ty = (e.clientY - r.top) / r.height * H
|
|
188
|
-
idle = 0
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
// tjs registers the compiled function as globalThis.__tjs_wasm_0; nulling it makes
|
|
192
|
-
// step() fall to its JS twin. (A public WASM on/off toggle would be tidier — noted
|
|
193
|
-
// in TJS-FEEDBACK.md.)
|
|
194
|
-
let useWasm = true, savedWasm = null
|
|
195
|
-
const btn = document.createElement('button')
|
|
196
|
-
btn.style.cssText = 'margin:8px 0;padding:6px 12px;border-radius:6px;cursor:pointer'
|
|
197
|
-
const readout = document.createElement('p')
|
|
198
|
-
function applyMode() {
|
|
199
|
-
if (useWasm && savedWasm) { globalThis.__tjs_wasm_0 = savedWasm; savedWasm = null }
|
|
200
|
-
if (!useWasm && globalThis.__tjs_wasm_0) { savedWasm = globalThis.__tjs_wasm_0; globalThis.__tjs_wasm_0 = null }
|
|
201
|
-
btn.textContent = useWasm ? '⚡ WebAssembly SIMD — click for JavaScript' : '🐢 JavaScript — click for WASM SIMD'
|
|
202
|
-
}
|
|
203
|
-
btn.onclick = () => { useWasm = !useWasm; applyMode() }
|
|
204
|
-
|
|
205
|
-
let acc = 0, frames = 0
|
|
206
|
-
function loop() {
|
|
207
|
-
if (!document.body.contains(canvas)) return // example removed → stop
|
|
208
|
-
frame++
|
|
209
|
-
idle++
|
|
210
|
-
if (idle > 45) {
|
|
211
|
-
tx = W / 2 + Math.cos(frame * 0.02) * W * 0.32
|
|
212
|
-
ty = H / 2 + Math.sin(frame * 0.031) * H * 0.32
|
|
213
|
-
}
|
|
214
|
-
const t0 = performance.now()
|
|
215
|
-
step(px, py, vx, vy, hx, hy, N, tx, ty)
|
|
216
|
-
acc += performance.now() - t0
|
|
217
|
-
frames++
|
|
218
|
-
ctx.fillStyle = 'rgba(11,14,20,0.3)'
|
|
219
|
-
ctx.fillRect(0, 0, W, H)
|
|
220
|
-
ctx.fillStyle = useWasm ? '#4fd1c5' : '#f6ad55'
|
|
221
|
-
for (let i = 0; i < N; i++) ctx.fillRect(px[i], py[i], 1.6, 1.6)
|
|
222
|
-
if (frames >= 20) {
|
|
223
|
-
readout.textContent = N.toLocaleString() + ' particles · ' + (acc / frames).toFixed(3) +
|
|
224
|
-
' ms/step (' + (useWasm ? 'WASM SIMD' : 'JS') + ') · move the pointer'
|
|
225
|
-
acc = 0
|
|
226
|
-
frames = 0
|
|
227
|
-
}
|
|
228
|
-
requestAnimationFrame(loop)
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
;(async () => {
|
|
232
|
-
// The wasm bootstrap is async — wait for the compiled function before starting,
|
|
233
|
-
// so the first frames run in WASM rather than the fallback.
|
|
234
|
-
const t0 = performance.now()
|
|
235
|
-
while (!globalThis.__tjs_wasm_0 && performance.now() - t0 < 3000) {
|
|
236
|
-
await new Promise((r) => setTimeout(r, 0))
|
|
237
|
-
}
|
|
238
|
-
applyMode()
|
|
239
|
-
requestAnimationFrame(loop)
|
|
240
|
-
})()
|
|
241
|
-
|
|
242
|
-
preview.append(canvas, btn, readout)
|
|
243
|
-
```
|
|
244
|
-
|
|
245
79
|
## CSS Isolation with `iframe`
|
|
246
80
|
|
|
247
|
-
Add the `iframe` attribute to render the preview inside an iframe
|
|
248
|
-
its own document, CSS scope, and custom-element registry. Reach for it when a demo's
|
|
249
|
-
styles would otherwise leak into (or get leaked on by) the rest of the page.
|
|
250
|
-
|
|
251
|
-
It isolates **DOM and CSS, not state.** The `tosijs`/`tosijs-ui` modules injected
|
|
252
|
-
into the iframe are the host page's own instances, so `tosi()` singletons remain
|
|
253
|
-
shared across every example (see "How examples run" above). Namespace your state to
|
|
254
|
-
keep examples from stomping each other; use `iframe` when you need visual/DOM
|
|
255
|
-
isolation.
|
|
256
|
-
|
|
257
|
-
*Fully-isolated examples (a separate module realm, so even `tosi()` state and
|
|
258
|
-
imported dependencies are sandboxed — the way tjs-lang's playgrounds do it with a
|
|
259
|
-
service worker intercepting imports) are a possible future option. For **actual
|
|
260
|
-
examples** the shared-page default is usually the nicer behavior: it shows off tosi's
|
|
261
|
-
isolation and lets you poke demos through the live global state.*
|
|
81
|
+
Add the `iframe` attribute to render the preview inside an iframe for complete CSS isolation.
|
|
262
82
|
|
|
263
83
|
## Test Blocks
|
|
264
84
|
|
|
@@ -598,11 +418,8 @@ export class LiveExample extends Component {
|
|
|
598
418
|
this.productTabsReady = true;
|
|
599
419
|
const { editors } = this.parts;
|
|
600
420
|
// Relabel the source tab from "js" to the actual dialect (the `part` stays
|
|
601
|
-
// `js`, so everything referencing this.parts.js / this.js is unaffected)
|
|
602
|
-
// put the source editor in the dialect's mode — so a `tjs` example gets
|
|
603
|
-
// first-class tjs editing (highlighting + autocomplete), `ts` gets TypeScript.
|
|
421
|
+
// `js`, so everything referencing this.parts.js / this.js is unaffected).
|
|
604
422
|
this.parts.js.setAttribute('name', this.dialect);
|
|
605
|
-
this.parts.js.mode = this.dialect;
|
|
606
423
|
this.jsOutEditor = codeEditor({
|
|
607
424
|
name: 'JS',
|
|
608
425
|
mode: 'javascript',
|
|
@@ -622,9 +439,10 @@ export class LiveExample extends Component {
|
|
|
622
439
|
updateUndo = () => {
|
|
623
440
|
const { activeTab } = this;
|
|
624
441
|
const { undo, redo } = this.parts;
|
|
625
|
-
if (activeTab instanceof CodeEditor) {
|
|
626
|
-
|
|
627
|
-
|
|
442
|
+
if (activeTab instanceof CodeEditor && activeTab.editor !== undefined) {
|
|
443
|
+
const undoManager = activeTab.editor.session.getUndoManager();
|
|
444
|
+
undo.disabled = !undoManager.hasUndo();
|
|
445
|
+
redo.disabled = !undoManager.hasRedo();
|
|
628
446
|
}
|
|
629
447
|
else {
|
|
630
448
|
undo.disabled = true;
|
|
@@ -647,13 +465,13 @@ export class LiveExample extends Component {
|
|
|
647
465
|
undo = () => {
|
|
648
466
|
const { activeTab } = this;
|
|
649
467
|
if (activeTab instanceof CodeEditor) {
|
|
650
|
-
activeTab.undo();
|
|
468
|
+
activeTab.editor.undo();
|
|
651
469
|
}
|
|
652
470
|
};
|
|
653
471
|
redo = () => {
|
|
654
472
|
const { activeTab } = this;
|
|
655
473
|
if (activeTab instanceof CodeEditor) {
|
|
656
|
-
activeTab.redo();
|
|
474
|
+
activeTab.editor.redo();
|
|
657
475
|
}
|
|
658
476
|
};
|
|
659
477
|
get isMaximized() {
|
|
@@ -943,11 +761,15 @@ export class LiveExample extends Component {
|
|
|
943
761
|
}
|
|
944
762
|
canUndo() {
|
|
945
763
|
const t = this.activeTab;
|
|
946
|
-
return t instanceof CodeEditor &&
|
|
764
|
+
return (t instanceof CodeEditor &&
|
|
765
|
+
t.editor !== undefined &&
|
|
766
|
+
t.editor.session.getUndoManager().hasUndo());
|
|
947
767
|
}
|
|
948
768
|
canRedo() {
|
|
949
769
|
const t = this.activeTab;
|
|
950
|
-
return t instanceof CodeEditor &&
|
|
770
|
+
return (t instanceof CodeEditor &&
|
|
771
|
+
t.editor !== undefined &&
|
|
772
|
+
t.editor.session.getUndoManager().hasRedo());
|
|
951
773
|
}
|
|
952
774
|
saveLocalEdit = () => {
|
|
953
775
|
const key = this.localEditKey();
|