@opentui/solid 0.0.0-20250908-4906ddad → 0.0.0-20250915-f5db043a
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/dist/index.d.ts +88 -0
- package/index.js +137 -262
- package/jsx-runtime.d.ts +14 -2
- package/package.json +2 -2
- package/scripts/solid-plugin.ts +6 -0
- package/src/elements/index.d.ts +30 -1
- package/src/reconciler.d.ts +3 -4
- package/src/types/elements.d.ts +12 -6
- package/src/elements/text-node.d.ts +0 -48
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export function useTimeline(timeline: any, initialValue: any, targetValue: any, options: any, startTime?: number): import("solid-js").Accessor<any>;
|
|
2
|
+
export function useTerminalDimensions(): import("solid-js").Accessor<{
|
|
3
|
+
width: any;
|
|
4
|
+
height: any;
|
|
5
|
+
}>;
|
|
6
|
+
export function useSelectionHandler(callback: any): void;
|
|
7
|
+
export function useRenderer(): any;
|
|
8
|
+
export function useKeyboard(callback: any): void;
|
|
9
|
+
export function useKeyHandler(callback: any): void;
|
|
10
|
+
export var use: <A, T>(fn: (element: any, arg: A) => T, element: any, arg: A) => T;
|
|
11
|
+
export var textNodeKeys: string[];
|
|
12
|
+
export var spread: <T>(node: any, accessor: (() => T) | T, skipChildren?: boolean) => void;
|
|
13
|
+
export var setProp: <T>(node: any, name: string, value: T, prev?: T | undefined) => T;
|
|
14
|
+
export function render(node: any, renderConfig?: {}): Promise<void>;
|
|
15
|
+
export function onResize(callback: any): void;
|
|
16
|
+
export var mergeProps: (...sources: unknown[]) => unknown;
|
|
17
|
+
export var memo: <T>(fn: () => T, equal: boolean) => () => T;
|
|
18
|
+
export var insertNode: (parent: any, node: any, anchor?: any) => void;
|
|
19
|
+
export var insert: <T>(parent: any, accessor: T | (() => T), marker?: any | null, initial?: any) => any;
|
|
20
|
+
export function getComponentCatalogue(): {
|
|
21
|
+
box: typeof BoxRenderable;
|
|
22
|
+
text: typeof TextRenderable;
|
|
23
|
+
input: typeof InputRenderable;
|
|
24
|
+
select: typeof SelectRenderable;
|
|
25
|
+
ascii_font: typeof ASCIIFontRenderable;
|
|
26
|
+
tab_select: typeof TabSelectRenderable;
|
|
27
|
+
scrollbox: typeof ScrollBoxRenderable;
|
|
28
|
+
span: typeof SpanRenderable;
|
|
29
|
+
strong: typeof BoldSpanRenderable;
|
|
30
|
+
b: typeof BoldSpanRenderable;
|
|
31
|
+
em: typeof ItalicSpanRenderable;
|
|
32
|
+
i: typeof ItalicSpanRenderable;
|
|
33
|
+
u: typeof UnderlineSpanRenderable;
|
|
34
|
+
br: typeof LineBreakRenderable;
|
|
35
|
+
};
|
|
36
|
+
export function extend(objects: any): void;
|
|
37
|
+
export var effect: <T>(fn: (prev?: T) => T, init?: T) => void;
|
|
38
|
+
export var createTextNode: (value: string) => any;
|
|
39
|
+
export var createElement: (tag: string) => any;
|
|
40
|
+
export function createComponentTimeline(options?: {}): Timeline;
|
|
41
|
+
export var createComponent: <T>(Comp: (props: T) => any, props: T) => any;
|
|
42
|
+
export namespace componentCatalogue {
|
|
43
|
+
export { BoxRenderable as box };
|
|
44
|
+
export { TextRenderable as text };
|
|
45
|
+
export { InputRenderable as input };
|
|
46
|
+
export { SelectRenderable as select };
|
|
47
|
+
export { ASCIIFontRenderable as ascii_font };
|
|
48
|
+
export { TabSelectRenderable as tab_select };
|
|
49
|
+
export { ScrollBoxRenderable as scrollbox };
|
|
50
|
+
export { SpanRenderable as span };
|
|
51
|
+
export { BoldSpanRenderable as strong };
|
|
52
|
+
export { BoldSpanRenderable as b };
|
|
53
|
+
export { ItalicSpanRenderable as em };
|
|
54
|
+
export { ItalicSpanRenderable as i };
|
|
55
|
+
export { UnderlineSpanRenderable as u };
|
|
56
|
+
export { LineBreakRenderable as br };
|
|
57
|
+
}
|
|
58
|
+
export namespace baseComponents { }
|
|
59
|
+
export var _render: (code: () => any, node: any) => () => void;
|
|
60
|
+
export class UnderlineSpanRenderable extends TextModifierRenderable {
|
|
61
|
+
constructor(options: any);
|
|
62
|
+
}
|
|
63
|
+
export var RendererContext: import("solid-js").Context<any>;
|
|
64
|
+
export class LineBreakRenderable extends SpanRenderable {
|
|
65
|
+
add(): number;
|
|
66
|
+
}
|
|
67
|
+
export class ItalicSpanRenderable extends TextModifierRenderable {
|
|
68
|
+
constructor(options: any);
|
|
69
|
+
}
|
|
70
|
+
export class BoldSpanRenderable extends TextModifierRenderable {
|
|
71
|
+
constructor(options: any);
|
|
72
|
+
}
|
|
73
|
+
import { BoxRenderable } from "@opentui/core";
|
|
74
|
+
import { TextRenderable } from "@opentui/core";
|
|
75
|
+
import { InputRenderable } from "@opentui/core";
|
|
76
|
+
import { SelectRenderable } from "@opentui/core";
|
|
77
|
+
import { ASCIIFontRenderable } from "@opentui/core";
|
|
78
|
+
import { TabSelectRenderable } from "@opentui/core";
|
|
79
|
+
import { ScrollBoxRenderable } from "@opentui/core";
|
|
80
|
+
declare class SpanRenderable extends TextNodeRenderable {
|
|
81
|
+
constructor(_ctx: any, options: any);
|
|
82
|
+
_ctx: any;
|
|
83
|
+
}
|
|
84
|
+
import { Timeline } from "@opentui/core";
|
|
85
|
+
declare class TextModifierRenderable extends SpanRenderable {
|
|
86
|
+
}
|
|
87
|
+
import { TextNodeRenderable } from "@opentui/core";
|
|
88
|
+
export {};
|
package/index.js
CHANGED
|
@@ -10,6 +10,8 @@ import {
|
|
|
10
10
|
ScrollBoxRenderable,
|
|
11
11
|
SelectRenderable,
|
|
12
12
|
TabSelectRenderable,
|
|
13
|
+
TextAttributes,
|
|
14
|
+
TextNodeRenderable,
|
|
13
15
|
TextRenderable
|
|
14
16
|
} from "@opentui/core";
|
|
15
17
|
|
|
@@ -95,6 +97,56 @@ var useTimeline = (timeline, initialValue, targetValue, options, startTime = 0)
|
|
|
95
97
|
};
|
|
96
98
|
|
|
97
99
|
// src/elements/index.ts
|
|
100
|
+
class SpanRenderable extends TextNodeRenderable {
|
|
101
|
+
_ctx;
|
|
102
|
+
constructor(_ctx, options) {
|
|
103
|
+
super(options);
|
|
104
|
+
this._ctx = _ctx;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
var textNodeKeys = ["span", "b", "strong", "i", "em", "u"];
|
|
108
|
+
|
|
109
|
+
class TextModifierRenderable extends SpanRenderable {
|
|
110
|
+
constructor(options, modifier) {
|
|
111
|
+
super(null, options);
|
|
112
|
+
if (modifier === "b" || modifier === "strong") {
|
|
113
|
+
this.attributes = (this.attributes || 0) | TextAttributes.BOLD;
|
|
114
|
+
} else if (modifier === "i" || modifier === "em") {
|
|
115
|
+
this.attributes = (this.attributes || 0) | TextAttributes.ITALIC;
|
|
116
|
+
} else if (modifier === "u") {
|
|
117
|
+
this.attributes = (this.attributes || 0) | TextAttributes.UNDERLINE;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
class BoldSpanRenderable extends TextModifierRenderable {
|
|
123
|
+
constructor(options) {
|
|
124
|
+
super(options, "b");
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
class ItalicSpanRenderable extends TextModifierRenderable {
|
|
129
|
+
constructor(options) {
|
|
130
|
+
super(options, "i");
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
class UnderlineSpanRenderable extends TextModifierRenderable {
|
|
135
|
+
constructor(options) {
|
|
136
|
+
super(options, "u");
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
class LineBreakRenderable extends SpanRenderable {
|
|
141
|
+
constructor(_ctx, options) {
|
|
142
|
+
super(null, options);
|
|
143
|
+
this.add();
|
|
144
|
+
}
|
|
145
|
+
add() {
|
|
146
|
+
return super.add(`
|
|
147
|
+
`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
98
150
|
var baseComponents = {
|
|
99
151
|
box: BoxRenderable,
|
|
100
152
|
text: TextRenderable,
|
|
@@ -102,7 +154,14 @@ var baseComponents = {
|
|
|
102
154
|
select: SelectRenderable,
|
|
103
155
|
ascii_font: ASCIIFontRenderable,
|
|
104
156
|
tab_select: TabSelectRenderable,
|
|
105
|
-
scrollbox: ScrollBoxRenderable
|
|
157
|
+
scrollbox: ScrollBoxRenderable,
|
|
158
|
+
span: SpanRenderable,
|
|
159
|
+
strong: BoldSpanRenderable,
|
|
160
|
+
b: BoldSpanRenderable,
|
|
161
|
+
em: ItalicSpanRenderable,
|
|
162
|
+
i: ItalicSpanRenderable,
|
|
163
|
+
u: UnderlineSpanRenderable,
|
|
164
|
+
br: LineBreakRenderable
|
|
106
165
|
};
|
|
107
166
|
var componentCatalogue = { ...baseComponents };
|
|
108
167
|
function extend(objects) {
|
|
@@ -114,22 +173,24 @@ function getComponentCatalogue() {
|
|
|
114
173
|
|
|
115
174
|
// src/reconciler.ts
|
|
116
175
|
import {
|
|
176
|
+
BaseRenderable,
|
|
177
|
+
createTextAttributes,
|
|
117
178
|
InputRenderable as InputRenderable2,
|
|
118
179
|
InputRenderableEvents,
|
|
119
|
-
|
|
180
|
+
isTextNodeRenderable,
|
|
181
|
+
parseColor,
|
|
182
|
+
Renderable,
|
|
183
|
+
RootTextNodeRenderable,
|
|
120
184
|
SelectRenderable as SelectRenderable2,
|
|
121
185
|
SelectRenderableEvents,
|
|
122
|
-
StyledText,
|
|
123
186
|
TabSelectRenderable as TabSelectRenderable2,
|
|
124
187
|
TabSelectRenderableEvents,
|
|
125
|
-
|
|
188
|
+
TextNodeRenderable as TextNodeRenderable2,
|
|
189
|
+
TextRenderable as TextRenderable2
|
|
126
190
|
} from "@opentui/core";
|
|
127
191
|
import { useContext as useContext2 } from "solid-js";
|
|
128
192
|
import { createRenderer } from "solid-js/universal";
|
|
129
193
|
|
|
130
|
-
// src/elements/text-node.ts
|
|
131
|
-
import { Renderable, TextRenderable as TextRenderable2 } from "@opentui/core";
|
|
132
|
-
|
|
133
194
|
// src/utils/id-counter.ts
|
|
134
195
|
var idCounter = new Map;
|
|
135
196
|
function getNextId(elementType) {
|
|
@@ -148,216 +209,76 @@ var log = (...args) => {
|
|
|
148
209
|
}
|
|
149
210
|
};
|
|
150
211
|
|
|
151
|
-
// src/
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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;
|
|
212
|
+
// src/reconciler.ts
|
|
213
|
+
class TextNode extends TextNodeRenderable2 {
|
|
214
|
+
static fromString(text, options = {}) {
|
|
215
|
+
const node = new TextNode(options);
|
|
216
|
+
node.add(text);
|
|
217
|
+
return node;
|
|
278
218
|
}
|
|
279
219
|
}
|
|
280
|
-
|
|
281
|
-
// src/reconciler.ts
|
|
282
220
|
var logId = (node) => {
|
|
283
221
|
if (!node)
|
|
284
222
|
return;
|
|
285
|
-
if (isTextChunk(node)) {
|
|
286
|
-
return node.plainText;
|
|
287
|
-
}
|
|
288
223
|
return node.id;
|
|
289
224
|
};
|
|
225
|
+
var getNodeChildren = (node) => {
|
|
226
|
+
let children;
|
|
227
|
+
if (node instanceof TextRenderable2) {
|
|
228
|
+
children = node.getTextChildren();
|
|
229
|
+
} else {
|
|
230
|
+
children = node.getChildren();
|
|
231
|
+
}
|
|
232
|
+
return children;
|
|
233
|
+
};
|
|
290
234
|
function _insertNode(parent, node, anchor) {
|
|
291
235
|
log("Inserting node:", logId(node), "into parent:", logId(parent), "with anchor:", logId(anchor), node instanceof TextNode);
|
|
292
|
-
if (node
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
_insertNode(parent, _createTextNode(chunk), anchor);
|
|
236
|
+
if (isTextNodeRenderable(node)) {
|
|
237
|
+
if (!(parent instanceof TextRenderable2) && !isTextNodeRenderable(parent)) {
|
|
238
|
+
log(`Text must have a <text> as a parent: ${parent.id} above ${node.id}`);
|
|
296
239
|
return;
|
|
297
240
|
}
|
|
298
241
|
}
|
|
299
|
-
if (
|
|
300
|
-
|
|
242
|
+
if (!(parent instanceof BaseRenderable)) {
|
|
243
|
+
log("[INSERT]", "Tried to mount a non base renderable");
|
|
301
244
|
return;
|
|
302
245
|
}
|
|
303
|
-
if (
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
if (!(parent instanceof Renderable2)) {
|
|
246
|
+
if (!anchor) {
|
|
247
|
+
parent.add(node);
|
|
307
248
|
return;
|
|
308
249
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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);
|
|
250
|
+
const children = getNodeChildren(parent);
|
|
251
|
+
const anchorIndex = children.findIndex((el) => el.id === anchor.id);
|
|
252
|
+
if (anchorIndex === -1) {
|
|
253
|
+
log("[INSERT]", "Could not find anchor", logId(parent), logId(anchor), "[children]", ...children.map((c) => c.id));
|
|
323
254
|
}
|
|
255
|
+
parent.add(node, anchorIndex);
|
|
324
256
|
}
|
|
325
257
|
function _removeNode(parent, node) {
|
|
326
258
|
log("Removing node:", logId(node), "from parent:", logId(parent));
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if (
|
|
330
|
-
|
|
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);
|
|
259
|
+
parent.remove(node.id);
|
|
260
|
+
process.nextTick(() => {
|
|
261
|
+
if (node instanceof Renderable && !node.parent) {
|
|
262
|
+
node.destroyRecursively();
|
|
263
|
+
return;
|
|
338
264
|
}
|
|
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
|
-
}
|
|
265
|
+
});
|
|
351
266
|
}
|
|
352
267
|
function _createTextNode(value) {
|
|
353
268
|
log("Creating text node:", value);
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
};
|
|
359
|
-
|
|
360
|
-
|
|
269
|
+
const id = getNextId("text-node");
|
|
270
|
+
if (typeof value === "number") {
|
|
271
|
+
value = value.toString();
|
|
272
|
+
}
|
|
273
|
+
return TextNode.fromString(value, { id });
|
|
274
|
+
}
|
|
275
|
+
function _getParentNode(childNode) {
|
|
276
|
+
log("Getting parent of node:", logId(childNode));
|
|
277
|
+
let parent = childNode.parent ?? undefined;
|
|
278
|
+
if (parent instanceof RootTextNodeRenderable) {
|
|
279
|
+
parent = parent.textParent ?? undefined;
|
|
280
|
+
}
|
|
281
|
+
return parent;
|
|
361
282
|
}
|
|
362
283
|
var {
|
|
363
284
|
render: _render,
|
|
@@ -391,24 +312,11 @@ var {
|
|
|
391
312
|
createTextNode: _createTextNode,
|
|
392
313
|
replaceText(textNode, value) {
|
|
393
314
|
log("Replacing text:", value, "in node:", logId(textNode));
|
|
394
|
-
if (textNode instanceof
|
|
395
|
-
return;
|
|
396
|
-
if (isTextChunk(textNode)) {
|
|
397
|
-
console.warn("Cannot replace text on text chunk", logId(textNode));
|
|
315
|
+
if (!(textNode instanceof TextNode))
|
|
398
316
|
return;
|
|
399
|
-
|
|
400
|
-
const newChunk = {
|
|
401
|
-
__isChunk: true,
|
|
402
|
-
text: new TextEncoder().encode(value),
|
|
403
|
-
plainText: value
|
|
404
|
-
};
|
|
405
|
-
textNode.replaceText(newChunk);
|
|
317
|
+
textNode.replace(value, 0);
|
|
406
318
|
},
|
|
407
319
|
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
320
|
if (name.startsWith("on:")) {
|
|
413
321
|
const eventName = name.slice(3);
|
|
414
322
|
if (value) {
|
|
@@ -419,8 +327,19 @@ var {
|
|
|
419
327
|
}
|
|
420
328
|
return;
|
|
421
329
|
}
|
|
330
|
+
if (isTextNodeRenderable(node)) {
|
|
331
|
+
if (name !== "style") {
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
node.attributes |= createTextAttributes(value);
|
|
335
|
+
node.fg = value.fg ? parseColor(value.fg) : node.fg;
|
|
336
|
+
node.bg = value.bg ? parseColor(value.bg) : node.bg;
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
422
339
|
switch (name) {
|
|
423
340
|
case "focused":
|
|
341
|
+
if (!(node instanceof Renderable))
|
|
342
|
+
return;
|
|
424
343
|
if (value) {
|
|
425
344
|
node.focus();
|
|
426
345
|
} else {
|
|
@@ -503,37 +422,10 @@ var {
|
|
|
503
422
|
},
|
|
504
423
|
insertNode: _insertNode,
|
|
505
424
|
removeNode: _removeNode,
|
|
506
|
-
getParentNode
|
|
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
|
-
},
|
|
425
|
+
getParentNode: _getParentNode,
|
|
523
426
|
getFirstChild(node) {
|
|
524
427
|
log("Getting first child of node:", logId(node));
|
|
525
|
-
|
|
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];
|
|
428
|
+
const firstChild = getNodeChildren(node)[0];
|
|
537
429
|
if (!firstChild) {
|
|
538
430
|
log("No first child found for node:", logId(node));
|
|
539
431
|
return;
|
|
@@ -543,34 +435,12 @@ var {
|
|
|
543
435
|
},
|
|
544
436
|
getNextSibling(node) {
|
|
545
437
|
log("Getting next sibling of node:", logId(node));
|
|
546
|
-
|
|
547
|
-
console.warn("Cannot get next sibling of text chunk");
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
const parent = node.parent;
|
|
438
|
+
const parent = _getParentNode(node);
|
|
551
439
|
if (!parent) {
|
|
552
440
|
log("No parent found for node:", logId(node));
|
|
553
441
|
return;
|
|
554
442
|
}
|
|
555
|
-
|
|
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();
|
|
443
|
+
const siblings = getNodeChildren(node);
|
|
574
444
|
const index = siblings.indexOf(node);
|
|
575
445
|
if (index === -1 || index === siblings.length - 1) {
|
|
576
446
|
log("No next sibling found for node:", logId(node));
|
|
@@ -606,6 +476,7 @@ export {
|
|
|
606
476
|
useKeyboard,
|
|
607
477
|
useKeyHandler,
|
|
608
478
|
use,
|
|
479
|
+
textNodeKeys,
|
|
609
480
|
spread,
|
|
610
481
|
setProp,
|
|
611
482
|
render,
|
|
@@ -624,5 +495,9 @@ export {
|
|
|
624
495
|
componentCatalogue,
|
|
625
496
|
baseComponents,
|
|
626
497
|
_render,
|
|
627
|
-
|
|
498
|
+
UnderlineSpanRenderable,
|
|
499
|
+
RendererContext,
|
|
500
|
+
LineBreakRenderable,
|
|
501
|
+
ItalicSpanRenderable,
|
|
502
|
+
BoldSpanRenderable
|
|
628
503
|
};
|
package/jsx-runtime.d.ts
CHANGED
|
@@ -5,24 +5,36 @@ import type {
|
|
|
5
5
|
ExtendedIntrinsicElements,
|
|
6
6
|
InputProps,
|
|
7
7
|
OpenTUIComponents,
|
|
8
|
+
ScrollBoxProps,
|
|
8
9
|
SelectProps,
|
|
10
|
+
SpanProps,
|
|
9
11
|
TabSelectProps,
|
|
10
12
|
TextProps,
|
|
11
13
|
} from "./src/types/elements"
|
|
14
|
+
import type { DomNode } from "./dist"
|
|
12
15
|
|
|
13
16
|
declare namespace JSX {
|
|
14
17
|
// Replace Node with Renderable
|
|
15
|
-
type Element =
|
|
18
|
+
type Element = DomNode | ArrayElement | string | number | boolean | null | undefined
|
|
16
19
|
|
|
17
|
-
|
|
20
|
+
type ArrayElement = Array<Element>
|
|
18
21
|
|
|
19
22
|
interface IntrinsicElements extends ExtendedIntrinsicElements<OpenTUIComponents> {
|
|
20
23
|
box: BoxProps
|
|
21
24
|
text: TextProps
|
|
25
|
+
span: SpanProps
|
|
22
26
|
input: InputProps
|
|
23
27
|
select: SelectProps
|
|
24
28
|
ascii_font: AsciiFontProps
|
|
25
29
|
tab_select: TabSelectProps
|
|
30
|
+
scrollbox: ScrollBoxProps
|
|
31
|
+
|
|
32
|
+
b: SpanProps
|
|
33
|
+
strong: SpanProps
|
|
34
|
+
i: SpanProps
|
|
35
|
+
em: SpanProps
|
|
36
|
+
u: SpanProps
|
|
37
|
+
br: {}
|
|
26
38
|
}
|
|
27
39
|
|
|
28
40
|
interface ElementChildrenAttribute {
|
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "index.d.ts",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"version": "0.0.0-
|
|
7
|
+
"version": "0.0.0-20250915-f5db043a",
|
|
8
8
|
"description": "SolidJS renderer for OpenTUI",
|
|
9
9
|
"license": "MIT",
|
|
10
10
|
"repository": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"./jsx-dev-runtime": "./jsx-runtime.d.ts"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@opentui/core": "0.0.0-
|
|
28
|
+
"@opentui/core": "0.0.0-20250915-f5db043a",
|
|
29
29
|
"babel-plugin-module-resolver": "5.0.2",
|
|
30
30
|
"@babel/core": "7.28.0",
|
|
31
31
|
"@babel/preset-typescript": "7.27.1",
|
package/scripts/solid-plugin.ts
CHANGED
|
@@ -14,6 +14,12 @@ const solidTransformPlugin: BunPlugin = {
|
|
|
14
14
|
const code = await file.text()
|
|
15
15
|
return { contents: code, loader: "js" }
|
|
16
16
|
})
|
|
17
|
+
build.onLoad({ filter: /\/node_modules\/solid-js\/store\/dist\/server\.js$/ }, async (args) => {
|
|
18
|
+
const path = args.path.replace("server.js", "store.js")
|
|
19
|
+
const file = Bun.file(path)
|
|
20
|
+
const code = await file.text()
|
|
21
|
+
return { contents: code, loader: "js" }
|
|
22
|
+
})
|
|
17
23
|
build.onLoad({ filter: /\.(js|ts)x$/ }, async (args) => {
|
|
18
24
|
const file = Bun.file(args.path)
|
|
19
25
|
const code = await file.text()
|
package/src/elements/index.d.ts
CHANGED
|
@@ -1,6 +1,28 @@
|
|
|
1
|
-
import { ASCIIFontRenderable, BoxRenderable, InputRenderable, ScrollBoxRenderable, SelectRenderable, TabSelectRenderable, TextRenderable } from "@opentui/core";
|
|
1
|
+
import { ASCIIFontRenderable, BoxRenderable, InputRenderable, ScrollBoxRenderable, SelectRenderable, TabSelectRenderable, TextNodeRenderable, TextRenderable, type RenderContext, type TextNodeOptions } from "@opentui/core";
|
|
2
2
|
import type { RenderableConstructor } from "../types/elements";
|
|
3
3
|
export * from "./hooks";
|
|
4
|
+
declare class SpanRenderable extends TextNodeRenderable {
|
|
5
|
+
private readonly _ctx;
|
|
6
|
+
constructor(_ctx: RenderContext | null, options: TextNodeOptions);
|
|
7
|
+
}
|
|
8
|
+
export declare const textNodeKeys: readonly ["span", "b", "strong", "i", "em", "u"];
|
|
9
|
+
export type TextNodeKey = (typeof textNodeKeys)[number];
|
|
10
|
+
declare class TextModifierRenderable extends SpanRenderable {
|
|
11
|
+
constructor(options: any, modifier?: TextNodeKey);
|
|
12
|
+
}
|
|
13
|
+
export declare class BoldSpanRenderable extends TextModifierRenderable {
|
|
14
|
+
constructor(options: any);
|
|
15
|
+
}
|
|
16
|
+
export declare class ItalicSpanRenderable extends TextModifierRenderable {
|
|
17
|
+
constructor(options: any);
|
|
18
|
+
}
|
|
19
|
+
export declare class UnderlineSpanRenderable extends TextModifierRenderable {
|
|
20
|
+
constructor(options: any);
|
|
21
|
+
}
|
|
22
|
+
export declare class LineBreakRenderable extends SpanRenderable {
|
|
23
|
+
constructor(_ctx: RenderContext | null, options: TextNodeOptions);
|
|
24
|
+
add(): number;
|
|
25
|
+
}
|
|
4
26
|
export declare const baseComponents: {
|
|
5
27
|
box: typeof BoxRenderable;
|
|
6
28
|
text: typeof TextRenderable;
|
|
@@ -9,6 +31,13 @@ export declare const baseComponents: {
|
|
|
9
31
|
ascii_font: typeof ASCIIFontRenderable;
|
|
10
32
|
tab_select: typeof TabSelectRenderable;
|
|
11
33
|
scrollbox: typeof ScrollBoxRenderable;
|
|
34
|
+
span: typeof SpanRenderable;
|
|
35
|
+
strong: typeof BoldSpanRenderable;
|
|
36
|
+
b: typeof BoldSpanRenderable;
|
|
37
|
+
em: typeof ItalicSpanRenderable;
|
|
38
|
+
i: typeof ItalicSpanRenderable;
|
|
39
|
+
u: typeof UnderlineSpanRenderable;
|
|
40
|
+
br: typeof LineBreakRenderable;
|
|
12
41
|
};
|
|
13
42
|
type ComponentCatalogue = Record<string, RenderableConstructor>;
|
|
14
43
|
export declare const componentCatalogue: ComponentCatalogue;
|
package/src/reconciler.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export
|
|
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;
|
|
1
|
+
import { BaseRenderable } from "@opentui/core";
|
|
2
|
+
export type DomNode = BaseRenderable;
|
|
3
|
+
export declare const _render: (code: () => BaseRenderable, node: BaseRenderable) => () => void, effect: <T>(fn: (prev?: T) => T, init?: T) => void, memo: <T>(fn: () => T, equal: boolean) => () => T, createComponent: <T>(Comp: (props: T) => BaseRenderable, props: T) => BaseRenderable, createElement: (tag: string) => BaseRenderable, createTextNode: (value: string) => BaseRenderable, insertNode: (parent: BaseRenderable, node: BaseRenderable, anchor?: BaseRenderable | undefined) => void, insert: <T>(parent: any, accessor: T | (() => T), marker?: any | null, initial?: any) => BaseRenderable, spread: <T>(node: any, accessor: (() => T) | T, skipChildren?: boolean) => void, setProp: <T>(node: BaseRenderable, name: string, value: T, prev?: T | undefined) => T, mergeProps: (...sources: unknown[]) => unknown, use: <A, T>(fn: (element: BaseRenderable, arg: A) => T, element: BaseRenderable, arg: A) => T;
|
package/src/types/elements.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { ASCIIFontOptions, ASCIIFontRenderable, BoxOptions, BoxRenderable, InputRenderable, InputRenderableOptions,
|
|
2
|
-
import type {
|
|
1
|
+
import type { ASCIIFontOptions, ASCIIFontRenderable, BaseRenderable, BoxOptions, BoxRenderable, InputRenderable, InputRenderableOptions, RenderableOptions, RenderContext, ScrollBoxOptions, ScrollBoxRenderable, SelectOption, SelectRenderable, SelectRenderableOptions, TabSelectOption, TabSelectRenderable, TabSelectRenderableOptions, TextNodeRenderable, TextOptions, TextRenderable } from "@opentui/core";
|
|
2
|
+
import type { Ref } from "solid-js";
|
|
3
|
+
import type { JSX } from "../../jsx-runtime";
|
|
3
4
|
/** Properties that should not be included in the style prop */
|
|
4
5
|
export type NonStyledProps = "id" | "buffered" | "live" | "enableLayout" | "selectable" | "renderAfter" | "renderBefore" | `on${string}`;
|
|
5
6
|
/** Solid-specific props for all components */
|
|
@@ -7,7 +8,7 @@ export type ElementProps<TRenderable = unknown> = {
|
|
|
7
8
|
ref?: Ref<TRenderable>;
|
|
8
9
|
};
|
|
9
10
|
/** Base type for any renderable constructor */
|
|
10
|
-
export type RenderableConstructor<TRenderable extends
|
|
11
|
+
export type RenderableConstructor<TRenderable extends BaseRenderable = BaseRenderable> = new (ctx: RenderContext, options: any) => TRenderable;
|
|
11
12
|
/** Extract the options type from a renderable constructor */
|
|
12
13
|
type ExtractRenderableOptions<TConstructor> = TConstructor extends new (ctx: RenderContext, options: infer TOptions) => any ? TOptions : never;
|
|
13
14
|
/** Extract the renderable type from a constructor */
|
|
@@ -19,13 +20,16 @@ type ContainerProps<TOptions> = TOptions & {
|
|
|
19
20
|
children?: JSX.Element;
|
|
20
21
|
};
|
|
21
22
|
/** Smart component props that automatically determine excluded properties */
|
|
22
|
-
type ComponentProps<TOptions extends RenderableOptions<TRenderable>, TRenderable extends
|
|
23
|
+
type ComponentProps<TOptions extends RenderableOptions<TRenderable>, TRenderable extends BaseRenderable> = TOptions & {
|
|
23
24
|
style?: Partial<Omit<TOptions, GetNonStyledProperties<RenderableConstructor<TRenderable>>>>;
|
|
24
25
|
} & ElementProps<TRenderable>;
|
|
25
26
|
/** Valid text content types for Text component children */
|
|
26
|
-
type TextChildren =
|
|
27
|
+
type TextChildren = string | number | boolean | null | undefined | JSX.Element;
|
|
27
28
|
export type TextProps = ComponentProps<TextOptions, TextRenderable> & {
|
|
28
|
-
children?: TextChildren |
|
|
29
|
+
children?: TextChildren | Array<TextChildren>;
|
|
30
|
+
};
|
|
31
|
+
export type SpanProps = ComponentProps<{}, TextNodeRenderable> & {
|
|
32
|
+
children?: TextChildren | Array<TextChildren>;
|
|
29
33
|
};
|
|
30
34
|
export type BoxProps = ComponentProps<ContainerProps<BoxOptions>, BoxRenderable>;
|
|
31
35
|
export type InputProps = ComponentProps<InputRenderableOptions, InputRenderable> & {
|
|
@@ -47,6 +51,8 @@ export type TabSelectProps = ComponentProps<TabSelectRenderableOptions, TabSelec
|
|
|
47
51
|
};
|
|
48
52
|
export type ScrollBoxProps = ComponentProps<ContainerProps<ScrollBoxOptions>, ScrollBoxRenderable> & {
|
|
49
53
|
focused?: boolean;
|
|
54
|
+
stickyScroll?: boolean;
|
|
55
|
+
stickyStart?: "bottom" | "top" | "left" | "right";
|
|
50
56
|
};
|
|
51
57
|
/** Convert renderable constructor to component props with proper style exclusions */
|
|
52
58
|
export type ExtendedComponentProps<TConstructor extends RenderableConstructor, TOptions = ExtractRenderableOptions<TConstructor>> = TOptions & {
|
|
@@ -1,48 +0,0 @@
|
|
|
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 {};
|