@dealdeploy/skl 0.1.7 → 0.1.8
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/.agents/skills/opentui/SKILL.md +198 -0
- package/.agents/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.agents/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.agents/skills/opentui/references/components/code-diff.md +496 -0
- package/.agents/skills/opentui/references/components/containers.md +412 -0
- package/.agents/skills/opentui/references/components/inputs.md +531 -0
- package/.agents/skills/opentui/references/components/text-display.md +384 -0
- package/.agents/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.agents/skills/opentui/references/core/api.md +506 -0
- package/.agents/skills/opentui/references/core/configuration.md +166 -0
- package/.agents/skills/opentui/references/core/gotchas.md +393 -0
- package/.agents/skills/opentui/references/core/patterns.md +448 -0
- package/.agents/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.agents/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.agents/skills/opentui/references/layout/patterns.md +444 -0
- package/.agents/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.agents/skills/opentui/references/react/api.md +435 -0
- package/.agents/skills/opentui/references/react/configuration.md +301 -0
- package/.agents/skills/opentui/references/react/gotchas.md +443 -0
- package/.agents/skills/opentui/references/react/patterns.md +501 -0
- package/.agents/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.agents/skills/opentui/references/solid/api.md +543 -0
- package/.agents/skills/opentui/references/solid/configuration.md +315 -0
- package/.agents/skills/opentui/references/solid/gotchas.md +415 -0
- package/.agents/skills/opentui/references/solid/patterns.md +558 -0
- package/.agents/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/.claude/settings.local.json +11 -0
- package/.claude/skills/opentui/SKILL.md +198 -0
- package/.claude/skills/opentui/references/animation/REFERENCE.md +431 -0
- package/.claude/skills/opentui/references/components/REFERENCE.md +143 -0
- package/.claude/skills/opentui/references/components/code-diff.md +496 -0
- package/.claude/skills/opentui/references/components/containers.md +412 -0
- package/.claude/skills/opentui/references/components/inputs.md +531 -0
- package/.claude/skills/opentui/references/components/text-display.md +384 -0
- package/.claude/skills/opentui/references/core/REFERENCE.md +145 -0
- package/.claude/skills/opentui/references/core/api.md +506 -0
- package/.claude/skills/opentui/references/core/configuration.md +166 -0
- package/.claude/skills/opentui/references/core/gotchas.md +393 -0
- package/.claude/skills/opentui/references/core/patterns.md +448 -0
- package/.claude/skills/opentui/references/keyboard/REFERENCE.md +511 -0
- package/.claude/skills/opentui/references/layout/REFERENCE.md +337 -0
- package/.claude/skills/opentui/references/layout/patterns.md +444 -0
- package/.claude/skills/opentui/references/react/REFERENCE.md +174 -0
- package/.claude/skills/opentui/references/react/api.md +435 -0
- package/.claude/skills/opentui/references/react/configuration.md +301 -0
- package/.claude/skills/opentui/references/react/gotchas.md +443 -0
- package/.claude/skills/opentui/references/react/patterns.md +501 -0
- package/.claude/skills/opentui/references/solid/REFERENCE.md +201 -0
- package/.claude/skills/opentui/references/solid/api.md +543 -0
- package/.claude/skills/opentui/references/solid/configuration.md +315 -0
- package/.claude/skills/opentui/references/solid/gotchas.md +415 -0
- package/.claude/skills/opentui/references/solid/patterns.md +558 -0
- package/.claude/skills/opentui/references/testing/REFERENCE.md +614 -0
- package/bun.lock +0 -1
- package/index.ts +163 -38
- package/package.json +1 -1
- package/update.ts +87 -0
|
@@ -0,0 +1,448 @@
|
|
|
1
|
+
# Core Patterns
|
|
2
|
+
|
|
3
|
+
## Composition Patterns
|
|
4
|
+
|
|
5
|
+
### Imperative Composition
|
|
6
|
+
|
|
7
|
+
Create renderables and compose with `.add()`:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { createCliRenderer, BoxRenderable, TextRenderable } from "@opentui/core"
|
|
11
|
+
|
|
12
|
+
const renderer = await createCliRenderer()
|
|
13
|
+
|
|
14
|
+
// Create parent
|
|
15
|
+
const container = new BoxRenderable(renderer, {
|
|
16
|
+
id: "container",
|
|
17
|
+
flexDirection: "column",
|
|
18
|
+
padding: 1,
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
// Create children
|
|
22
|
+
const header = new TextRenderable(renderer, {
|
|
23
|
+
id: "header",
|
|
24
|
+
content: "Header",
|
|
25
|
+
fg: "#00FF00",
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const body = new TextRenderable(renderer, {
|
|
29
|
+
id: "body",
|
|
30
|
+
content: "Body content",
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
// Compose tree
|
|
34
|
+
container.add(header)
|
|
35
|
+
container.add(body)
|
|
36
|
+
renderer.root.add(container)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Declarative Composition (Constructs)
|
|
40
|
+
|
|
41
|
+
Use VNode functions for cleaner composition:
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import { createCliRenderer, Box, Text, Input, delegate } from "@opentui/core"
|
|
45
|
+
|
|
46
|
+
const renderer = await createCliRenderer()
|
|
47
|
+
|
|
48
|
+
// Compose as function calls
|
|
49
|
+
const ui = Box(
|
|
50
|
+
{ flexDirection: "column", padding: 1 },
|
|
51
|
+
Text({ content: "Header", fg: "#00FF00" }),
|
|
52
|
+
Box(
|
|
53
|
+
{ flexDirection: "row", gap: 2 },
|
|
54
|
+
Text({ content: "Name:" }),
|
|
55
|
+
Input({ id: "name", placeholder: "Enter name..." }),
|
|
56
|
+
),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
renderer.root.add(ui)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Reusable Components
|
|
63
|
+
|
|
64
|
+
Create factory functions for reusable UI pieces:
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// Imperative factory
|
|
68
|
+
function createLabeledInput(
|
|
69
|
+
renderer: RenderContext,
|
|
70
|
+
props: { id: string; label: string; placeholder: string }
|
|
71
|
+
) {
|
|
72
|
+
const container = new BoxRenderable(renderer, {
|
|
73
|
+
id: `${props.id}-container`,
|
|
74
|
+
flexDirection: "row",
|
|
75
|
+
gap: 1,
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
container.add(new TextRenderable(renderer, {
|
|
79
|
+
id: `${props.id}-label`,
|
|
80
|
+
content: props.label,
|
|
81
|
+
}))
|
|
82
|
+
|
|
83
|
+
container.add(new InputRenderable(renderer, {
|
|
84
|
+
id: `${props.id}-input`,
|
|
85
|
+
placeholder: props.placeholder,
|
|
86
|
+
width: 20,
|
|
87
|
+
}))
|
|
88
|
+
|
|
89
|
+
return container
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Declarative factory
|
|
93
|
+
function LabeledInput(props: { id: string; label: string; placeholder: string }) {
|
|
94
|
+
return delegate(
|
|
95
|
+
{ focus: `${props.id}-input` },
|
|
96
|
+
Box(
|
|
97
|
+
{ flexDirection: "row", gap: 1 },
|
|
98
|
+
Text({ content: props.label }),
|
|
99
|
+
Input({
|
|
100
|
+
id: `${props.id}-input`,
|
|
101
|
+
placeholder: props.placeholder,
|
|
102
|
+
width: 20,
|
|
103
|
+
}),
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Focus Delegation
|
|
110
|
+
|
|
111
|
+
Route focus calls to nested elements:
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { delegate, Box, Input, Text } from "@opentui/core"
|
|
115
|
+
|
|
116
|
+
const form = delegate(
|
|
117
|
+
{
|
|
118
|
+
focus: "email-input", // Route .focus() to this child
|
|
119
|
+
blur: "email-input", // Route .blur() to this child
|
|
120
|
+
},
|
|
121
|
+
Box(
|
|
122
|
+
{ border: true, padding: 1 },
|
|
123
|
+
Text({ content: "Email:" }),
|
|
124
|
+
Input({ id: "email-input", placeholder: "you@example.com" }),
|
|
125
|
+
),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
// This focuses the input inside, not the box
|
|
129
|
+
form.focus()
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Event Handling
|
|
133
|
+
|
|
134
|
+
### Keyboard Events
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
const renderer = await createCliRenderer()
|
|
138
|
+
|
|
139
|
+
// Global keyboard handler
|
|
140
|
+
renderer.keyInput.on("keypress", (key) => {
|
|
141
|
+
if (key.name === "escape") {
|
|
142
|
+
renderer.destroy()
|
|
143
|
+
process.exit(0)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (key.ctrl && key.name === "c") {
|
|
147
|
+
// Ctrl+C handling (if exitOnCtrlC is false)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (key.name === "tab") {
|
|
151
|
+
// Tab navigation
|
|
152
|
+
focusNext()
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
// Paste events
|
|
157
|
+
renderer.keyInput.on("paste", (text) => {
|
|
158
|
+
currentInput?.setValue(currentInput.value + text)
|
|
159
|
+
})
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Component Events
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { InputRenderable, InputRenderableEvents } from "@opentui/core"
|
|
166
|
+
|
|
167
|
+
const input = new InputRenderable(renderer, {
|
|
168
|
+
id: "search",
|
|
169
|
+
placeholder: "Search...",
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
input.on(InputRenderableEvents.CHANGE, (value) => {
|
|
173
|
+
performSearch(value)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
// Select events
|
|
177
|
+
const select = new SelectRenderable(renderer, {
|
|
178
|
+
id: "menu",
|
|
179
|
+
options: [...],
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
select.on(SelectRenderableEvents.ITEM_SELECTED, (index, option) => {
|
|
183
|
+
handleSelection(option)
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
select.on(SelectRenderableEvents.SELECTION_CHANGED, (index, option) => {
|
|
187
|
+
showPreview(option)
|
|
188
|
+
})
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Mouse Events
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
const button = new BoxRenderable(renderer, {
|
|
195
|
+
id: "button",
|
|
196
|
+
border: true,
|
|
197
|
+
onMouseDown: (event) => {
|
|
198
|
+
button.setBackgroundColor("#444444")
|
|
199
|
+
},
|
|
200
|
+
onMouseUp: (event) => {
|
|
201
|
+
button.setBackgroundColor("#222222")
|
|
202
|
+
handleClick()
|
|
203
|
+
},
|
|
204
|
+
onMouseMove: (event) => {
|
|
205
|
+
// Hover effect
|
|
206
|
+
},
|
|
207
|
+
})
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## State Management
|
|
211
|
+
|
|
212
|
+
### Local State
|
|
213
|
+
|
|
214
|
+
Manage state in closures or objects:
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// Closure-based state
|
|
218
|
+
function createCounter(renderer: RenderContext) {
|
|
219
|
+
let count = 0
|
|
220
|
+
|
|
221
|
+
const display = new TextRenderable(renderer, {
|
|
222
|
+
id: "count",
|
|
223
|
+
content: `Count: ${count}`,
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
const increment = () => {
|
|
227
|
+
count++
|
|
228
|
+
display.setContent(`Count: ${count}`)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return { display, increment }
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Class-based state
|
|
235
|
+
class CounterWidget {
|
|
236
|
+
private count = 0
|
|
237
|
+
private display: TextRenderable
|
|
238
|
+
|
|
239
|
+
constructor(renderer: RenderContext) {
|
|
240
|
+
this.display = new TextRenderable(renderer, {
|
|
241
|
+
id: "count",
|
|
242
|
+
content: this.formatCount(),
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
private formatCount() {
|
|
247
|
+
return `Count: ${this.count}`
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
increment() {
|
|
251
|
+
this.count++
|
|
252
|
+
this.display.setContent(this.formatCount())
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
getRenderable() {
|
|
256
|
+
return this.display
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Focus Management
|
|
262
|
+
|
|
263
|
+
Track and manage focus across components:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
class FocusManager {
|
|
267
|
+
private focusables: Renderable[] = []
|
|
268
|
+
private currentIndex = 0
|
|
269
|
+
|
|
270
|
+
register(renderable: Renderable) {
|
|
271
|
+
this.focusables.push(renderable)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
focusNext() {
|
|
275
|
+
this.focusables[this.currentIndex]?.blur()
|
|
276
|
+
this.currentIndex = (this.currentIndex + 1) % this.focusables.length
|
|
277
|
+
this.focusables[this.currentIndex]?.focus()
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
focusPrevious() {
|
|
281
|
+
this.focusables[this.currentIndex]?.blur()
|
|
282
|
+
this.currentIndex = (this.currentIndex - 1 + this.focusables.length) % this.focusables.length
|
|
283
|
+
this.focusables[this.currentIndex]?.focus()
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Usage
|
|
288
|
+
const focusManager = new FocusManager()
|
|
289
|
+
focusManager.register(input1)
|
|
290
|
+
focusManager.register(input2)
|
|
291
|
+
focusManager.register(select1)
|
|
292
|
+
|
|
293
|
+
renderer.keyInput.on("keypress", (key) => {
|
|
294
|
+
if (key.name === "tab") {
|
|
295
|
+
key.shift ? focusManager.focusPrevious() : focusManager.focusNext()
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## Lifecycle Patterns
|
|
301
|
+
|
|
302
|
+
### Cleanup
|
|
303
|
+
|
|
304
|
+
Always clean up resources:
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
const renderer = await createCliRenderer()
|
|
308
|
+
|
|
309
|
+
// Track intervals/timeouts
|
|
310
|
+
const intervals: Timer[] = []
|
|
311
|
+
|
|
312
|
+
intervals.push(setInterval(() => {
|
|
313
|
+
updateClock()
|
|
314
|
+
}, 1000))
|
|
315
|
+
|
|
316
|
+
// Cleanup on exit
|
|
317
|
+
process.on("SIGINT", () => {
|
|
318
|
+
intervals.forEach(clearInterval)
|
|
319
|
+
renderer.destroy()
|
|
320
|
+
process.exit(0)
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
// Or use onDestroy callback
|
|
324
|
+
const renderer = await createCliRenderer({
|
|
325
|
+
onDestroy: () => {
|
|
326
|
+
intervals.forEach(clearInterval)
|
|
327
|
+
},
|
|
328
|
+
})
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Dynamic Updates
|
|
332
|
+
|
|
333
|
+
Update UI based on external data:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
async function createDashboard(renderer: RenderContext) {
|
|
337
|
+
const statsText = new TextRenderable(renderer, {
|
|
338
|
+
id: "stats",
|
|
339
|
+
content: "Loading...",
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
// Poll for updates
|
|
343
|
+
const updateStats = async () => {
|
|
344
|
+
const data = await fetchStats()
|
|
345
|
+
statsText.setContent(`CPU: ${data.cpu}% | Memory: ${data.memory}%`)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Initial load
|
|
349
|
+
await updateStats()
|
|
350
|
+
|
|
351
|
+
// Periodic updates
|
|
352
|
+
setInterval(updateStats, 5000)
|
|
353
|
+
|
|
354
|
+
return statsText
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## Layout Patterns
|
|
359
|
+
|
|
360
|
+
### Responsive Layout
|
|
361
|
+
|
|
362
|
+
Adapt to terminal size:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
const renderer = await createCliRenderer()
|
|
366
|
+
|
|
367
|
+
const mainPanel = new BoxRenderable(renderer, {
|
|
368
|
+
id: "main",
|
|
369
|
+
width: "100%",
|
|
370
|
+
height: "100%",
|
|
371
|
+
flexDirection: renderer.width > 80 ? "row" : "column",
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
// Listen for resize
|
|
375
|
+
process.stdout.on("resize", () => {
|
|
376
|
+
mainPanel.setFlexDirection(renderer.width > 80 ? "row" : "column")
|
|
377
|
+
})
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Split Panels
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
function createSplitView(renderer: RenderContext, ratio = 0.3) {
|
|
384
|
+
const container = new BoxRenderable(renderer, {
|
|
385
|
+
id: "split",
|
|
386
|
+
flexDirection: "row",
|
|
387
|
+
width: "100%",
|
|
388
|
+
height: "100%",
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
const left = new BoxRenderable(renderer, {
|
|
392
|
+
id: "left",
|
|
393
|
+
width: `${ratio * 100}%`,
|
|
394
|
+
border: true,
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
const right = new BoxRenderable(renderer, {
|
|
398
|
+
id: "right",
|
|
399
|
+
flexGrow: 1,
|
|
400
|
+
border: true,
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
container.add(left)
|
|
404
|
+
container.add(right)
|
|
405
|
+
|
|
406
|
+
return { container, left, right }
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
## Debugging Patterns
|
|
411
|
+
|
|
412
|
+
### Console Overlay
|
|
413
|
+
|
|
414
|
+
Use the built-in console for debugging:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
const renderer = await createCliRenderer({
|
|
418
|
+
consoleOptions: {
|
|
419
|
+
startInDebugMode: true,
|
|
420
|
+
},
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
// Show console
|
|
424
|
+
renderer.console.show()
|
|
425
|
+
|
|
426
|
+
// All console methods work
|
|
427
|
+
console.log("Debug info")
|
|
428
|
+
console.warn("Warning")
|
|
429
|
+
console.error("Error")
|
|
430
|
+
|
|
431
|
+
// Toggle with keyboard
|
|
432
|
+
renderer.keyInput.on("keypress", (key) => {
|
|
433
|
+
if (key.name === "f12") {
|
|
434
|
+
renderer.console.toggle()
|
|
435
|
+
}
|
|
436
|
+
})
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
### State Inspection
|
|
440
|
+
|
|
441
|
+
```typescript
|
|
442
|
+
function debugState(label: string, state: unknown) {
|
|
443
|
+
console.log(`[${label}]`, JSON.stringify(state, null, 2))
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// In your update logic
|
|
447
|
+
debugState("form", { name: nameInput.value, email: emailInput.value })
|
|
448
|
+
```
|