silvery 0.3.0 → 0.4.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 +41 -145
- package/dist/chalk.js +3 -0
- package/dist/chalk.js.map +11 -0
- package/dist/index.js +340 -0
- package/dist/index.js.map +282 -0
- package/dist/ink.js +129 -0
- package/dist/ink.js.map +140 -0
- package/dist/runtime.js +394 -0
- package/dist/runtime.js.map +286 -0
- package/dist/theme.js +343 -0
- package/dist/theme.js.map +286 -0
- package/dist/ui/animation.js +3 -0
- package/dist/ui/animation.js.map +15 -0
- package/dist/ui/ansi.js +3 -0
- package/dist/ui/ansi.js.map +10 -0
- package/dist/ui/cli.js +8 -0
- package/dist/ui/cli.js.map +14 -0
- package/dist/ui/display.js +4 -0
- package/dist/ui/display.js.map +10 -0
- package/dist/ui/image.js +4 -0
- package/dist/ui/image.js.map +15 -0
- package/dist/ui/input.js +3 -0
- package/dist/ui/input.js.map +11 -0
- package/dist/ui/progress.js +8 -0
- package/dist/ui/progress.js.map +20 -0
- package/dist/ui/react.js +3 -0
- package/dist/ui/react.js.map +15 -0
- package/dist/ui/utils.js +3 -0
- package/dist/ui/utils.js.map +10 -0
- package/dist/ui/wrappers.js +14 -0
- package/dist/ui/wrappers.js.map +19 -0
- package/dist/ui.js +17 -0
- package/dist/ui.js.map +20 -0
- package/package.json +67 -15
- package/src/index.ts +67 -1
- package/src/runtime.ts +4 -0
- package/src/theme.ts +4 -0
- package/src/ui/animation.ts +2 -0
- package/src/ui/ansi.ts +2 -0
- package/src/ui/cli.ts +2 -0
- package/src/ui/display.ts +2 -0
- package/src/ui/image.ts +2 -0
- package/src/ui/input.ts +2 -0
- package/src/ui/progress.ts +2 -0
- package/src/ui/react.ts +2 -0
- package/src/ui/utils.ts +2 -0
- package/src/ui/wrappers.ts +2 -0
- package/src/ui.ts +4 -0
- package/examples/CLAUDE.md +0 -75
- package/examples/_banner.tsx +0 -60
- package/examples/cli.ts +0 -228
- package/examples/index.md +0 -101
- package/examples/inline/inline-nontty.tsx +0 -98
- package/examples/inline/inline-progress.tsx +0 -79
- package/examples/inline/inline-simple.tsx +0 -63
- package/examples/inline/scrollback.tsx +0 -185
- package/examples/interactive/_input-debug.tsx +0 -110
- package/examples/interactive/_stdin-test.ts +0 -71
- package/examples/interactive/_textarea-bare.tsx +0 -45
- package/examples/interactive/aichat/components.tsx +0 -468
- package/examples/interactive/aichat/index.tsx +0 -207
- package/examples/interactive/aichat/script.ts +0 -460
- package/examples/interactive/aichat/state.ts +0 -326
- package/examples/interactive/aichat/types.ts +0 -19
- package/examples/interactive/app-todo.tsx +0 -198
- package/examples/interactive/async-data.tsx +0 -208
- package/examples/interactive/cli-wizard.tsx +0 -332
- package/examples/interactive/clipboard.tsx +0 -183
- package/examples/interactive/components.tsx +0 -463
- package/examples/interactive/data-explorer.tsx +0 -506
- package/examples/interactive/dev-tools.tsx +0 -379
- package/examples/interactive/explorer.tsx +0 -747
- package/examples/interactive/gallery.tsx +0 -652
- package/examples/interactive/inline-bench.tsx +0 -136
- package/examples/interactive/kanban.tsx +0 -267
- package/examples/interactive/layout-ref.tsx +0 -185
- package/examples/interactive/outline.tsx +0 -171
- package/examples/interactive/paste-demo.tsx +0 -198
- package/examples/interactive/scroll.tsx +0 -77
- package/examples/interactive/search-filter.tsx +0 -240
- package/examples/interactive/task-list.tsx +0 -279
- package/examples/interactive/terminal.tsx +0 -798
- package/examples/interactive/textarea.tsx +0 -103
- package/examples/interactive/theme.tsx +0 -336
- package/examples/interactive/transform.tsx +0 -256
- package/examples/interactive/virtual-10k.tsx +0 -413
- package/examples/kitty/canvas.tsx +0 -519
- package/examples/kitty/generate-samples.ts +0 -236
- package/examples/kitty/image-component.tsx +0 -273
- package/examples/kitty/images.tsx +0 -604
- package/examples/kitty/input.tsx +0 -371
- package/examples/kitty/keys.tsx +0 -378
- package/examples/kitty/paint.tsx +0 -1017
- package/examples/layout/dashboard.tsx +0 -551
- package/examples/layout/live-resize.tsx +0 -290
- package/examples/layout/overflow.tsx +0 -51
- package/examples/playground/README.md +0 -69
- package/examples/playground/build.ts +0 -61
- package/examples/playground/index.html +0 -420
- package/examples/playground/playground-app.tsx +0 -416
- package/examples/runtime/elm-counter.tsx +0 -206
- package/examples/runtime/hello-runtime.tsx +0 -73
- package/examples/runtime/pipe-composition.tsx +0 -184
- package/examples/runtime/run-counter.tsx +0 -78
- package/examples/runtime/runtime-counter.tsx +0 -197
- package/examples/screenshots/generate.tsx +0 -563
- package/examples/scrollback-perf.tsx +0 -230
- package/examples/viewer.tsx +0 -654
- package/examples/web/build.ts +0 -365
- package/examples/web/canvas-app.tsx +0 -80
- package/examples/web/canvas.html +0 -89
- package/examples/web/dom-app.tsx +0 -81
- package/examples/web/dom.html +0 -113
- package/examples/web/showcase-app.tsx +0 -107
- package/examples/web/showcase.html +0 -34
- package/examples/web/showcases/index.tsx +0 -56
- package/examples/web/viewer-app.tsx +0 -555
- package/examples/web/viewer.html +0 -30
- package/examples/web/xterm-app.tsx +0 -105
- package/examples/web/xterm.html +0 -118
|
@@ -1,420 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>silvery Canvas Playground</title>
|
|
7
|
-
<style>
|
|
8
|
-
:root {
|
|
9
|
-
--bg-primary: #1a1a2e;
|
|
10
|
-
--bg-secondary: #16213e;
|
|
11
|
-
--bg-tertiary: #0f0f1a;
|
|
12
|
-
--text-primary: #eee;
|
|
13
|
-
--text-secondary: #808080;
|
|
14
|
-
--accent-cyan: #4ec9b0;
|
|
15
|
-
--accent-blue: #9cdcfe;
|
|
16
|
-
--accent-yellow: #dcdcaa;
|
|
17
|
-
--border: #333;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
* {
|
|
21
|
-
box-sizing: border-box;
|
|
22
|
-
margin: 0;
|
|
23
|
-
padding: 0;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
body {
|
|
27
|
-
background: var(--bg-primary);
|
|
28
|
-
color: var(--text-primary);
|
|
29
|
-
font-family:
|
|
30
|
-
system-ui,
|
|
31
|
-
-apple-system,
|
|
32
|
-
"Segoe UI",
|
|
33
|
-
sans-serif;
|
|
34
|
-
min-height: 100vh;
|
|
35
|
-
display: flex;
|
|
36
|
-
flex-direction: column;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/* Header */
|
|
40
|
-
header {
|
|
41
|
-
padding: 16px 24px;
|
|
42
|
-
border-bottom: 1px solid var(--border);
|
|
43
|
-
display: flex;
|
|
44
|
-
align-items: center;
|
|
45
|
-
gap: 16px;
|
|
46
|
-
flex-wrap: wrap;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
header h1 {
|
|
50
|
-
color: var(--accent-cyan);
|
|
51
|
-
font-size: 1.4rem;
|
|
52
|
-
font-weight: 600;
|
|
53
|
-
white-space: nowrap;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
header .subtitle {
|
|
57
|
-
color: var(--text-secondary);
|
|
58
|
-
font-size: 0.9rem;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/* Preset buttons */
|
|
62
|
-
.preset-bar {
|
|
63
|
-
display: flex;
|
|
64
|
-
gap: 8px;
|
|
65
|
-
padding: 12px 24px;
|
|
66
|
-
border-bottom: 1px solid var(--border);
|
|
67
|
-
background: var(--bg-secondary);
|
|
68
|
-
flex-wrap: wrap;
|
|
69
|
-
align-items: center;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.preset-bar label {
|
|
73
|
-
color: var(--text-secondary);
|
|
74
|
-
font-size: 0.85rem;
|
|
75
|
-
margin-right: 4px;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.preset-btn {
|
|
79
|
-
background: var(--bg-tertiary);
|
|
80
|
-
color: var(--text-primary);
|
|
81
|
-
border: 1px solid var(--border);
|
|
82
|
-
border-radius: 4px;
|
|
83
|
-
padding: 6px 14px;
|
|
84
|
-
font-size: 0.85rem;
|
|
85
|
-
cursor: pointer;
|
|
86
|
-
transition: all 0.15s;
|
|
87
|
-
font-family: inherit;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
.preset-btn:hover {
|
|
91
|
-
border-color: var(--accent-cyan);
|
|
92
|
-
color: var(--accent-cyan);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.preset-btn.active {
|
|
96
|
-
background: var(--accent-cyan);
|
|
97
|
-
color: var(--bg-primary);
|
|
98
|
-
border-color: var(--accent-cyan);
|
|
99
|
-
font-weight: 600;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/* Main area */
|
|
103
|
-
.main {
|
|
104
|
-
display: flex;
|
|
105
|
-
flex: 1;
|
|
106
|
-
min-height: 0;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/* Canvas container */
|
|
110
|
-
.canvas-container {
|
|
111
|
-
flex: 1;
|
|
112
|
-
position: relative;
|
|
113
|
-
min-height: 400px;
|
|
114
|
-
display: flex;
|
|
115
|
-
align-items: stretch;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
#canvas {
|
|
119
|
-
display: block;
|
|
120
|
-
width: 100%;
|
|
121
|
-
height: 100%;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.canvas-size-label {
|
|
125
|
-
position: absolute;
|
|
126
|
-
bottom: 8px;
|
|
127
|
-
right: 8px;
|
|
128
|
-
background: rgba(0, 0, 0, 0.7);
|
|
129
|
-
color: var(--text-secondary);
|
|
130
|
-
font-size: 0.75rem;
|
|
131
|
-
padding: 2px 8px;
|
|
132
|
-
border-radius: 3px;
|
|
133
|
-
font-family: monospace;
|
|
134
|
-
pointer-events: none;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/* Sidebar */
|
|
138
|
-
.sidebar {
|
|
139
|
-
width: 320px;
|
|
140
|
-
border-left: 1px solid var(--border);
|
|
141
|
-
background: var(--bg-secondary);
|
|
142
|
-
overflow-y: auto;
|
|
143
|
-
padding: 20px;
|
|
144
|
-
flex-shrink: 0;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
.sidebar h3 {
|
|
148
|
-
color: var(--accent-blue);
|
|
149
|
-
font-size: 1rem;
|
|
150
|
-
margin-bottom: 12px;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
.sidebar p {
|
|
154
|
-
color: var(--text-secondary);
|
|
155
|
-
font-size: 0.85rem;
|
|
156
|
-
line-height: 1.6;
|
|
157
|
-
margin-bottom: 16px;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
.sidebar code {
|
|
161
|
-
background: var(--bg-tertiary);
|
|
162
|
-
padding: 1px 5px;
|
|
163
|
-
border-radius: 3px;
|
|
164
|
-
font-family: "SF Mono", "Fira Code", "JetBrains Mono", monospace;
|
|
165
|
-
font-size: 0.8rem;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.code-block {
|
|
169
|
-
background: var(--bg-tertiary);
|
|
170
|
-
border: 1px solid var(--border);
|
|
171
|
-
border-radius: 4px;
|
|
172
|
-
padding: 12px;
|
|
173
|
-
font-family: "SF Mono", "Fira Code", "JetBrains Mono", monospace;
|
|
174
|
-
font-size: 0.78rem;
|
|
175
|
-
line-height: 1.5;
|
|
176
|
-
overflow-x: auto;
|
|
177
|
-
white-space: pre;
|
|
178
|
-
color: var(--text-primary);
|
|
179
|
-
margin-bottom: 16px;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.code-block .keyword {
|
|
183
|
-
color: #c586c0;
|
|
184
|
-
}
|
|
185
|
-
.code-block .component {
|
|
186
|
-
color: #4ec9b0;
|
|
187
|
-
}
|
|
188
|
-
.code-block .string {
|
|
189
|
-
color: #ce9178;
|
|
190
|
-
}
|
|
191
|
-
.code-block .prop {
|
|
192
|
-
color: #9cdcfe;
|
|
193
|
-
}
|
|
194
|
-
.code-block .comment {
|
|
195
|
-
color: #6a9955;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.section {
|
|
199
|
-
margin-bottom: 24px;
|
|
200
|
-
padding-bottom: 20px;
|
|
201
|
-
border-bottom: 1px solid var(--border);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
.section:last-child {
|
|
205
|
-
border-bottom: none;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.feature-list {
|
|
209
|
-
list-style: none;
|
|
210
|
-
padding: 0;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.feature-list li {
|
|
214
|
-
color: var(--text-secondary);
|
|
215
|
-
font-size: 0.85rem;
|
|
216
|
-
padding: 4px 0;
|
|
217
|
-
padding-left: 16px;
|
|
218
|
-
position: relative;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.feature-list li::before {
|
|
222
|
-
content: "\2022";
|
|
223
|
-
color: var(--accent-cyan);
|
|
224
|
-
position: absolute;
|
|
225
|
-
left: 0;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
/* Loading state */
|
|
229
|
-
.loading {
|
|
230
|
-
display: flex;
|
|
231
|
-
align-items: center;
|
|
232
|
-
justify-content: center;
|
|
233
|
-
height: 100%;
|
|
234
|
-
color: var(--text-secondary);
|
|
235
|
-
font-size: 1.1rem;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
.loading::after {
|
|
239
|
-
content: "";
|
|
240
|
-
display: inline-block;
|
|
241
|
-
width: 20px;
|
|
242
|
-
height: 20px;
|
|
243
|
-
border: 2px solid var(--border);
|
|
244
|
-
border-top-color: var(--accent-cyan);
|
|
245
|
-
border-radius: 50%;
|
|
246
|
-
margin-left: 12px;
|
|
247
|
-
animation: spin 0.8s linear infinite;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
@keyframes spin {
|
|
251
|
-
to {
|
|
252
|
-
transform: rotate(360deg);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
/* Responsive */
|
|
257
|
-
@media (max-width: 900px) {
|
|
258
|
-
.main {
|
|
259
|
-
flex-direction: column;
|
|
260
|
-
}
|
|
261
|
-
.sidebar {
|
|
262
|
-
width: 100%;
|
|
263
|
-
border-left: none;
|
|
264
|
-
border-top: 1px solid var(--border);
|
|
265
|
-
max-height: 40vh;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
</style>
|
|
269
|
-
</head>
|
|
270
|
-
<body>
|
|
271
|
-
<header>
|
|
272
|
-
<h1>silvery Canvas Playground</h1>
|
|
273
|
-
<span class="subtitle">React components rendered to HTML5 Canvas via the silvery rendering pipeline</span>
|
|
274
|
-
</header>
|
|
275
|
-
|
|
276
|
-
<div class="preset-bar">
|
|
277
|
-
<label>Examples:</label>
|
|
278
|
-
<button class="preset-btn active" data-preset="hello">Hello World</button>
|
|
279
|
-
<button class="preset-btn" data-preset="text">Text Styles</button>
|
|
280
|
-
<button class="preset-btn" data-preset="colors">Colors</button>
|
|
281
|
-
<button class="preset-btn" data-preset="flexbox">Flexbox</button>
|
|
282
|
-
<button class="preset-btn" data-preset="borders">Borders</button>
|
|
283
|
-
<button class="preset-btn" data-preset="dashboard">Dashboard</button>
|
|
284
|
-
<button class="preset-btn" data-preset="responsive">Responsive</button>
|
|
285
|
-
</div>
|
|
286
|
-
|
|
287
|
-
<div class="main">
|
|
288
|
-
<div class="canvas-container">
|
|
289
|
-
<canvas id="canvas"></canvas>
|
|
290
|
-
<div class="canvas-size-label" id="size-label"></div>
|
|
291
|
-
</div>
|
|
292
|
-
|
|
293
|
-
<div class="sidebar">
|
|
294
|
-
<div class="section">
|
|
295
|
-
<h3>How It Works</h3>
|
|
296
|
-
<p>
|
|
297
|
-
silvery is a React renderer that computes layout
|
|
298
|
-
<strong>before</strong> content rendering. Components know their size during render via
|
|
299
|
-
<code>useContentRect()</code>, not after.
|
|
300
|
-
</p>
|
|
301
|
-
<ul class="feature-list">
|
|
302
|
-
<li>React reconciler builds an <code>InkxNode</code> tree</li>
|
|
303
|
-
<li>Flexx (pure JS flexbox) computes layout synchronously</li>
|
|
304
|
-
<li>Content renders to an OffscreenCanvas buffer</li>
|
|
305
|
-
<li>Buffer is drawn to the visible canvas element</li>
|
|
306
|
-
<li><code>requestAnimationFrame</code> drives re-renders on state changes</li>
|
|
307
|
-
</ul>
|
|
308
|
-
</div>
|
|
309
|
-
|
|
310
|
-
<div class="section">
|
|
311
|
-
<h3>Canvas Adapter API</h3>
|
|
312
|
-
<div class="code-block">
|
|
313
|
-
<span class="keyword">import</span> { <span class="component">renderToCanvas</span>,
|
|
314
|
-
<span class="component">Box</span>, <span class="component">Text</span>,
|
|
315
|
-
<span class="component">useContentRect</span> }
|
|
316
|
-
<span class="keyword">from</span>
|
|
317
|
-
<span class="string">'silvery/canvas'</span>;
|
|
318
|
-
|
|
319
|
-
<span class="keyword">function</span>
|
|
320
|
-
<span class="component">App</span>() { <span class="keyword">const</span> { width, height } =
|
|
321
|
-
<span class="component">useContentRect</span>(); <span class="keyword">return</span> ( <<span
|
|
322
|
-
class="component"
|
|
323
|
-
>Box</span
|
|
324
|
-
>
|
|
325
|
-
<span class="prop">borderStyle</span>=<span class="string">"single"</span>> <<span class="component"
|
|
326
|
-
>Text</span
|
|
327
|
-
>> {width}px x {height}px </<span class="component">Text</span>> </<span class="component"
|
|
328
|
-
>Box</span
|
|
329
|
-
>> ); }
|
|
330
|
-
|
|
331
|
-
<span class="keyword">const</span> canvas = document.getElementById(<span class="string">'canvas'</span>);
|
|
332
|
-
<span class="component">renderToCanvas</span>(<<span class="component">App</span>
|
|
333
|
-
/>, canvas);
|
|
334
|
-
</div>
|
|
335
|
-
</div>
|
|
336
|
-
|
|
337
|
-
<div class="section">
|
|
338
|
-
<h3>Render Targets</h3>
|
|
339
|
-
<p>
|
|
340
|
-
silvery supports multiple render targets through its
|
|
341
|
-
<code>RenderAdapter</code> interface:
|
|
342
|
-
</p>
|
|
343
|
-
<ul class="feature-list">
|
|
344
|
-
<li><strong>Terminal</strong> — ANSI character grid (production)</li>
|
|
345
|
-
<li><strong>Canvas 2D</strong> — pixel buffer (this demo)</li>
|
|
346
|
-
<li><strong>DOM</strong> — accessible HTML elements</li>
|
|
347
|
-
<li><strong>WebGL</strong> — GPU-accelerated (planned)</li>
|
|
348
|
-
</ul>
|
|
349
|
-
</div>
|
|
350
|
-
|
|
351
|
-
<div class="section">
|
|
352
|
-
<h3>Build & Run</h3>
|
|
353
|
-
<div class="code-block">
|
|
354
|
-
<span class="comment"># Build the playground bundle</span>
|
|
355
|
-
cd vendor/beorn-silvery bun run examples/playground/build.ts
|
|
356
|
-
|
|
357
|
-
<span class="comment"># Open in browser</span>
|
|
358
|
-
open examples/playground/index.html
|
|
359
|
-
</div>
|
|
360
|
-
<p>
|
|
361
|
-
For a full live-editing playground with Monaco editor and hot reload, see
|
|
362
|
-
<code>docs/playground-design.md</code>.
|
|
363
|
-
</p>
|
|
364
|
-
</div>
|
|
365
|
-
</div>
|
|
366
|
-
</div>
|
|
367
|
-
|
|
368
|
-
<script>
|
|
369
|
-
// Preset button handling
|
|
370
|
-
const buttons = document.querySelectorAll(".preset-btn")
|
|
371
|
-
const sizeLabel = document.getElementById("size-label")
|
|
372
|
-
const canvas = document.getElementById("canvas")
|
|
373
|
-
let currentPreset = "hello"
|
|
374
|
-
|
|
375
|
-
function setPreset(preset) {
|
|
376
|
-
currentPreset = preset
|
|
377
|
-
buttons.forEach((btn) => {
|
|
378
|
-
btn.classList.toggle("active", btn.dataset.preset === preset)
|
|
379
|
-
})
|
|
380
|
-
// Send message to the app (loaded via module script below)
|
|
381
|
-
window.postMessage({ type: "set-preset", preset }, "*")
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
buttons.forEach((btn) => {
|
|
385
|
-
btn.addEventListener("click", () => setPreset(btn.dataset.preset))
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
// Update size label
|
|
389
|
-
function updateSizeLabel() {
|
|
390
|
-
if (canvas && sizeLabel) {
|
|
391
|
-
sizeLabel.textContent = canvas.width + " x " + canvas.height
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const resizeObserver = new ResizeObserver(() => {
|
|
396
|
-
requestAnimationFrame(updateSizeLabel)
|
|
397
|
-
})
|
|
398
|
-
if (canvas) resizeObserver.observe(canvas)
|
|
399
|
-
updateSizeLabel()
|
|
400
|
-
|
|
401
|
-
// Keyboard shortcuts for switching presets
|
|
402
|
-
const presetKeys = {
|
|
403
|
-
1: "hello",
|
|
404
|
-
2: "text",
|
|
405
|
-
3: "colors",
|
|
406
|
-
4: "flexbox",
|
|
407
|
-
5: "borders",
|
|
408
|
-
6: "dashboard",
|
|
409
|
-
7: "responsive",
|
|
410
|
-
}
|
|
411
|
-
document.addEventListener("keydown", (e) => {
|
|
412
|
-
if (e.target.tagName === "INPUT" || e.target.tagName === "TEXTAREA") return
|
|
413
|
-
const preset = presetKeys[e.key]
|
|
414
|
-
if (preset) setPreset(preset)
|
|
415
|
-
})
|
|
416
|
-
</script>
|
|
417
|
-
|
|
418
|
-
<script type="module" src="./dist/playground-app.js"></script>
|
|
419
|
-
</body>
|
|
420
|
-
</html>
|