@opentui/solid 0.1.6
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 +36 -0
- package/index.d.ts +5 -0
- package/index.js +449 -0
- package/jsx-runtime.d.ts +31 -0
- package/package.json +47 -0
- package/scripts/preload.ts +4 -0
- package/scripts/solid-plugin.ts +58 -0
- package/src/elements/hooks.d.ts +16 -0
- package/src/elements/index.d.ts +55 -0
- package/src/reconciler.d.ts +11 -0
- package/src/reconciler.js +403 -0
- package/src/utils/id-counter.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,36 @@
|
|
|
1
|
+
# @opentui/solid
|
|
2
|
+
|
|
3
|
+
Solid.js support for 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
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"compilerOptions": {
|
|
17
|
+
"jsx": "preserve",
|
|
18
|
+
"jsxImportSource": "@opentui/solid"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
2. Add preload script to bunfig.toml:
|
|
24
|
+
```toml
|
|
25
|
+
preload = ["@opentui/solid/preload"]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
3. Add render function to index.tsx:
|
|
29
|
+
```tsx
|
|
30
|
+
import { render } from "@opentui/solid";
|
|
31
|
+
|
|
32
|
+
render(() => <text>Hello, World!</text>);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
4. Run with `bun --conditions=browser index.tsx`.
|
package/index.d.ts
ADDED
package/index.js
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// index.ts
|
|
3
|
+
import { createCliRenderer } from "@opentui/core";
|
|
4
|
+
|
|
5
|
+
// src/elements/index.ts
|
|
6
|
+
import {
|
|
7
|
+
ASCIIFontRenderable,
|
|
8
|
+
BoxRenderable,
|
|
9
|
+
GroupRenderable,
|
|
10
|
+
InputRenderable,
|
|
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.terminalWidth, height: renderer.terminalHeight });
|
|
42
|
+
const callback = (width, height) => {
|
|
43
|
+
setTerminalDimensions({ width, height });
|
|
44
|
+
};
|
|
45
|
+
onResize(callback);
|
|
46
|
+
return terminalDimensions;
|
|
47
|
+
};
|
|
48
|
+
var useKeyHandler = (callback) => {
|
|
49
|
+
const keyHandler = getKeyHandler();
|
|
50
|
+
onMount(() => {
|
|
51
|
+
keyHandler.on("keypress", callback);
|
|
52
|
+
});
|
|
53
|
+
onCleanup(() => {
|
|
54
|
+
keyHandler.off("keypress", callback);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
var useSelectionHandler = (callback) => {
|
|
58
|
+
const renderer = useRenderer();
|
|
59
|
+
onMount(() => {
|
|
60
|
+
renderer.on("selection", callback);
|
|
61
|
+
});
|
|
62
|
+
onCleanup(() => {
|
|
63
|
+
renderer.off("selection", callback);
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
var createComponentTimeline = (options = {}) => {
|
|
67
|
+
const renderer = useRenderer();
|
|
68
|
+
const timeline = new Timeline(options);
|
|
69
|
+
const frameCallback = async (dt) => timeline.update(dt);
|
|
70
|
+
onMount(() => {
|
|
71
|
+
if (options.autoplay !== false) {
|
|
72
|
+
timeline.play();
|
|
73
|
+
}
|
|
74
|
+
renderer.setFrameCallback(frameCallback);
|
|
75
|
+
});
|
|
76
|
+
onCleanup(() => {
|
|
77
|
+
renderer.removeFrameCallback(frameCallback);
|
|
78
|
+
timeline.pause();
|
|
79
|
+
});
|
|
80
|
+
return timeline;
|
|
81
|
+
};
|
|
82
|
+
var useTimeline = (timeline, initialValue, targetValue, options, startTime = 0) => {
|
|
83
|
+
const [store, setStore] = createSignal(initialValue);
|
|
84
|
+
const { onUpdate, ...animationOptions } = options;
|
|
85
|
+
timeline.add(store(), {
|
|
86
|
+
...targetValue,
|
|
87
|
+
...animationOptions,
|
|
88
|
+
onUpdate: (values) => {
|
|
89
|
+
setStore({ ...values.targets[0] });
|
|
90
|
+
onUpdate?.(values);
|
|
91
|
+
}
|
|
92
|
+
}, startTime);
|
|
93
|
+
return store;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/elements/index.ts
|
|
97
|
+
var elements = {
|
|
98
|
+
ascii_font: ASCIIFontRenderable,
|
|
99
|
+
box: BoxRenderable,
|
|
100
|
+
group: GroupRenderable,
|
|
101
|
+
input: InputRenderable,
|
|
102
|
+
select: SelectRenderable,
|
|
103
|
+
tab_select: TabSelectRenderable,
|
|
104
|
+
text: TextRenderable
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/reconciler.ts
|
|
108
|
+
import {
|
|
109
|
+
GroupRenderable as GroupRenderable2,
|
|
110
|
+
InputRenderable as InputRenderable2,
|
|
111
|
+
InputRenderableEvents,
|
|
112
|
+
Renderable,
|
|
113
|
+
SelectRenderable as SelectRenderable2,
|
|
114
|
+
SelectRenderableEvents,
|
|
115
|
+
StyledText,
|
|
116
|
+
TabSelectRenderable as TabSelectRenderable2,
|
|
117
|
+
TabSelectRenderableEvents,
|
|
118
|
+
TextRenderable as TextRenderable2
|
|
119
|
+
} from "@opentui/core";
|
|
120
|
+
import { createRenderer } from "solid-js/universal";
|
|
121
|
+
|
|
122
|
+
// src/utils/id-counter.ts
|
|
123
|
+
var idCounter = new Map;
|
|
124
|
+
function getNextId(elementType) {
|
|
125
|
+
if (!idCounter.has(elementType)) {
|
|
126
|
+
idCounter.set(elementType, 0);
|
|
127
|
+
}
|
|
128
|
+
const value = idCounter.get(elementType) + 1;
|
|
129
|
+
idCounter.set(elementType, value);
|
|
130
|
+
return `${elementType}-${value}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// src/reconciler.ts
|
|
134
|
+
class TextNode {
|
|
135
|
+
id;
|
|
136
|
+
chunk;
|
|
137
|
+
parent;
|
|
138
|
+
constructor(chunk) {
|
|
139
|
+
this.id = getNextId("text-node");
|
|
140
|
+
this.chunk = chunk;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
var ChunkToTextNodeMap = new WeakMap;
|
|
144
|
+
var log = (...args) => {
|
|
145
|
+
console.log("[Reconciler]", ...args);
|
|
146
|
+
};
|
|
147
|
+
function _insertNode(parent, node, anchor) {
|
|
148
|
+
log("Inserting node:", node.id, "into parent:", parent.id, "with anchor:", anchor?.id);
|
|
149
|
+
if (node instanceof TextNode) {
|
|
150
|
+
if (!(parent instanceof TextRenderable2)) {
|
|
151
|
+
console.warn(`Cannot insert text:"${node.chunk.plainText}" unless wrapped with a <text> element.`);
|
|
152
|
+
const ghostNode = new GroupRenderable2(getNextId("ghost-group"), {});
|
|
153
|
+
ghostNode.getLayoutNode().yogaNode.setDisplay(2);
|
|
154
|
+
if (parent instanceof Renderable) {
|
|
155
|
+
parent.add(ghostNode);
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const styledText = parent.content;
|
|
160
|
+
if (anchor && anchor instanceof TextNode) {
|
|
161
|
+
const anchorIndex = styledText.chunks.indexOf(anchor.chunk);
|
|
162
|
+
if (anchorIndex == -1) {
|
|
163
|
+
console.log("anchor not found");
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
styledText.insert(node.chunk, anchorIndex);
|
|
167
|
+
} else {
|
|
168
|
+
const firstChunk = parent.content.chunks[0];
|
|
169
|
+
if (firstChunk && !ChunkToTextNodeMap.has(firstChunk)) {
|
|
170
|
+
styledText.replace(node.chunk, firstChunk);
|
|
171
|
+
} else {
|
|
172
|
+
styledText.insert(node.chunk);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
parent.content = styledText;
|
|
176
|
+
node.parent = parent;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!(parent instanceof Renderable)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
if (anchor) {
|
|
183
|
+
const anchorIndex = parent.getChildren().findIndex((el) => el.id === anchor.id);
|
|
184
|
+
parent.add(node, anchorIndex);
|
|
185
|
+
} else {
|
|
186
|
+
parent.add(node);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function _removeNode(parent, node) {
|
|
190
|
+
log("Removing node:", node.id, "from parent:", parent.id);
|
|
191
|
+
if (parent instanceof TextRenderable2 && node instanceof TextNode) {
|
|
192
|
+
ChunkToTextNodeMap.delete(node.chunk);
|
|
193
|
+
const styledText = parent.content;
|
|
194
|
+
styledText.remove(node.chunk);
|
|
195
|
+
parent.content = styledText;
|
|
196
|
+
} else if (parent instanceof Renderable && node instanceof Renderable) {
|
|
197
|
+
node.destroyRecursively();
|
|
198
|
+
parent.remove(node.id);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
var {
|
|
202
|
+
render: _render,
|
|
203
|
+
effect,
|
|
204
|
+
memo,
|
|
205
|
+
createComponent,
|
|
206
|
+
createElement,
|
|
207
|
+
createTextNode,
|
|
208
|
+
insertNode,
|
|
209
|
+
insert: solidUniversalInsert,
|
|
210
|
+
spread,
|
|
211
|
+
setProp,
|
|
212
|
+
mergeProps,
|
|
213
|
+
use
|
|
214
|
+
} = createRenderer({
|
|
215
|
+
createElement(tagName) {
|
|
216
|
+
log("Creating element:", tagName);
|
|
217
|
+
const id = getNextId(tagName);
|
|
218
|
+
const element = new elements[tagName](id, {});
|
|
219
|
+
log("Element created with id:", id);
|
|
220
|
+
return element;
|
|
221
|
+
},
|
|
222
|
+
createTextNode(value) {
|
|
223
|
+
log("Creating text node:", value);
|
|
224
|
+
const chunk = typeof value === "object" && "__isChunk" in value ? value : {
|
|
225
|
+
__isChunk: true,
|
|
226
|
+
text: new TextEncoder().encode(`${value}`),
|
|
227
|
+
plainText: `${value}`
|
|
228
|
+
};
|
|
229
|
+
const textNode = new TextNode(chunk);
|
|
230
|
+
ChunkToTextNodeMap.set(chunk, textNode);
|
|
231
|
+
return textNode;
|
|
232
|
+
},
|
|
233
|
+
replaceText(textNode, value) {
|
|
234
|
+
log("Replacing text:", value, "in node:", textNode.id);
|
|
235
|
+
if (textNode instanceof Renderable)
|
|
236
|
+
return;
|
|
237
|
+
const newChunk = {
|
|
238
|
+
__isChunk: true,
|
|
239
|
+
text: new TextEncoder().encode(value),
|
|
240
|
+
plainText: value
|
|
241
|
+
};
|
|
242
|
+
const parent = textNode.parent;
|
|
243
|
+
if (!parent) {
|
|
244
|
+
log("No parent found for text node:", textNode.id);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (parent instanceof TextRenderable2) {
|
|
248
|
+
const styledText = parent.content;
|
|
249
|
+
styledText.replace(newChunk, textNode.chunk);
|
|
250
|
+
parent.content = styledText;
|
|
251
|
+
textNode.chunk = newChunk;
|
|
252
|
+
ChunkToTextNodeMap.set(newChunk, textNode);
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
setProperty(node, name, value, prev) {
|
|
256
|
+
if (node instanceof TextNode) {
|
|
257
|
+
console.warn("Cannot set property on text node:", node.id);
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (name.startsWith("on:")) {
|
|
261
|
+
const eventName = name.slice(3);
|
|
262
|
+
if (value) {
|
|
263
|
+
node.on(eventName, value);
|
|
264
|
+
}
|
|
265
|
+
if (prev) {
|
|
266
|
+
node.off(eventName, prev);
|
|
267
|
+
}
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
switch (name) {
|
|
271
|
+
case "focused":
|
|
272
|
+
if (value) {
|
|
273
|
+
node.focus();
|
|
274
|
+
} else {
|
|
275
|
+
node.blur();
|
|
276
|
+
}
|
|
277
|
+
break;
|
|
278
|
+
case "onChange":
|
|
279
|
+
let event = undefined;
|
|
280
|
+
if (node instanceof SelectRenderable2) {
|
|
281
|
+
event = SelectRenderableEvents.SELECTION_CHANGED;
|
|
282
|
+
} else if (node instanceof TabSelectRenderable2) {
|
|
283
|
+
event = TabSelectRenderableEvents.SELECTION_CHANGED;
|
|
284
|
+
} else if (node instanceof InputRenderable2) {
|
|
285
|
+
event = InputRenderableEvents.CHANGE;
|
|
286
|
+
}
|
|
287
|
+
if (!event)
|
|
288
|
+
break;
|
|
289
|
+
if (value) {
|
|
290
|
+
node.on(event, value);
|
|
291
|
+
}
|
|
292
|
+
if (prev) {
|
|
293
|
+
node.off(event, prev);
|
|
294
|
+
}
|
|
295
|
+
break;
|
|
296
|
+
case "onInput":
|
|
297
|
+
if (node instanceof InputRenderable2) {
|
|
298
|
+
if (value) {
|
|
299
|
+
node.on(InputRenderableEvents.INPUT, value);
|
|
300
|
+
}
|
|
301
|
+
if (prev) {
|
|
302
|
+
node.off(InputRenderableEvents.INPUT, prev);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
break;
|
|
306
|
+
case "onSubmit":
|
|
307
|
+
if (node instanceof InputRenderable2) {
|
|
308
|
+
if (value) {
|
|
309
|
+
node.on(InputRenderableEvents.ENTER, value);
|
|
310
|
+
}
|
|
311
|
+
if (prev) {
|
|
312
|
+
node.off(InputRenderableEvents.ENTER, prev);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
break;
|
|
316
|
+
case "onSelect":
|
|
317
|
+
if (node instanceof SelectRenderable2) {
|
|
318
|
+
if (value) {
|
|
319
|
+
node.on(SelectRenderableEvents.ITEM_SELECTED, value);
|
|
320
|
+
}
|
|
321
|
+
if (prev) {
|
|
322
|
+
node.off(SelectRenderableEvents.ITEM_SELECTED, prev);
|
|
323
|
+
}
|
|
324
|
+
} else if (node instanceof TabSelectRenderable2) {
|
|
325
|
+
if (value) {
|
|
326
|
+
node.on(TabSelectRenderableEvents.ITEM_SELECTED, value);
|
|
327
|
+
}
|
|
328
|
+
if (prev) {
|
|
329
|
+
node.off(TabSelectRenderableEvents.ITEM_SELECTED, prev);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
break;
|
|
333
|
+
case "style":
|
|
334
|
+
for (const prop in value) {
|
|
335
|
+
const propVal = value[prop];
|
|
336
|
+
if (prev !== undefined && propVal === prev[prop])
|
|
337
|
+
continue;
|
|
338
|
+
node[prop] = propVal;
|
|
339
|
+
}
|
|
340
|
+
break;
|
|
341
|
+
case "text":
|
|
342
|
+
case "content":
|
|
343
|
+
node[name] = typeof value === "string" ? value : Array.isArray(value) ? value.join("") : `${value}`;
|
|
344
|
+
break;
|
|
345
|
+
default:
|
|
346
|
+
node[name] = value;
|
|
347
|
+
}
|
|
348
|
+
},
|
|
349
|
+
isTextNode(node) {
|
|
350
|
+
return node instanceof TextNode;
|
|
351
|
+
},
|
|
352
|
+
insertNode: _insertNode,
|
|
353
|
+
removeNode: _removeNode,
|
|
354
|
+
getParentNode(node) {
|
|
355
|
+
log("Getting parent of node:", node.id);
|
|
356
|
+
const parent = node.parent;
|
|
357
|
+
if (!parent) {
|
|
358
|
+
log("No parent found for node:", node.id);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
log("Parent found:", parent.id, "for node:", node.id);
|
|
362
|
+
return parent;
|
|
363
|
+
},
|
|
364
|
+
getFirstChild(node) {
|
|
365
|
+
log("Getting first child of node:", node.id);
|
|
366
|
+
if (node instanceof TextRenderable2) {
|
|
367
|
+
const chunk = node.content.chunks[0];
|
|
368
|
+
if (chunk) {
|
|
369
|
+
return ChunkToTextNodeMap.get(chunk);
|
|
370
|
+
} else {
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (node instanceof TextNode) {
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const firstChild = node.getChildren()[0];
|
|
378
|
+
if (!firstChild) {
|
|
379
|
+
log("No first child found for node:", node.id);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
log("First child found:", firstChild.id, "for node:", node.id);
|
|
383
|
+
return firstChild;
|
|
384
|
+
},
|
|
385
|
+
getNextSibling(node) {
|
|
386
|
+
log("Getting next sibling of node:", node.id);
|
|
387
|
+
const parent = node.parent;
|
|
388
|
+
if (!parent) {
|
|
389
|
+
log("No parent found for node:", node.id);
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
if (node instanceof TextNode) {
|
|
393
|
+
if (parent instanceof TextRenderable2) {
|
|
394
|
+
const siblings2 = parent.content.chunks;
|
|
395
|
+
const index2 = siblings2.indexOf(node.chunk);
|
|
396
|
+
if (index2 === -1 || index2 === siblings2.length - 1) {
|
|
397
|
+
log("No next sibling found for node:", node.id);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
const nextSibling2 = siblings2[index2 + 1];
|
|
401
|
+
if (!nextSibling2) {
|
|
402
|
+
log("Next sibling is null for node:", node.id);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
return ChunkToTextNodeMap.get(nextSibling2);
|
|
406
|
+
}
|
|
407
|
+
console.warn("Text parent is not a text node:", node.id);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
const siblings = parent.getChildren();
|
|
411
|
+
const index = siblings.indexOf(node);
|
|
412
|
+
if (index === -1 || index === siblings.length - 1) {
|
|
413
|
+
log("No next sibling found for node:", node.id);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
const nextSibling = siblings[index + 1];
|
|
417
|
+
if (!nextSibling) {
|
|
418
|
+
log("Next sibling is null for node:", node.id);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
log("Next sibling found:", nextSibling.id, "for node:", node.id);
|
|
422
|
+
return nextSibling;
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// index.ts
|
|
427
|
+
var render = async (node, renderConfig = {}) => {
|
|
428
|
+
const renderer = await createCliRenderer(renderConfig);
|
|
429
|
+
_render(() => createComponent(RendererContext.Provider, {
|
|
430
|
+
get value() {
|
|
431
|
+
return renderer;
|
|
432
|
+
},
|
|
433
|
+
get children() {
|
|
434
|
+
return createComponent(node, {});
|
|
435
|
+
}
|
|
436
|
+
}), renderer.root);
|
|
437
|
+
};
|
|
438
|
+
export {
|
|
439
|
+
useTimeline,
|
|
440
|
+
useTerminalDimensions,
|
|
441
|
+
useSelectionHandler,
|
|
442
|
+
useRenderer,
|
|
443
|
+
useKeyHandler,
|
|
444
|
+
render,
|
|
445
|
+
onResize,
|
|
446
|
+
elements,
|
|
447
|
+
createComponentTimeline,
|
|
448
|
+
RendererContext
|
|
449
|
+
};
|
package/jsx-runtime.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Renderable } from "@opentui/core";
|
|
2
|
+
import {
|
|
3
|
+
ASCIIFontElementProps,
|
|
4
|
+
BoxElementProps,
|
|
5
|
+
GroupElementProps,
|
|
6
|
+
InputElementProps,
|
|
7
|
+
SelectElementProps,
|
|
8
|
+
TabSelectElementProps,
|
|
9
|
+
TextElementProps,
|
|
10
|
+
} from "./src/elements/index";
|
|
11
|
+
|
|
12
|
+
declare namespace JSX {
|
|
13
|
+
// Replace Node with Renderable
|
|
14
|
+
type Element = Renderable | ArrayElement | (string & {}) | number | boolean | null | undefined;
|
|
15
|
+
|
|
16
|
+
interface ArrayElement extends Array<Element> {}
|
|
17
|
+
|
|
18
|
+
interface IntrinsicElements {
|
|
19
|
+
ascii_font: ASCIIFontElementProps;
|
|
20
|
+
box: BoxElementProps;
|
|
21
|
+
group: GroupElementProps;
|
|
22
|
+
input: InputElementProps;
|
|
23
|
+
select: SelectElementProps;
|
|
24
|
+
tab_select: TabSelectElementProps;
|
|
25
|
+
text: TextElementProps;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface ElementChildrenAttribute {
|
|
29
|
+
children: {};
|
|
30
|
+
}
|
|
31
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@opentui/solid",
|
|
3
|
+
"module": "index.js",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"types": "index.d.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"version": "0.1.6",
|
|
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
|
+
"./reconciler": {
|
|
22
|
+
"types": "./src/reconciler.d.ts",
|
|
23
|
+
"import": "./src/reconciler.js",
|
|
24
|
+
"require": "./src/reconciler.js"
|
|
25
|
+
},
|
|
26
|
+
"./preload": {
|
|
27
|
+
"import": "./scripts/preload.ts"
|
|
28
|
+
},
|
|
29
|
+
"./jsx-runtime": "./jsx-runtime.d.ts",
|
|
30
|
+
"./jsx-dev-runtime": "./jsx-runtime.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"@opentui/core": "0.1.6"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/babel__core": "^7.20.5",
|
|
37
|
+
"babel-plugin-module-resolver": "^5.0.2",
|
|
38
|
+
"@babel/core": "^7.28.0",
|
|
39
|
+
"@babel/preset-typescript": "^7.27.1",
|
|
40
|
+
"babel-preset-solid": "^1.9.9",
|
|
41
|
+
"@types/bun": "latest"
|
|
42
|
+
},
|
|
43
|
+
"peerDependencies": {
|
|
44
|
+
"solid-js": "^1.9.9",
|
|
45
|
+
"typescript": "^5"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
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: /\.(js|ts)$/ }, async (args) => {
|
|
12
|
+
// const { readFile } = await import("node:fs/promises");
|
|
13
|
+
// const code = await readFile(args.path, "utf8");
|
|
14
|
+
// const transforms = await transformAsync(code, {
|
|
15
|
+
// filename: args.path,
|
|
16
|
+
// // env: {
|
|
17
|
+
// // development: {
|
|
18
|
+
// // plugins: ["solid-refresh/babel"],
|
|
19
|
+
// // },
|
|
20
|
+
// // },
|
|
21
|
+
// presets: [[ts, {}]],
|
|
22
|
+
// });
|
|
23
|
+
// return {
|
|
24
|
+
// contents: transforms.code,
|
|
25
|
+
// loader: "js",
|
|
26
|
+
// };
|
|
27
|
+
// });
|
|
28
|
+
build.onLoad({ filter: /\.(js|ts)x$/ }, async (args) => {
|
|
29
|
+
const { readFile } = await import("node:fs/promises");
|
|
30
|
+
const code = await readFile(args.path, "utf8");
|
|
31
|
+
const transforms = await transformAsync(code, {
|
|
32
|
+
filename: args.path,
|
|
33
|
+
// env: {
|
|
34
|
+
// development: {
|
|
35
|
+
// plugins: [["solid-refresh/babel", { "bundler": "esm" }]],
|
|
36
|
+
// },
|
|
37
|
+
// },
|
|
38
|
+
// plugins: [["solid-refresh/babel", { bundler: "esm" }]],
|
|
39
|
+
presets: [
|
|
40
|
+
[
|
|
41
|
+
solid,
|
|
42
|
+
{
|
|
43
|
+
moduleName: "@opentui/solid/reconciler",
|
|
44
|
+
generate: "universal",
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
[ts],
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
return {
|
|
51
|
+
contents: transforms?.code ?? "",
|
|
52
|
+
loader: "js",
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
export default solidTransformPlugin;
|
|
@@ -0,0 +1,16 @@
|
|
|
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 useKeyHandler: (callback: (key: ParsedKey) => void) => void;
|
|
10
|
+
export declare const useSelectionHandler: (callback: (selection: Selection) => void) => void;
|
|
11
|
+
export declare const createComponentTimeline: (options?: TimelineOptions) => Timeline;
|
|
12
|
+
export declare const useTimeline: <T extends Record<string, number>>(timeline: Timeline, initialValue: T, targetValue: T, options: AnimationOptions & {
|
|
13
|
+
onUpdate?: (values: JSAnimation & {
|
|
14
|
+
targets: T[];
|
|
15
|
+
}) => void;
|
|
16
|
+
}, startTime?: number | string) => import("solid-js").Accessor<T>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ASCIIFontOptions, BoxOptions, InputRenderableOptions, Renderable, RenderableOptions, SelectOption, SelectRenderableOptions, StyledText, TabSelectOption, TabSelectRenderableOptions, TextChunk, TextOptions } from "@opentui/core";
|
|
2
|
+
import { ASCIIFontRenderable, BoxRenderable, GroupRenderable, InputRenderable, SelectRenderable, TabSelectRenderable, TextRenderable } from "@opentui/core";
|
|
3
|
+
import type { JSX, Ref } from "solid-js";
|
|
4
|
+
export * from "./hooks";
|
|
5
|
+
export declare const elements: {
|
|
6
|
+
ascii_font: typeof ASCIIFontRenderable;
|
|
7
|
+
box: typeof BoxRenderable;
|
|
8
|
+
group: typeof GroupRenderable;
|
|
9
|
+
input: typeof InputRenderable;
|
|
10
|
+
select: typeof SelectRenderable;
|
|
11
|
+
tab_select: typeof TabSelectRenderable;
|
|
12
|
+
text: typeof TextRenderable;
|
|
13
|
+
};
|
|
14
|
+
export type Element = keyof typeof elements;
|
|
15
|
+
type RenderableNonStyleKeys = "buffered";
|
|
16
|
+
type ElementProps<T extends RenderableOptions, K extends Renderable = Renderable, NonStyleKeys extends keyof T = RenderableNonStyleKeys> = {
|
|
17
|
+
style?: Omit<T, NonStyleKeys | RenderableNonStyleKeys>;
|
|
18
|
+
ref?: Ref<K>;
|
|
19
|
+
} & T;
|
|
20
|
+
type ContianerProps = {
|
|
21
|
+
children?: JSX.Element;
|
|
22
|
+
};
|
|
23
|
+
export type BoxElementProps = ElementProps<BoxOptions, BoxRenderable, "title"> & ContianerProps;
|
|
24
|
+
export type BoxStyle = BoxElementProps["style"];
|
|
25
|
+
export type GroupElementProps = ElementProps<RenderableOptions, GroupRenderable> & ContianerProps;
|
|
26
|
+
export type GroupStyle = GroupElementProps["style"];
|
|
27
|
+
export type InputElementProps = ElementProps<InputRenderableOptions, InputRenderable, "value" | "maxLength" | "placeholder"> & {
|
|
28
|
+
onInput?: (value: string) => void;
|
|
29
|
+
onSubmit?: (value: string) => void;
|
|
30
|
+
onChange?: (value: string) => void;
|
|
31
|
+
focused?: boolean;
|
|
32
|
+
};
|
|
33
|
+
export type InputStyle = InputElementProps["style"];
|
|
34
|
+
type TabSelectEventCallback = (index: number, option: TabSelectOption) => void;
|
|
35
|
+
export type TabSelectElementProps = ElementProps<TabSelectRenderableOptions, TabSelectRenderable, "options" | "showScrollArrows" | "showDescription" | "wrapSelection"> & {
|
|
36
|
+
onSelect?: TabSelectEventCallback;
|
|
37
|
+
onChange?: TabSelectEventCallback;
|
|
38
|
+
focused?: boolean;
|
|
39
|
+
};
|
|
40
|
+
export type TabSelectStyle = TabSelectElementProps["style"];
|
|
41
|
+
type SelectEventCallback = (index: number, option: SelectOption) => void;
|
|
42
|
+
export type SelectElementProps = ElementProps<SelectRenderableOptions, SelectRenderable, "options" | "showScrollIndicator" | "wrapSelection" | "fastScrollStep"> & {
|
|
43
|
+
onSelect?: SelectEventCallback;
|
|
44
|
+
onChange?: SelectEventCallback;
|
|
45
|
+
focused?: boolean;
|
|
46
|
+
};
|
|
47
|
+
export type SelectStyle = SelectElementProps["style"];
|
|
48
|
+
type TextChildTypes = (string & {}) | number | boolean | null | undefined;
|
|
49
|
+
type TextProps = {
|
|
50
|
+
children: TextChildTypes | StyledText | TextChunk | Array<TextChildTypes | TextChunk>;
|
|
51
|
+
};
|
|
52
|
+
export type ASCIIFontElementProps = ElementProps<ASCIIFontOptions, ASCIIFontRenderable, "text" | "selectable"> & {};
|
|
53
|
+
export type ASCIIFontStyle = ASCIIFontElementProps["style"];
|
|
54
|
+
export type TextElementProps = ElementProps<TextOptions, TextRenderable, "content" | "selectable"> & TextProps;
|
|
55
|
+
export type TextStyle = TextElementProps["style"];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Renderable, type TextChunk } from "@opentui/core";
|
|
2
|
+
declare class TextNode {
|
|
3
|
+
id: string;
|
|
4
|
+
chunk: TextChunk;
|
|
5
|
+
parent?: Renderable;
|
|
6
|
+
constructor(chunk: TextChunk);
|
|
7
|
+
}
|
|
8
|
+
type DomNode = Renderable | TextNode;
|
|
9
|
+
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, solidUniversalInsert: <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;
|
|
10
|
+
export declare const insert: typeof solidUniversalInsert;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/reconciler.ts
|
|
3
|
+
import {
|
|
4
|
+
GroupRenderable as GroupRenderable2,
|
|
5
|
+
InputRenderable as InputRenderable2,
|
|
6
|
+
InputRenderableEvents,
|
|
7
|
+
Renderable,
|
|
8
|
+
SelectRenderable as SelectRenderable2,
|
|
9
|
+
SelectRenderableEvents,
|
|
10
|
+
StyledText,
|
|
11
|
+
TabSelectRenderable as TabSelectRenderable2,
|
|
12
|
+
TabSelectRenderableEvents,
|
|
13
|
+
TextRenderable as TextRenderable2
|
|
14
|
+
} from "@opentui/core";
|
|
15
|
+
import { createRenderer } from "solid-js/universal";
|
|
16
|
+
|
|
17
|
+
// src/elements/index.ts
|
|
18
|
+
import {
|
|
19
|
+
ASCIIFontRenderable,
|
|
20
|
+
BoxRenderable,
|
|
21
|
+
GroupRenderable,
|
|
22
|
+
InputRenderable,
|
|
23
|
+
SelectRenderable,
|
|
24
|
+
TabSelectRenderable,
|
|
25
|
+
TextRenderable
|
|
26
|
+
} from "@opentui/core";
|
|
27
|
+
|
|
28
|
+
// src/elements/hooks.ts
|
|
29
|
+
import {
|
|
30
|
+
getKeyHandler,
|
|
31
|
+
Timeline
|
|
32
|
+
} from "@opentui/core";
|
|
33
|
+
import { createContext, createSignal, onCleanup, onMount, useContext } from "solid-js";
|
|
34
|
+
var RendererContext = createContext();
|
|
35
|
+
|
|
36
|
+
// src/elements/index.ts
|
|
37
|
+
var elements = {
|
|
38
|
+
ascii_font: ASCIIFontRenderable,
|
|
39
|
+
box: BoxRenderable,
|
|
40
|
+
group: GroupRenderable,
|
|
41
|
+
input: InputRenderable,
|
|
42
|
+
select: SelectRenderable,
|
|
43
|
+
tab_select: TabSelectRenderable,
|
|
44
|
+
text: TextRenderable
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/utils/id-counter.ts
|
|
48
|
+
var idCounter = new Map;
|
|
49
|
+
function getNextId(elementType) {
|
|
50
|
+
if (!idCounter.has(elementType)) {
|
|
51
|
+
idCounter.set(elementType, 0);
|
|
52
|
+
}
|
|
53
|
+
const value = idCounter.get(elementType) + 1;
|
|
54
|
+
idCounter.set(elementType, value);
|
|
55
|
+
return `${elementType}-${value}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// src/reconciler.ts
|
|
59
|
+
class TextNode {
|
|
60
|
+
id;
|
|
61
|
+
chunk;
|
|
62
|
+
parent;
|
|
63
|
+
constructor(chunk) {
|
|
64
|
+
this.id = getNextId("text-node");
|
|
65
|
+
this.chunk = chunk;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
var ChunkToTextNodeMap = new WeakMap;
|
|
69
|
+
var log = (...args) => {
|
|
70
|
+
console.log("[Reconciler]", ...args);
|
|
71
|
+
};
|
|
72
|
+
function _insertNode(parent, node, anchor) {
|
|
73
|
+
log("Inserting node:", node.id, "into parent:", parent.id, "with anchor:", anchor?.id);
|
|
74
|
+
if (node instanceof TextNode) {
|
|
75
|
+
if (!(parent instanceof TextRenderable2)) {
|
|
76
|
+
console.warn(`Cannot insert text:"${node.chunk.plainText}" unless wrapped with a <text> element.`);
|
|
77
|
+
const ghostNode = new GroupRenderable2(getNextId("ghost-group"), {});
|
|
78
|
+
ghostNode.getLayoutNode().yogaNode.setDisplay(2);
|
|
79
|
+
if (parent instanceof Renderable) {
|
|
80
|
+
parent.add(ghostNode);
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const styledText = parent.content;
|
|
85
|
+
if (anchor && anchor instanceof TextNode) {
|
|
86
|
+
const anchorIndex = styledText.chunks.indexOf(anchor.chunk);
|
|
87
|
+
if (anchorIndex == -1) {
|
|
88
|
+
console.log("anchor not found");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
styledText.insert(node.chunk, anchorIndex);
|
|
92
|
+
} else {
|
|
93
|
+
const firstChunk = parent.content.chunks[0];
|
|
94
|
+
if (firstChunk && !ChunkToTextNodeMap.has(firstChunk)) {
|
|
95
|
+
styledText.replace(node.chunk, firstChunk);
|
|
96
|
+
} else {
|
|
97
|
+
styledText.insert(node.chunk);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
parent.content = styledText;
|
|
101
|
+
node.parent = parent;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (!(parent instanceof Renderable)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (anchor) {
|
|
108
|
+
const anchorIndex = parent.getChildren().findIndex((el) => el.id === anchor.id);
|
|
109
|
+
parent.add(node, anchorIndex);
|
|
110
|
+
} else {
|
|
111
|
+
parent.add(node);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function _removeNode(parent, node) {
|
|
115
|
+
log("Removing node:", node.id, "from parent:", parent.id);
|
|
116
|
+
if (parent instanceof TextRenderable2 && node instanceof TextNode) {
|
|
117
|
+
ChunkToTextNodeMap.delete(node.chunk);
|
|
118
|
+
const styledText = parent.content;
|
|
119
|
+
styledText.remove(node.chunk);
|
|
120
|
+
parent.content = styledText;
|
|
121
|
+
} else if (parent instanceof Renderable && node instanceof Renderable) {
|
|
122
|
+
node.destroyRecursively();
|
|
123
|
+
parent.remove(node.id);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
var {
|
|
127
|
+
render: _render,
|
|
128
|
+
effect,
|
|
129
|
+
memo,
|
|
130
|
+
createComponent,
|
|
131
|
+
createElement,
|
|
132
|
+
createTextNode,
|
|
133
|
+
insertNode,
|
|
134
|
+
insert: solidUniversalInsert,
|
|
135
|
+
spread,
|
|
136
|
+
setProp,
|
|
137
|
+
mergeProps,
|
|
138
|
+
use
|
|
139
|
+
} = createRenderer({
|
|
140
|
+
createElement(tagName) {
|
|
141
|
+
log("Creating element:", tagName);
|
|
142
|
+
const id = getNextId(tagName);
|
|
143
|
+
const element = new elements[tagName](id, {});
|
|
144
|
+
log("Element created with id:", id);
|
|
145
|
+
return element;
|
|
146
|
+
},
|
|
147
|
+
createTextNode(value) {
|
|
148
|
+
log("Creating text node:", value);
|
|
149
|
+
const chunk = typeof value === "object" && "__isChunk" in value ? value : {
|
|
150
|
+
__isChunk: true,
|
|
151
|
+
text: new TextEncoder().encode(`${value}`),
|
|
152
|
+
plainText: `${value}`
|
|
153
|
+
};
|
|
154
|
+
const textNode = new TextNode(chunk);
|
|
155
|
+
ChunkToTextNodeMap.set(chunk, textNode);
|
|
156
|
+
return textNode;
|
|
157
|
+
},
|
|
158
|
+
replaceText(textNode, value) {
|
|
159
|
+
log("Replacing text:", value, "in node:", textNode.id);
|
|
160
|
+
if (textNode instanceof Renderable)
|
|
161
|
+
return;
|
|
162
|
+
const newChunk = {
|
|
163
|
+
__isChunk: true,
|
|
164
|
+
text: new TextEncoder().encode(value),
|
|
165
|
+
plainText: value
|
|
166
|
+
};
|
|
167
|
+
const parent = textNode.parent;
|
|
168
|
+
if (!parent) {
|
|
169
|
+
log("No parent found for text node:", textNode.id);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (parent instanceof TextRenderable2) {
|
|
173
|
+
const styledText = parent.content;
|
|
174
|
+
styledText.replace(newChunk, textNode.chunk);
|
|
175
|
+
parent.content = styledText;
|
|
176
|
+
textNode.chunk = newChunk;
|
|
177
|
+
ChunkToTextNodeMap.set(newChunk, textNode);
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
setProperty(node, name, value, prev) {
|
|
181
|
+
if (node instanceof TextNode) {
|
|
182
|
+
console.warn("Cannot set property on text node:", node.id);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (name.startsWith("on:")) {
|
|
186
|
+
const eventName = name.slice(3);
|
|
187
|
+
if (value) {
|
|
188
|
+
node.on(eventName, value);
|
|
189
|
+
}
|
|
190
|
+
if (prev) {
|
|
191
|
+
node.off(eventName, prev);
|
|
192
|
+
}
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
switch (name) {
|
|
196
|
+
case "focused":
|
|
197
|
+
if (value) {
|
|
198
|
+
node.focus();
|
|
199
|
+
} else {
|
|
200
|
+
node.blur();
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
case "onChange":
|
|
204
|
+
let event = undefined;
|
|
205
|
+
if (node instanceof SelectRenderable2) {
|
|
206
|
+
event = SelectRenderableEvents.SELECTION_CHANGED;
|
|
207
|
+
} else if (node instanceof TabSelectRenderable2) {
|
|
208
|
+
event = TabSelectRenderableEvents.SELECTION_CHANGED;
|
|
209
|
+
} else if (node instanceof InputRenderable2) {
|
|
210
|
+
event = InputRenderableEvents.CHANGE;
|
|
211
|
+
}
|
|
212
|
+
if (!event)
|
|
213
|
+
break;
|
|
214
|
+
if (value) {
|
|
215
|
+
node.on(event, value);
|
|
216
|
+
}
|
|
217
|
+
if (prev) {
|
|
218
|
+
node.off(event, prev);
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
case "onInput":
|
|
222
|
+
if (node instanceof InputRenderable2) {
|
|
223
|
+
if (value) {
|
|
224
|
+
node.on(InputRenderableEvents.INPUT, value);
|
|
225
|
+
}
|
|
226
|
+
if (prev) {
|
|
227
|
+
node.off(InputRenderableEvents.INPUT, prev);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
case "onSubmit":
|
|
232
|
+
if (node instanceof InputRenderable2) {
|
|
233
|
+
if (value) {
|
|
234
|
+
node.on(InputRenderableEvents.ENTER, value);
|
|
235
|
+
}
|
|
236
|
+
if (prev) {
|
|
237
|
+
node.off(InputRenderableEvents.ENTER, prev);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
break;
|
|
241
|
+
case "onSelect":
|
|
242
|
+
if (node instanceof SelectRenderable2) {
|
|
243
|
+
if (value) {
|
|
244
|
+
node.on(SelectRenderableEvents.ITEM_SELECTED, value);
|
|
245
|
+
}
|
|
246
|
+
if (prev) {
|
|
247
|
+
node.off(SelectRenderableEvents.ITEM_SELECTED, prev);
|
|
248
|
+
}
|
|
249
|
+
} else if (node instanceof TabSelectRenderable2) {
|
|
250
|
+
if (value) {
|
|
251
|
+
node.on(TabSelectRenderableEvents.ITEM_SELECTED, value);
|
|
252
|
+
}
|
|
253
|
+
if (prev) {
|
|
254
|
+
node.off(TabSelectRenderableEvents.ITEM_SELECTED, prev);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
break;
|
|
258
|
+
case "style":
|
|
259
|
+
for (const prop in value) {
|
|
260
|
+
const propVal = value[prop];
|
|
261
|
+
if (prev !== undefined && propVal === prev[prop])
|
|
262
|
+
continue;
|
|
263
|
+
node[prop] = propVal;
|
|
264
|
+
}
|
|
265
|
+
break;
|
|
266
|
+
case "text":
|
|
267
|
+
case "content":
|
|
268
|
+
node[name] = typeof value === "string" ? value : Array.isArray(value) ? value.join("") : `${value}`;
|
|
269
|
+
break;
|
|
270
|
+
default:
|
|
271
|
+
node[name] = value;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
isTextNode(node) {
|
|
275
|
+
return node instanceof TextNode;
|
|
276
|
+
},
|
|
277
|
+
insertNode: _insertNode,
|
|
278
|
+
removeNode: _removeNode,
|
|
279
|
+
getParentNode(node) {
|
|
280
|
+
log("Getting parent of node:", node.id);
|
|
281
|
+
const parent = node.parent;
|
|
282
|
+
if (!parent) {
|
|
283
|
+
log("No parent found for node:", node.id);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
log("Parent found:", parent.id, "for node:", node.id);
|
|
287
|
+
return parent;
|
|
288
|
+
},
|
|
289
|
+
getFirstChild(node) {
|
|
290
|
+
log("Getting first child of node:", node.id);
|
|
291
|
+
if (node instanceof TextRenderable2) {
|
|
292
|
+
const chunk = node.content.chunks[0];
|
|
293
|
+
if (chunk) {
|
|
294
|
+
return ChunkToTextNodeMap.get(chunk);
|
|
295
|
+
} else {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (node instanceof TextNode) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const firstChild = node.getChildren()[0];
|
|
303
|
+
if (!firstChild) {
|
|
304
|
+
log("No first child found for node:", node.id);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
log("First child found:", firstChild.id, "for node:", node.id);
|
|
308
|
+
return firstChild;
|
|
309
|
+
},
|
|
310
|
+
getNextSibling(node) {
|
|
311
|
+
log("Getting next sibling of node:", node.id);
|
|
312
|
+
const parent = node.parent;
|
|
313
|
+
if (!parent) {
|
|
314
|
+
log("No parent found for node:", node.id);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (node instanceof TextNode) {
|
|
318
|
+
if (parent instanceof TextRenderable2) {
|
|
319
|
+
const siblings2 = parent.content.chunks;
|
|
320
|
+
const index2 = siblings2.indexOf(node.chunk);
|
|
321
|
+
if (index2 === -1 || index2 === siblings2.length - 1) {
|
|
322
|
+
log("No next sibling found for node:", node.id);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
const nextSibling2 = siblings2[index2 + 1];
|
|
326
|
+
if (!nextSibling2) {
|
|
327
|
+
log("Next sibling is null for node:", node.id);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
return ChunkToTextNodeMap.get(nextSibling2);
|
|
331
|
+
}
|
|
332
|
+
console.warn("Text parent is not a text node:", node.id);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const siblings = parent.getChildren();
|
|
336
|
+
const index = siblings.indexOf(node);
|
|
337
|
+
if (index === -1 || index === siblings.length - 1) {
|
|
338
|
+
log("No next sibling found for node:", node.id);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const nextSibling = siblings[index + 1];
|
|
342
|
+
if (!nextSibling) {
|
|
343
|
+
log("Next sibling is null for node:", node.id);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
log("Next sibling found:", nextSibling.id, "for node:", node.id);
|
|
347
|
+
return nextSibling;
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
var insertStyledText = (parent, value, current, marker) => {
|
|
351
|
+
while (typeof current === "function")
|
|
352
|
+
current = current();
|
|
353
|
+
if (value === current)
|
|
354
|
+
return current;
|
|
355
|
+
if (current) {
|
|
356
|
+
if (typeof current === "object" && "__isChunk" in current) {
|
|
357
|
+
const node = ChunkToTextNodeMap.get(current);
|
|
358
|
+
if (node) {
|
|
359
|
+
_removeNode(parent, node);
|
|
360
|
+
}
|
|
361
|
+
} else if (current instanceof StyledText) {
|
|
362
|
+
for (const chunk of current.chunks) {
|
|
363
|
+
const chunkNode = ChunkToTextNodeMap.get(chunk);
|
|
364
|
+
if (!chunkNode)
|
|
365
|
+
continue;
|
|
366
|
+
_removeNode(parent, chunkNode);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (value instanceof StyledText) {
|
|
371
|
+
log("Inserting styled text:", value.toString());
|
|
372
|
+
for (const chunk of value.chunks) {
|
|
373
|
+
insertNode(parent, createTextNode(chunk), marker);
|
|
374
|
+
}
|
|
375
|
+
return value;
|
|
376
|
+
} else if (value && typeof value === "object" && "__isChunk" in value) {
|
|
377
|
+
insertNode(parent, createTextNode(value), marker);
|
|
378
|
+
return value;
|
|
379
|
+
}
|
|
380
|
+
return solidUniversalInsert(parent, value, marker, current);
|
|
381
|
+
};
|
|
382
|
+
var insert = (parent, accessor, marker, initial) => {
|
|
383
|
+
if (marker !== undefined && !initial)
|
|
384
|
+
initial = [];
|
|
385
|
+
if (typeof accessor !== "function")
|
|
386
|
+
return insertStyledText(parent, accessor, initial, marker);
|
|
387
|
+
effect((current) => insertStyledText(parent, accessor(), current, marker), initial);
|
|
388
|
+
};
|
|
389
|
+
export {
|
|
390
|
+
use,
|
|
391
|
+
spread,
|
|
392
|
+
solidUniversalInsert,
|
|
393
|
+
setProp,
|
|
394
|
+
mergeProps,
|
|
395
|
+
memo,
|
|
396
|
+
insertNode,
|
|
397
|
+
insert,
|
|
398
|
+
effect,
|
|
399
|
+
createTextNode,
|
|
400
|
+
createElement,
|
|
401
|
+
createComponent,
|
|
402
|
+
_render
|
|
403
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getNextId(elementType: string): string;
|