@opentui/solid 0.0.0-20250908-4906ddad
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/LICENSE +21 -0
- package/README.md +38 -0
- package/index.d.ts +7 -0
- package/index.js +628 -0
- package/jsx-runtime.d.ts +31 -0
- package/package.json +42 -0
- package/scripts/preload.ts +4 -0
- package/scripts/solid-plugin.ts +47 -0
- package/src/elements/hooks.d.ts +20 -0
- package/src/elements/index.d.ts +29 -0
- package/src/elements/text-node.d.ts +48 -0
- package/src/reconciler.d.ts +4 -0
- package/src/types/elements.d.ts +67 -0
- package/src/utils/id-counter.d.ts +1 -0
- package/src/utils/log.d.ts +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 opentui
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# @opentui/solid
|
|
2
|
+
|
|
3
|
+
Solid.js support for [OpenTUI](https://github.com/sst/opentui).
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install solid-js @opentui/solid
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
1. Add jsx config to tsconfig.json:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"compilerOptions": {
|
|
18
|
+
"jsx": "preserve",
|
|
19
|
+
"jsxImportSource": "@opentui/solid"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
2. Add preload script to bunfig.toml:
|
|
25
|
+
|
|
26
|
+
```toml
|
|
27
|
+
preload = ["@opentui/solid/preload"]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
3. Add render function to index.tsx:
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { render } from "@opentui/solid"
|
|
34
|
+
|
|
35
|
+
render(() => <text>Hello, World!</text>)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
4. Run with `bun index.tsx`.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type CliRendererConfig } from "@opentui/core";
|
|
2
|
+
import type { JSX } from "./jsx-runtime";
|
|
3
|
+
export declare const render: (node: () => JSX.Element, renderConfig?: CliRendererConfig) => Promise<void>;
|
|
4
|
+
export * from "./src/reconciler";
|
|
5
|
+
export * from "./src/elements";
|
|
6
|
+
export * from "./src/types/elements";
|
|
7
|
+
export { type JSX };
|
package/index.js
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// index.ts
|
|
3
|
+
import { createCliRenderer } from "@opentui/core";
|
|
4
|
+
|
|
5
|
+
// src/elements/index.ts
|
|
6
|
+
import {
|
|
7
|
+
ASCIIFontRenderable,
|
|
8
|
+
BoxRenderable,
|
|
9
|
+
InputRenderable,
|
|
10
|
+
ScrollBoxRenderable,
|
|
11
|
+
SelectRenderable,
|
|
12
|
+
TabSelectRenderable,
|
|
13
|
+
TextRenderable
|
|
14
|
+
} from "@opentui/core";
|
|
15
|
+
|
|
16
|
+
// src/elements/hooks.ts
|
|
17
|
+
import {
|
|
18
|
+
getKeyHandler,
|
|
19
|
+
Timeline
|
|
20
|
+
} from "@opentui/core";
|
|
21
|
+
import { createContext, createSignal, onCleanup, onMount, useContext } from "solid-js";
|
|
22
|
+
var RendererContext = createContext();
|
|
23
|
+
var useRenderer = () => {
|
|
24
|
+
const renderer = useContext(RendererContext);
|
|
25
|
+
if (!renderer) {
|
|
26
|
+
throw new Error("No renderer found");
|
|
27
|
+
}
|
|
28
|
+
return renderer;
|
|
29
|
+
};
|
|
30
|
+
var onResize = (callback) => {
|
|
31
|
+
const renderer = useRenderer();
|
|
32
|
+
onMount(() => {
|
|
33
|
+
renderer.on("resize", callback);
|
|
34
|
+
});
|
|
35
|
+
onCleanup(() => {
|
|
36
|
+
renderer.off("resize", callback);
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
var useTerminalDimensions = () => {
|
|
40
|
+
const renderer = useRenderer();
|
|
41
|
+
const [terminalDimensions, setTerminalDimensions] = createSignal({ width: renderer.width, height: renderer.height });
|
|
42
|
+
const callback = (width, height) => {
|
|
43
|
+
setTerminalDimensions({ width, height });
|
|
44
|
+
};
|
|
45
|
+
onResize(callback);
|
|
46
|
+
return terminalDimensions;
|
|
47
|
+
};
|
|
48
|
+
var useKeyboard = (callback) => {
|
|
49
|
+
const keyHandler = getKeyHandler();
|
|
50
|
+
onMount(() => {
|
|
51
|
+
keyHandler.on("keypress", callback);
|
|
52
|
+
});
|
|
53
|
+
onCleanup(() => {
|
|
54
|
+
keyHandler.off("keypress", callback);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
var useKeyHandler = useKeyboard;
|
|
58
|
+
var useSelectionHandler = (callback) => {
|
|
59
|
+
const renderer = useRenderer();
|
|
60
|
+
onMount(() => {
|
|
61
|
+
renderer.on("selection", callback);
|
|
62
|
+
});
|
|
63
|
+
onCleanup(() => {
|
|
64
|
+
renderer.off("selection", callback);
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
var createComponentTimeline = (options = {}) => {
|
|
68
|
+
const renderer = useRenderer();
|
|
69
|
+
const timeline = new Timeline(options);
|
|
70
|
+
const frameCallback = async (dt) => timeline.update(dt);
|
|
71
|
+
onMount(() => {
|
|
72
|
+
if (options.autoplay !== false) {
|
|
73
|
+
timeline.play();
|
|
74
|
+
}
|
|
75
|
+
renderer.setFrameCallback(frameCallback);
|
|
76
|
+
});
|
|
77
|
+
onCleanup(() => {
|
|
78
|
+
renderer.removeFrameCallback(frameCallback);
|
|
79
|
+
timeline.pause();
|
|
80
|
+
});
|
|
81
|
+
return timeline;
|
|
82
|
+
};
|
|
83
|
+
var useTimeline = (timeline, initialValue, targetValue, options, startTime = 0) => {
|
|
84
|
+
const [store, setStore] = createSignal(initialValue);
|
|
85
|
+
const { onUpdate, ...animationOptions } = options;
|
|
86
|
+
timeline.add(store(), {
|
|
87
|
+
...targetValue,
|
|
88
|
+
...animationOptions,
|
|
89
|
+
onUpdate: (values) => {
|
|
90
|
+
setStore({ ...values.targets[0] });
|
|
91
|
+
onUpdate?.(values);
|
|
92
|
+
}
|
|
93
|
+
}, startTime);
|
|
94
|
+
return store;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/elements/index.ts
|
|
98
|
+
var baseComponents = {
|
|
99
|
+
box: BoxRenderable,
|
|
100
|
+
text: TextRenderable,
|
|
101
|
+
input: InputRenderable,
|
|
102
|
+
select: SelectRenderable,
|
|
103
|
+
ascii_font: ASCIIFontRenderable,
|
|
104
|
+
tab_select: TabSelectRenderable,
|
|
105
|
+
scrollbox: ScrollBoxRenderable
|
|
106
|
+
};
|
|
107
|
+
var componentCatalogue = { ...baseComponents };
|
|
108
|
+
function extend(objects) {
|
|
109
|
+
Object.assign(componentCatalogue, objects);
|
|
110
|
+
}
|
|
111
|
+
function getComponentCatalogue() {
|
|
112
|
+
return componentCatalogue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/reconciler.ts
|
|
116
|
+
import {
|
|
117
|
+
InputRenderable as InputRenderable2,
|
|
118
|
+
InputRenderableEvents,
|
|
119
|
+
Renderable as Renderable2,
|
|
120
|
+
SelectRenderable as SelectRenderable2,
|
|
121
|
+
SelectRenderableEvents,
|
|
122
|
+
StyledText,
|
|
123
|
+
TabSelectRenderable as TabSelectRenderable2,
|
|
124
|
+
TabSelectRenderableEvents,
|
|
125
|
+
TextRenderable as TextRenderable3
|
|
126
|
+
} from "@opentui/core";
|
|
127
|
+
import { useContext as useContext2 } from "solid-js";
|
|
128
|
+
import { createRenderer } from "solid-js/universal";
|
|
129
|
+
|
|
130
|
+
// src/elements/text-node.ts
|
|
131
|
+
import { Renderable, TextRenderable as TextRenderable2 } from "@opentui/core";
|
|
132
|
+
|
|
133
|
+
// src/utils/id-counter.ts
|
|
134
|
+
var idCounter = new Map;
|
|
135
|
+
function getNextId(elementType) {
|
|
136
|
+
if (!idCounter.has(elementType)) {
|
|
137
|
+
idCounter.set(elementType, 0);
|
|
138
|
+
}
|
|
139
|
+
const value = idCounter.get(elementType) + 1;
|
|
140
|
+
idCounter.set(elementType, value);
|
|
141
|
+
return `${elementType}-${value}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// src/utils/log.ts
|
|
145
|
+
var log = (...args) => {
|
|
146
|
+
if (process.env.DEBUG) {
|
|
147
|
+
console.log("[Reconciler]", ...args);
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/elements/text-node.ts
|
|
152
|
+
var GHOST_NODE_TAG = "text-ghost";
|
|
153
|
+
var ChunkToTextNodeMap = new WeakMap;
|
|
154
|
+
var isTextChunk = (node) => {
|
|
155
|
+
return typeof node === "object" && "__isChunk" in node;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
class TextNode {
|
|
159
|
+
id;
|
|
160
|
+
chunk;
|
|
161
|
+
parent;
|
|
162
|
+
textParent;
|
|
163
|
+
constructor(chunk) {
|
|
164
|
+
this.id = getNextId("text-node");
|
|
165
|
+
this.chunk = chunk;
|
|
166
|
+
ChunkToTextNodeMap.set(chunk, this);
|
|
167
|
+
}
|
|
168
|
+
replaceText(newChunk) {
|
|
169
|
+
const textParent = this.textParent;
|
|
170
|
+
if (!textParent) {
|
|
171
|
+
log("No parent found for text node:", this.id);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
textParent.content = textParent.content.replace(newChunk, this.chunk);
|
|
175
|
+
this.chunk = newChunk;
|
|
176
|
+
ChunkToTextNodeMap.set(newChunk, this);
|
|
177
|
+
}
|
|
178
|
+
static getTextNodeFromChunk(chunk) {
|
|
179
|
+
return ChunkToTextNodeMap.get(chunk);
|
|
180
|
+
}
|
|
181
|
+
insert(parent, anchor) {
|
|
182
|
+
if (!(parent instanceof Renderable)) {
|
|
183
|
+
log("Attaching text node to parent text node, impossible");
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
let textParent;
|
|
187
|
+
if (!(parent instanceof TextRenderable2)) {
|
|
188
|
+
textParent = this.getOrCreateTextGhostNode(parent, anchor);
|
|
189
|
+
} else {
|
|
190
|
+
textParent = parent;
|
|
191
|
+
}
|
|
192
|
+
this.textParent = textParent;
|
|
193
|
+
let styledText = textParent.content;
|
|
194
|
+
if (anchor) {
|
|
195
|
+
if (anchor instanceof Renderable) {
|
|
196
|
+
console.warn("text node can't be anchored to Renderable");
|
|
197
|
+
return;
|
|
198
|
+
} else if (isTextChunk(anchor)) {
|
|
199
|
+
anchor = ChunkToTextNodeMap.get(anchor);
|
|
200
|
+
}
|
|
201
|
+
const anchorIndex = anchor ? styledText.chunks.indexOf(anchor.chunk) : -1;
|
|
202
|
+
if (anchorIndex === -1) {
|
|
203
|
+
log("anchor not found");
|
|
204
|
+
styledText = styledText.insert(this.chunk);
|
|
205
|
+
} else {
|
|
206
|
+
styledText = styledText.insert(this.chunk, anchorIndex);
|
|
207
|
+
}
|
|
208
|
+
} else {
|
|
209
|
+
const firstChunk = textParent.content.chunks[0];
|
|
210
|
+
if (firstChunk && !ChunkToTextNodeMap.has(firstChunk)) {
|
|
211
|
+
styledText = styledText.replace(this.chunk, firstChunk);
|
|
212
|
+
} else {
|
|
213
|
+
styledText = styledText.insert(this.chunk);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
textParent.content = styledText;
|
|
217
|
+
textParent.visible = textParent.textLength !== 0;
|
|
218
|
+
this.parent = parent;
|
|
219
|
+
}
|
|
220
|
+
remove(parent) {
|
|
221
|
+
if (!(parent instanceof Renderable)) {
|
|
222
|
+
ChunkToTextNodeMap.delete(this.chunk);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (parent === this.textParent && parent instanceof TextRenderable2) {
|
|
226
|
+
ChunkToTextNodeMap.delete(this.chunk);
|
|
227
|
+
parent.content = parent.content.remove(this.chunk);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (this.textParent) {
|
|
231
|
+
ChunkToTextNodeMap.delete(this.chunk);
|
|
232
|
+
let styledText = this.textParent.content;
|
|
233
|
+
styledText = styledText.remove(this.chunk);
|
|
234
|
+
if (styledText.chunks.length > 0) {
|
|
235
|
+
this.textParent.content = styledText;
|
|
236
|
+
} else {
|
|
237
|
+
this.parent?.remove(this.textParent.id);
|
|
238
|
+
process.nextTick(() => {
|
|
239
|
+
if (!this.textParent)
|
|
240
|
+
return;
|
|
241
|
+
if (!this.textParent.parent) {
|
|
242
|
+
this.textParent.destroyRecursively();
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
getOrCreateTextGhostNode(parent, anchor) {
|
|
249
|
+
if (anchor instanceof TextNode && anchor.textParent) {
|
|
250
|
+
return anchor.textParent;
|
|
251
|
+
}
|
|
252
|
+
const children = parent.getChildren();
|
|
253
|
+
if (anchor instanceof Renderable) {
|
|
254
|
+
const anchorIndex = children.findIndex((el) => el.id === anchor.id);
|
|
255
|
+
const beforeAnchor = children[anchorIndex - 1];
|
|
256
|
+
if (beforeAnchor instanceof GhostTextRenderable) {
|
|
257
|
+
return beforeAnchor;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const lastChild = children.at(-1);
|
|
261
|
+
if (lastChild instanceof GhostTextRenderable) {
|
|
262
|
+
return lastChild;
|
|
263
|
+
}
|
|
264
|
+
const ghostNode = new GhostTextRenderable(parent.ctx, {
|
|
265
|
+
id: getNextId(GHOST_NODE_TAG)
|
|
266
|
+
});
|
|
267
|
+
insertNode(parent, ghostNode, anchor);
|
|
268
|
+
return ghostNode;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
class GhostTextRenderable extends TextRenderable2 {
|
|
273
|
+
constructor(ctx, options) {
|
|
274
|
+
super(ctx, options);
|
|
275
|
+
}
|
|
276
|
+
static isGhostNode(node) {
|
|
277
|
+
return node instanceof GhostTextRenderable;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// src/reconciler.ts
|
|
282
|
+
var logId = (node) => {
|
|
283
|
+
if (!node)
|
|
284
|
+
return;
|
|
285
|
+
if (isTextChunk(node)) {
|
|
286
|
+
return node.plainText;
|
|
287
|
+
}
|
|
288
|
+
return node.id;
|
|
289
|
+
};
|
|
290
|
+
function _insertNode(parent, node, anchor) {
|
|
291
|
+
log("Inserting node:", logId(node), "into parent:", logId(parent), "with anchor:", logId(anchor), node instanceof TextNode);
|
|
292
|
+
if (node instanceof StyledText) {
|
|
293
|
+
log("Inserting styled text:", node.toString());
|
|
294
|
+
for (const chunk of node.chunks) {
|
|
295
|
+
_insertNode(parent, _createTextNode(chunk), anchor);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (isTextChunk(node)) {
|
|
300
|
+
_insertNode(parent, _createTextNode(node), anchor);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (node instanceof TextNode) {
|
|
304
|
+
return node.insert(parent, anchor);
|
|
305
|
+
}
|
|
306
|
+
if (!(parent instanceof Renderable2)) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (anchor) {
|
|
310
|
+
if (isTextChunk(anchor)) {
|
|
311
|
+
console.warn("Cannot add non text node with text chunk anchor");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const anchorIndex = parent.getChildren().findIndex((el) => {
|
|
315
|
+
if (anchor instanceof TextNode) {
|
|
316
|
+
return el.id === anchor.textParent?.id;
|
|
317
|
+
}
|
|
318
|
+
return el.id === anchor.id;
|
|
319
|
+
});
|
|
320
|
+
parent.add(node, anchorIndex);
|
|
321
|
+
} else {
|
|
322
|
+
parent.add(node);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function _removeNode(parent, node) {
|
|
326
|
+
log("Removing node:", logId(node), "from parent:", logId(parent));
|
|
327
|
+
if (isTextChunk(node)) {
|
|
328
|
+
const textNode = TextNode.getTextNodeFromChunk(node);
|
|
329
|
+
if (textNode) {
|
|
330
|
+
_removeNode(parent, textNode);
|
|
331
|
+
}
|
|
332
|
+
} else if (node instanceof StyledText) {
|
|
333
|
+
for (const chunk of node.chunks) {
|
|
334
|
+
const textNode = TextNode.getTextNodeFromChunk(chunk);
|
|
335
|
+
if (!textNode)
|
|
336
|
+
continue;
|
|
337
|
+
_removeNode(parent, textNode);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (node instanceof TextNode) {
|
|
341
|
+
return node.remove(parent);
|
|
342
|
+
}
|
|
343
|
+
if (parent instanceof Renderable2 && node instanceof Renderable2) {
|
|
344
|
+
parent.remove(node.id);
|
|
345
|
+
process.nextTick(() => {
|
|
346
|
+
if (!node.parent) {
|
|
347
|
+
node.destroyRecursively();
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
function _createTextNode(value) {
|
|
353
|
+
log("Creating text node:", value);
|
|
354
|
+
const chunk = value && isTextChunk(value) ? value : {
|
|
355
|
+
__isChunk: true,
|
|
356
|
+
text: new TextEncoder().encode(`${value}`),
|
|
357
|
+
plainText: `${value}`
|
|
358
|
+
};
|
|
359
|
+
const textNode = new TextNode(chunk);
|
|
360
|
+
return textNode;
|
|
361
|
+
}
|
|
362
|
+
var {
|
|
363
|
+
render: _render,
|
|
364
|
+
effect,
|
|
365
|
+
memo,
|
|
366
|
+
createComponent,
|
|
367
|
+
createElement,
|
|
368
|
+
createTextNode,
|
|
369
|
+
insertNode,
|
|
370
|
+
insert,
|
|
371
|
+
spread,
|
|
372
|
+
setProp,
|
|
373
|
+
mergeProps,
|
|
374
|
+
use
|
|
375
|
+
} = createRenderer({
|
|
376
|
+
createElement(tagName) {
|
|
377
|
+
log("Creating element:", tagName);
|
|
378
|
+
const id = getNextId(tagName);
|
|
379
|
+
const solidRenderer = useContext2(RendererContext);
|
|
380
|
+
if (!solidRenderer) {
|
|
381
|
+
throw new Error("No renderer found");
|
|
382
|
+
}
|
|
383
|
+
const elements = getComponentCatalogue();
|
|
384
|
+
if (!elements[tagName]) {
|
|
385
|
+
throw new Error(`[Reconciler] Unknown component type: ${tagName}`);
|
|
386
|
+
}
|
|
387
|
+
const element = new elements[tagName](solidRenderer, { id });
|
|
388
|
+
log("Element created with id:", id);
|
|
389
|
+
return element;
|
|
390
|
+
},
|
|
391
|
+
createTextNode: _createTextNode,
|
|
392
|
+
replaceText(textNode, value) {
|
|
393
|
+
log("Replacing text:", value, "in node:", logId(textNode));
|
|
394
|
+
if (textNode instanceof Renderable2)
|
|
395
|
+
return;
|
|
396
|
+
if (isTextChunk(textNode)) {
|
|
397
|
+
console.warn("Cannot replace text on text chunk", logId(textNode));
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const newChunk = {
|
|
401
|
+
__isChunk: true,
|
|
402
|
+
text: new TextEncoder().encode(value),
|
|
403
|
+
plainText: value
|
|
404
|
+
};
|
|
405
|
+
textNode.replaceText(newChunk);
|
|
406
|
+
},
|
|
407
|
+
setProperty(node, name, value, prev) {
|
|
408
|
+
if (node instanceof TextNode || isTextChunk(node)) {
|
|
409
|
+
console.warn("Cannot set property on text node:", logId(node));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (name.startsWith("on:")) {
|
|
413
|
+
const eventName = name.slice(3);
|
|
414
|
+
if (value) {
|
|
415
|
+
node.on(eventName, value);
|
|
416
|
+
}
|
|
417
|
+
if (prev) {
|
|
418
|
+
node.off(eventName, prev);
|
|
419
|
+
}
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
switch (name) {
|
|
423
|
+
case "focused":
|
|
424
|
+
if (value) {
|
|
425
|
+
node.focus();
|
|
426
|
+
} else {
|
|
427
|
+
node.blur();
|
|
428
|
+
}
|
|
429
|
+
break;
|
|
430
|
+
case "onChange":
|
|
431
|
+
let event = undefined;
|
|
432
|
+
if (node instanceof SelectRenderable2) {
|
|
433
|
+
event = SelectRenderableEvents.SELECTION_CHANGED;
|
|
434
|
+
} else if (node instanceof TabSelectRenderable2) {
|
|
435
|
+
event = TabSelectRenderableEvents.SELECTION_CHANGED;
|
|
436
|
+
} else if (node instanceof InputRenderable2) {
|
|
437
|
+
event = InputRenderableEvents.CHANGE;
|
|
438
|
+
}
|
|
439
|
+
if (!event)
|
|
440
|
+
break;
|
|
441
|
+
if (value) {
|
|
442
|
+
node.on(event, value);
|
|
443
|
+
}
|
|
444
|
+
if (prev) {
|
|
445
|
+
node.off(event, prev);
|
|
446
|
+
}
|
|
447
|
+
break;
|
|
448
|
+
case "onInput":
|
|
449
|
+
if (node instanceof InputRenderable2) {
|
|
450
|
+
if (value) {
|
|
451
|
+
node.on(InputRenderableEvents.INPUT, value);
|
|
452
|
+
}
|
|
453
|
+
if (prev) {
|
|
454
|
+
node.off(InputRenderableEvents.INPUT, prev);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
break;
|
|
458
|
+
case "onSubmit":
|
|
459
|
+
if (node instanceof InputRenderable2) {
|
|
460
|
+
if (value) {
|
|
461
|
+
node.on(InputRenderableEvents.ENTER, value);
|
|
462
|
+
}
|
|
463
|
+
if (prev) {
|
|
464
|
+
node.off(InputRenderableEvents.ENTER, prev);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
break;
|
|
468
|
+
case "onSelect":
|
|
469
|
+
if (node instanceof SelectRenderable2) {
|
|
470
|
+
if (value) {
|
|
471
|
+
node.on(SelectRenderableEvents.ITEM_SELECTED, value);
|
|
472
|
+
}
|
|
473
|
+
if (prev) {
|
|
474
|
+
node.off(SelectRenderableEvents.ITEM_SELECTED, prev);
|
|
475
|
+
}
|
|
476
|
+
} else if (node instanceof TabSelectRenderable2) {
|
|
477
|
+
if (value) {
|
|
478
|
+
node.on(TabSelectRenderableEvents.ITEM_SELECTED, value);
|
|
479
|
+
}
|
|
480
|
+
if (prev) {
|
|
481
|
+
node.off(TabSelectRenderableEvents.ITEM_SELECTED, prev);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
break;
|
|
485
|
+
case "style":
|
|
486
|
+
for (const prop in value) {
|
|
487
|
+
const propVal = value[prop];
|
|
488
|
+
if (prev !== undefined && propVal === prev[prop])
|
|
489
|
+
continue;
|
|
490
|
+
node[prop] = propVal;
|
|
491
|
+
}
|
|
492
|
+
break;
|
|
493
|
+
case "text":
|
|
494
|
+
case "content":
|
|
495
|
+
node[name] = typeof value === "string" ? value : Array.isArray(value) ? value.join("") : `${value}`;
|
|
496
|
+
break;
|
|
497
|
+
default:
|
|
498
|
+
node[name] = value;
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
isTextNode(node) {
|
|
502
|
+
return node instanceof TextNode;
|
|
503
|
+
},
|
|
504
|
+
insertNode: _insertNode,
|
|
505
|
+
removeNode: _removeNode,
|
|
506
|
+
getParentNode(childNode) {
|
|
507
|
+
log("Getting parent of node:", logId(childNode));
|
|
508
|
+
let node = childNode;
|
|
509
|
+
if (isTextChunk(childNode)) {
|
|
510
|
+
const parentTextNode = TextNode.getTextNodeFromChunk(childNode);
|
|
511
|
+
if (!parentTextNode)
|
|
512
|
+
return;
|
|
513
|
+
node = parentTextNode;
|
|
514
|
+
}
|
|
515
|
+
const parent = node.parent;
|
|
516
|
+
if (!parent) {
|
|
517
|
+
log("No parent found for node:", logId(node));
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
log("Parent found:", logId(parent), "for node:", logId(node));
|
|
521
|
+
return parent;
|
|
522
|
+
},
|
|
523
|
+
getFirstChild(node) {
|
|
524
|
+
log("Getting first child of node:", logId(node));
|
|
525
|
+
if (node instanceof TextRenderable3) {
|
|
526
|
+
const chunk = node.content.chunks[0];
|
|
527
|
+
if (chunk) {
|
|
528
|
+
return TextNode.getTextNodeFromChunk(chunk);
|
|
529
|
+
} else {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
if (node instanceof TextNode || isTextChunk(node)) {
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
const firstChild = node.getChildren()[0];
|
|
537
|
+
if (!firstChild) {
|
|
538
|
+
log("No first child found for node:", logId(node));
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
log("First child found:", logId(firstChild), "for node:", logId(node));
|
|
542
|
+
return firstChild;
|
|
543
|
+
},
|
|
544
|
+
getNextSibling(node) {
|
|
545
|
+
log("Getting next sibling of node:", logId(node));
|
|
546
|
+
if (isTextChunk(node)) {
|
|
547
|
+
console.warn("Cannot get next sibling of text chunk");
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const parent = node.parent;
|
|
551
|
+
if (!parent) {
|
|
552
|
+
log("No parent found for node:", logId(node));
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
if (node instanceof TextNode) {
|
|
556
|
+
if (parent instanceof TextRenderable3) {
|
|
557
|
+
const siblings2 = parent.content.chunks;
|
|
558
|
+
const index2 = siblings2.indexOf(node.chunk);
|
|
559
|
+
if (index2 === -1 || index2 === siblings2.length - 1) {
|
|
560
|
+
log("No next sibling found for node:", logId(node));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const nextSibling2 = siblings2[index2 + 1];
|
|
564
|
+
if (!nextSibling2) {
|
|
565
|
+
log("Next sibling is null for node:", logId(node));
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
return TextNode.getTextNodeFromChunk(nextSibling2);
|
|
569
|
+
}
|
|
570
|
+
console.warn("Text parent is not a text node:", logId(node));
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
const siblings = parent.getChildren();
|
|
574
|
+
const index = siblings.indexOf(node);
|
|
575
|
+
if (index === -1 || index === siblings.length - 1) {
|
|
576
|
+
log("No next sibling found for node:", logId(node));
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
const nextSibling = siblings[index + 1];
|
|
580
|
+
if (!nextSibling) {
|
|
581
|
+
log("Next sibling is null for node:", logId(node));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
log("Next sibling found:", logId(nextSibling), "for node:", logId(node));
|
|
585
|
+
return nextSibling;
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// index.ts
|
|
590
|
+
var render = async (node, renderConfig = {}) => {
|
|
591
|
+
const renderer = await createCliRenderer(renderConfig);
|
|
592
|
+
_render(() => createComponent(RendererContext.Provider, {
|
|
593
|
+
get value() {
|
|
594
|
+
return renderer;
|
|
595
|
+
},
|
|
596
|
+
get children() {
|
|
597
|
+
return createComponent(node, {});
|
|
598
|
+
}
|
|
599
|
+
}), renderer.root);
|
|
600
|
+
};
|
|
601
|
+
export {
|
|
602
|
+
useTimeline,
|
|
603
|
+
useTerminalDimensions,
|
|
604
|
+
useSelectionHandler,
|
|
605
|
+
useRenderer,
|
|
606
|
+
useKeyboard,
|
|
607
|
+
useKeyHandler,
|
|
608
|
+
use,
|
|
609
|
+
spread,
|
|
610
|
+
setProp,
|
|
611
|
+
render,
|
|
612
|
+
onResize,
|
|
613
|
+
mergeProps,
|
|
614
|
+
memo,
|
|
615
|
+
insertNode,
|
|
616
|
+
insert,
|
|
617
|
+
getComponentCatalogue,
|
|
618
|
+
extend,
|
|
619
|
+
effect,
|
|
620
|
+
createTextNode,
|
|
621
|
+
createElement,
|
|
622
|
+
createComponentTimeline,
|
|
623
|
+
createComponent,
|
|
624
|
+
componentCatalogue,
|
|
625
|
+
baseComponents,
|
|
626
|
+
_render,
|
|
627
|
+
RendererContext
|
|
628
|
+
};
|
package/jsx-runtime.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Renderable } from "@opentui/core"
|
|
2
|
+
import type {
|
|
3
|
+
AsciiFontProps,
|
|
4
|
+
BoxProps,
|
|
5
|
+
ExtendedIntrinsicElements,
|
|
6
|
+
InputProps,
|
|
7
|
+
OpenTUIComponents,
|
|
8
|
+
SelectProps,
|
|
9
|
+
TabSelectProps,
|
|
10
|
+
TextProps,
|
|
11
|
+
} from "./src/types/elements"
|
|
12
|
+
|
|
13
|
+
declare namespace JSX {
|
|
14
|
+
// Replace Node with Renderable
|
|
15
|
+
type Element = Renderable | ArrayElement | (string & {}) | number | boolean | null | undefined
|
|
16
|
+
|
|
17
|
+
interface ArrayElement extends Array<Element> {}
|
|
18
|
+
|
|
19
|
+
interface IntrinsicElements extends ExtendedIntrinsicElements<OpenTUIComponents> {
|
|
20
|
+
box: BoxProps
|
|
21
|
+
text: TextProps
|
|
22
|
+
input: InputProps
|
|
23
|
+
select: SelectProps
|
|
24
|
+
ascii_font: AsciiFontProps
|
|
25
|
+
tab_select: TabSelectProps
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ElementChildrenAttribute {
|
|
29
|
+
children: {}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opentui/solid",
|
|
3
|
+
"module": "index.js",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"types": "index.d.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"version": "0.0.0-20250908-4906ddad",
|
|
8
|
+
"description": "SolidJS renderer for OpenTUI",
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/sst/opentui",
|
|
13
|
+
"directory": "packages/solid"
|
|
14
|
+
},
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./index.d.ts",
|
|
18
|
+
"import": "./index.js",
|
|
19
|
+
"require": "./index.js"
|
|
20
|
+
},
|
|
21
|
+
"./preload": {
|
|
22
|
+
"import": "./scripts/preload.ts"
|
|
23
|
+
},
|
|
24
|
+
"./jsx-runtime": "./jsx-runtime.d.ts",
|
|
25
|
+
"./jsx-dev-runtime": "./jsx-runtime.d.ts"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@opentui/core": "0.0.0-20250908-4906ddad",
|
|
29
|
+
"babel-plugin-module-resolver": "5.0.2",
|
|
30
|
+
"@babel/core": "7.28.0",
|
|
31
|
+
"@babel/preset-typescript": "7.27.1",
|
|
32
|
+
"babel-preset-solid": "1.9.9"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/babel__core": "7.20.5",
|
|
36
|
+
"@types/bun": "latest"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"solid-js": "1.9.9",
|
|
40
|
+
"typescript": "^5"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { transformAsync } from "@babel/core"
|
|
2
|
+
// @ts-expect-error - Types not important.
|
|
3
|
+
import ts from "@babel/preset-typescript"
|
|
4
|
+
// @ts-expect-error - Types not important.
|
|
5
|
+
import solid from "babel-preset-solid"
|
|
6
|
+
import { type BunPlugin } from "bun"
|
|
7
|
+
|
|
8
|
+
const solidTransformPlugin: BunPlugin = {
|
|
9
|
+
name: "bun-plugin-solid",
|
|
10
|
+
setup: (build) => {
|
|
11
|
+
build.onLoad({ filter: /\/node_modules\/solid-js\/dist\/server\.js$/ }, async (args) => {
|
|
12
|
+
const path = args.path.replace("server.js", "solid.js")
|
|
13
|
+
const file = Bun.file(path)
|
|
14
|
+
const code = await file.text()
|
|
15
|
+
return { contents: code, loader: "js" }
|
|
16
|
+
})
|
|
17
|
+
build.onLoad({ filter: /\.(js|ts)x$/ }, async (args) => {
|
|
18
|
+
const file = Bun.file(args.path)
|
|
19
|
+
const code = await file.text()
|
|
20
|
+
const transforms = await transformAsync(code, {
|
|
21
|
+
filename: args.path,
|
|
22
|
+
// env: {
|
|
23
|
+
// development: {
|
|
24
|
+
// plugins: [["solid-refresh/babel", { "bundler": "esm" }]],
|
|
25
|
+
// },
|
|
26
|
+
// },
|
|
27
|
+
// plugins: [["solid-refresh/babel", { bundler: "esm" }]],
|
|
28
|
+
presets: [
|
|
29
|
+
[
|
|
30
|
+
solid,
|
|
31
|
+
{
|
|
32
|
+
moduleName: "@opentui/solid",
|
|
33
|
+
generate: "universal",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
[ts],
|
|
37
|
+
],
|
|
38
|
+
})
|
|
39
|
+
return {
|
|
40
|
+
contents: transforms?.code ?? "",
|
|
41
|
+
loader: "js",
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export default solidTransformPlugin
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Selection, Timeline, type AnimationOptions, type CliRenderer, type JSAnimation, type ParsedKey, type TimelineOptions } from "@opentui/core";
|
|
2
|
+
export declare const RendererContext: import("solid-js").Context<CliRenderer | undefined>;
|
|
3
|
+
export declare const useRenderer: () => CliRenderer;
|
|
4
|
+
export declare const onResize: (callback: (width: number, height: number) => void) => void;
|
|
5
|
+
export declare const useTerminalDimensions: () => import("solid-js").Accessor<{
|
|
6
|
+
width: number;
|
|
7
|
+
height: number;
|
|
8
|
+
}>;
|
|
9
|
+
export declare const useKeyboard: (callback: (key: ParsedKey) => void) => void;
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated renamed to useKeyboard
|
|
12
|
+
*/
|
|
13
|
+
export declare const useKeyHandler: (callback: (key: ParsedKey) => void) => void;
|
|
14
|
+
export declare const useSelectionHandler: (callback: (selection: Selection) => void) => void;
|
|
15
|
+
export declare const createComponentTimeline: (options?: TimelineOptions) => Timeline;
|
|
16
|
+
export declare const useTimeline: <T extends Record<string, number>>(timeline: Timeline, initialValue: T, targetValue: T, options: AnimationOptions & {
|
|
17
|
+
onUpdate?: (values: JSAnimation & {
|
|
18
|
+
targets: T[];
|
|
19
|
+
}) => void;
|
|
20
|
+
}, startTime?: number | string) => import("solid-js").Accessor<T>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { ASCIIFontRenderable, BoxRenderable, InputRenderable, ScrollBoxRenderable, SelectRenderable, TabSelectRenderable, TextRenderable } from "@opentui/core";
|
|
2
|
+
import type { RenderableConstructor } from "../types/elements";
|
|
3
|
+
export * from "./hooks";
|
|
4
|
+
export declare const baseComponents: {
|
|
5
|
+
box: typeof BoxRenderable;
|
|
6
|
+
text: typeof TextRenderable;
|
|
7
|
+
input: typeof InputRenderable;
|
|
8
|
+
select: typeof SelectRenderable;
|
|
9
|
+
ascii_font: typeof ASCIIFontRenderable;
|
|
10
|
+
tab_select: typeof TabSelectRenderable;
|
|
11
|
+
scrollbox: typeof ScrollBoxRenderable;
|
|
12
|
+
};
|
|
13
|
+
type ComponentCatalogue = Record<string, RenderableConstructor>;
|
|
14
|
+
export declare const componentCatalogue: ComponentCatalogue;
|
|
15
|
+
/**
|
|
16
|
+
* Extend the component catalogue with new renderable components
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* // Extend with an object of components
|
|
21
|
+
* extend({
|
|
22
|
+
* consoleButton: ConsoleButtonRenderable,
|
|
23
|
+
* customBox: CustomBoxRenderable
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function extend<T extends ComponentCatalogue>(objects: T): void;
|
|
28
|
+
export declare function getComponentCatalogue(): ComponentCatalogue;
|
|
29
|
+
export type { ExtendedComponentProps, ExtendedIntrinsicElements, RenderableConstructor } from "../types/elements";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Renderable, TextRenderable, type RenderContext, type TextChunk, type TextOptions } from "@opentui/core";
|
|
2
|
+
import { type DomNode } from "../reconciler";
|
|
3
|
+
export declare const isTextChunk: (node: any) => node is TextChunk;
|
|
4
|
+
/**
|
|
5
|
+
* Represents a text node in the SolidJS reconciler.
|
|
6
|
+
*/
|
|
7
|
+
export declare class TextNode {
|
|
8
|
+
id: string;
|
|
9
|
+
chunk: TextChunk;
|
|
10
|
+
parent?: Renderable;
|
|
11
|
+
textParent?: TextRenderable | GhostTextRenderable;
|
|
12
|
+
constructor(chunk: TextChunk);
|
|
13
|
+
/**
|
|
14
|
+
* Replaces the current text chunk with a new one.
|
|
15
|
+
* @param newChunk The new text chunk to replace with.
|
|
16
|
+
*/
|
|
17
|
+
replaceText(newChunk: TextChunk): void;
|
|
18
|
+
/**
|
|
19
|
+
* Retrieves the TextNode associated with a given TextChunk.
|
|
20
|
+
* @param chunk The text chunk to look up.
|
|
21
|
+
* @returns The associated TextNode or undefined if not found.
|
|
22
|
+
*/
|
|
23
|
+
static getTextNodeFromChunk(chunk: TextChunk): TextNode | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* Inserts this text node into the DOM structure.
|
|
26
|
+
* @param parent The parent DOM node.
|
|
27
|
+
* @param anchor The anchor node for positioning.
|
|
28
|
+
*/
|
|
29
|
+
insert(parent: DomNode, anchor?: DomNode): void;
|
|
30
|
+
/**
|
|
31
|
+
* Removes this text node from the DOM structure.
|
|
32
|
+
* @param parent The parent DOM node.
|
|
33
|
+
*/
|
|
34
|
+
remove(parent: DomNode): void;
|
|
35
|
+
/**
|
|
36
|
+
* Gets or creates a ghost text node for rendering text content.
|
|
37
|
+
* @param parent The parent renderable.
|
|
38
|
+
* @param anchor The anchor node for positioning.
|
|
39
|
+
* @returns The text renderable ghost node.
|
|
40
|
+
* @private
|
|
41
|
+
*/
|
|
42
|
+
private getOrCreateTextGhostNode;
|
|
43
|
+
}
|
|
44
|
+
declare class GhostTextRenderable extends TextRenderable {
|
|
45
|
+
constructor(ctx: RenderContext, options: TextOptions);
|
|
46
|
+
static isGhostNode(node: DomNode): node is GhostTextRenderable;
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { Renderable, type TextChunk } from "@opentui/core";
|
|
2
|
+
import { TextNode } from "./elements/text-node";
|
|
3
|
+
export type DomNode = Renderable | TextNode | TextChunk;
|
|
4
|
+
export declare const _render: (code: () => DomNode, node: DomNode) => () => void, effect: <T>(fn: (prev?: T) => T, init?: T) => void, memo: <T>(fn: () => T, equal: boolean) => () => T, createComponent: <T>(Comp: (props: T) => DomNode, props: T) => DomNode, createElement: (tag: string) => DomNode, createTextNode: (value: string) => DomNode, insertNode: (parent: DomNode, node: DomNode, anchor?: DomNode | undefined) => void, insert: <T>(parent: any, accessor: T | (() => T), marker?: any | null, initial?: any) => DomNode, spread: <T>(node: any, accessor: (() => T) | T, skipChildren?: boolean) => void, setProp: <T>(node: DomNode, name: string, value: T, prev?: T | undefined) => T, mergeProps: (...sources: unknown[]) => unknown, use: <A, T>(fn: (element: DomNode, arg: A) => T, element: DomNode, arg: A) => T;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ASCIIFontOptions, ASCIIFontRenderable, BoxOptions, BoxRenderable, InputRenderable, InputRenderableOptions, Renderable, RenderableOptions, RenderContext, ScrollBoxOptions, ScrollBoxRenderable, SelectOption, SelectRenderable, SelectRenderableOptions, StyledText, TabSelectOption, TabSelectRenderable, TabSelectRenderableOptions, TextChunk, TextOptions, TextRenderable } from "@opentui/core";
|
|
2
|
+
import type { JSX, Ref } from "solid-js";
|
|
3
|
+
/** Properties that should not be included in the style prop */
|
|
4
|
+
export type NonStyledProps = "id" | "buffered" | "live" | "enableLayout" | "selectable" | "renderAfter" | "renderBefore" | `on${string}`;
|
|
5
|
+
/** Solid-specific props for all components */
|
|
6
|
+
export type ElementProps<TRenderable = unknown> = {
|
|
7
|
+
ref?: Ref<TRenderable>;
|
|
8
|
+
};
|
|
9
|
+
/** Base type for any renderable constructor */
|
|
10
|
+
export type RenderableConstructor<TRenderable extends Renderable = Renderable> = new (ctx: RenderContext, options: any) => TRenderable;
|
|
11
|
+
/** Extract the options type from a renderable constructor */
|
|
12
|
+
type ExtractRenderableOptions<TConstructor> = TConstructor extends new (ctx: RenderContext, options: infer TOptions) => any ? TOptions : never;
|
|
13
|
+
/** Extract the renderable type from a constructor */
|
|
14
|
+
type ExtractRenderable<TConstructor> = TConstructor extends new (ctx: RenderContext, options: any) => infer TRenderable ? TRenderable : never;
|
|
15
|
+
/** Determine which properties should be excluded from styling for different renderable types */
|
|
16
|
+
export type GetNonStyledProperties<TConstructor> = TConstructor extends RenderableConstructor<TextRenderable> ? NonStyledProps | "content" : TConstructor extends RenderableConstructor<BoxRenderable> ? NonStyledProps | "title" : TConstructor extends RenderableConstructor<ASCIIFontRenderable> ? NonStyledProps | "text" | "selectable" : TConstructor extends RenderableConstructor<InputRenderable> ? NonStyledProps | "placeholder" | "value" : NonStyledProps;
|
|
17
|
+
/** Base props for container components that accept children */
|
|
18
|
+
type ContainerProps<TOptions> = TOptions & {
|
|
19
|
+
children?: JSX.Element;
|
|
20
|
+
};
|
|
21
|
+
/** Smart component props that automatically determine excluded properties */
|
|
22
|
+
type ComponentProps<TOptions extends RenderableOptions<TRenderable>, TRenderable extends Renderable> = TOptions & {
|
|
23
|
+
style?: Partial<Omit<TOptions, GetNonStyledProperties<RenderableConstructor<TRenderable>>>>;
|
|
24
|
+
} & ElementProps<TRenderable>;
|
|
25
|
+
/** Valid text content types for Text component children */
|
|
26
|
+
type TextChildren = (string & {}) | number | boolean | null | undefined;
|
|
27
|
+
export type TextProps = ComponentProps<TextOptions, TextRenderable> & {
|
|
28
|
+
children?: TextChildren | StyledText | TextChunk | Array<TextChildren | StyledText | TextChunk>;
|
|
29
|
+
};
|
|
30
|
+
export type BoxProps = ComponentProps<ContainerProps<BoxOptions>, BoxRenderable>;
|
|
31
|
+
export type InputProps = ComponentProps<InputRenderableOptions, InputRenderable> & {
|
|
32
|
+
focused?: boolean;
|
|
33
|
+
onInput?: (value: string) => void;
|
|
34
|
+
onChange?: (value: string) => void;
|
|
35
|
+
onSubmit?: (value: string) => void;
|
|
36
|
+
};
|
|
37
|
+
export type SelectProps = ComponentProps<SelectRenderableOptions, SelectRenderable> & {
|
|
38
|
+
focused?: boolean;
|
|
39
|
+
onChange?: (index: number, option: SelectOption | null) => void;
|
|
40
|
+
onSelect?: (index: number, option: SelectOption | null) => void;
|
|
41
|
+
};
|
|
42
|
+
export type AsciiFontProps = ComponentProps<ASCIIFontOptions, ASCIIFontRenderable>;
|
|
43
|
+
export type TabSelectProps = ComponentProps<TabSelectRenderableOptions, TabSelectRenderable> & {
|
|
44
|
+
focused?: boolean;
|
|
45
|
+
onChange?: (index: number, option: TabSelectOption | null) => void;
|
|
46
|
+
onSelect?: (index: number, option: TabSelectOption | null) => void;
|
|
47
|
+
};
|
|
48
|
+
export type ScrollBoxProps = ComponentProps<ContainerProps<ScrollBoxOptions>, ScrollBoxRenderable> & {
|
|
49
|
+
focused?: boolean;
|
|
50
|
+
};
|
|
51
|
+
/** Convert renderable constructor to component props with proper style exclusions */
|
|
52
|
+
export type ExtendedComponentProps<TConstructor extends RenderableConstructor, TOptions = ExtractRenderableOptions<TConstructor>> = TOptions & {
|
|
53
|
+
children?: JSX.Element;
|
|
54
|
+
style?: Partial<Omit<TOptions, GetNonStyledProperties<TConstructor>>>;
|
|
55
|
+
} & ElementProps<ExtractRenderable<TConstructor>>;
|
|
56
|
+
/** Helper type to create JSX element properties from a component catalogue */
|
|
57
|
+
export type ExtendedIntrinsicElements<TComponentCatalogue extends Record<string, RenderableConstructor>> = {
|
|
58
|
+
[TComponentName in keyof TComponentCatalogue]: ExtendedComponentProps<TComponentCatalogue[TComponentName]>;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Global augmentation interface for extended components
|
|
62
|
+
* This will be augmented by user code using module augmentation
|
|
63
|
+
*/
|
|
64
|
+
export interface OpenTUIComponents {
|
|
65
|
+
[componentName: string]: RenderableConstructor;
|
|
66
|
+
}
|
|
67
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getNextId(elementType: string): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const log: (...args: any[]) => void;
|