@opentui/solid 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,6 +11,7 @@ bun install solid-js @opentui/solid
11
11
  ## Usage
12
12
 
13
13
  1. Add jsx config to tsconfig.json:
14
+
14
15
  ```json
15
16
  {
16
17
  "compilerOptions": {
@@ -21,16 +22,17 @@ bun install solid-js @opentui/solid
21
22
  ```
22
23
 
23
24
  2. Add preload script to bunfig.toml:
25
+
24
26
  ```toml
25
27
  preload = ["@opentui/solid/preload"]
26
28
  ```
27
29
 
28
30
  3. Add render function to index.tsx:
31
+
29
32
  ```tsx
30
- import { render } from "@opentui/solid";
33
+ import { render } from "@opentui/solid"
31
34
 
32
- render(() => <text>Hello, World!</text>);
35
+ render(() => <text>Hello, World!</text>)
33
36
  ```
34
37
 
35
-
36
- 4. Run with `bun --conditions=browser index.tsx`.
38
+ 4. Run with `bun index.tsx`.
package/index.js CHANGED
@@ -108,16 +108,19 @@ var elements = {
108
108
  import {
109
109
  InputRenderable as InputRenderable2,
110
110
  InputRenderableEvents,
111
- Renderable,
111
+ Renderable as Renderable2,
112
112
  SelectRenderable as SelectRenderable2,
113
113
  SelectRenderableEvents,
114
114
  StyledText,
115
115
  TabSelectRenderable as TabSelectRenderable2,
116
116
  TabSelectRenderableEvents,
117
- TextRenderable as TextRenderable2
117
+ TextRenderable as TextRenderable3
118
118
  } from "@opentui/core";
119
119
  import { createRenderer } from "solid-js/universal";
120
120
 
121
+ // src/elements/text-node.ts
122
+ import { Renderable, TextRenderable as TextRenderable2 } from "@opentui/core";
123
+
121
124
  // src/utils/id-counter.ts
122
125
  var idCounter = new Map;
123
126
  function getNextId(elementType) {
@@ -129,8 +132,14 @@ function getNextId(elementType) {
129
132
  return `${elementType}-${value}`;
130
133
  }
131
134
 
132
- // src/reconciler.ts
135
+ // src/utils/log.ts
136
+ var log = (...args) => {
137
+ console.log("[Reconciler]", ...args);
138
+ };
139
+
140
+ // src/elements/text-node.ts
133
141
  var GHOST_NODE_TAG = "text-ghost";
142
+ var ChunkToTextNodeMap = new WeakMap;
134
143
 
135
144
  class TextNode {
136
145
  id;
@@ -140,93 +149,112 @@ class TextNode {
140
149
  constructor(chunk) {
141
150
  this.id = getNextId("text-node");
142
151
  this.chunk = chunk;
152
+ ChunkToTextNodeMap.set(chunk, this);
143
153
  }
144
- }
145
- var ChunkToTextNodeMap = new WeakMap;
146
- var log = (...args) => {
147
- console.log("[Reconciler]", ...args);
148
- };
149
- function getOrCreateTextGhostNode(parent, anchor) {
150
- if (anchor instanceof TextNode && anchor.textParent) {
151
- return anchor.textParent;
152
- }
153
- const children = parent.getChildren();
154
- if (anchor instanceof Renderable) {
155
- const anchorIndex = children.findIndex((el) => el.id === anchor.id);
156
- const beforeAnchor = children[anchorIndex - 1];
157
- if (beforeAnchor instanceof TextRenderable2 && beforeAnchor.id.startsWith(GHOST_NODE_TAG)) {
158
- return beforeAnchor;
154
+ replaceText(newChunk) {
155
+ const textParent = this.textParent;
156
+ if (!textParent) {
157
+ log("No parent found for text node:", this.id);
158
+ return;
159
159
  }
160
+ textParent.content = textParent.content.replace(newChunk, this.chunk);
161
+ this.chunk = newChunk;
162
+ ChunkToTextNodeMap.set(newChunk, this);
160
163
  }
161
- const lastChild = children.at(-1);
162
- if (lastChild instanceof TextRenderable2 && lastChild.id.startsWith(GHOST_NODE_TAG)) {
163
- return lastChild;
164
- }
165
- const ghostNode = new TextRenderable2(getNextId(GHOST_NODE_TAG), {});
166
- _insertNode(parent, ghostNode, anchor);
167
- return ghostNode;
168
- }
169
- function insertTextNode(parent, node, anchor) {
170
- if (!(parent instanceof Renderable)) {
171
- console.warn("Attaching text node to parent text node, impossible");
172
- return;
173
- }
174
- log("Inserting text node:", node.id, "into parent:", parent.id, "with anchor:", anchor?.id);
175
- let textParent;
176
- if (!(parent instanceof TextRenderable2)) {
177
- textParent = getOrCreateTextGhostNode(parent, anchor);
178
- } else {
179
- textParent = parent;
164
+ static getTextNodeFromChunk(chunk) {
165
+ return ChunkToTextNodeMap.get(chunk);
180
166
  }
181
- node.textParent = textParent;
182
- const styledText = textParent.content;
183
- if (anchor && anchor instanceof TextNode) {
184
- const anchorIndex = styledText.chunks.indexOf(anchor.chunk);
185
- if (anchorIndex == -1) {
186
- console.log("anchor not found");
167
+ insert(parent, anchor) {
168
+ if (!(parent instanceof Renderable)) {
169
+ log("Attaching text node to parent text node, impossible");
187
170
  return;
188
171
  }
189
- styledText.insert(node.chunk, anchorIndex);
190
- } else {
191
- const firstChunk = textParent.content.chunks[0];
192
- if (firstChunk && !ChunkToTextNodeMap.has(firstChunk)) {
193
- styledText.replace(node.chunk, firstChunk);
172
+ let textParent;
173
+ if (!(parent instanceof TextRenderable2)) {
174
+ textParent = this.getOrCreateTextGhostNode(parent, anchor);
175
+ } else {
176
+ textParent = parent;
177
+ }
178
+ this.textParent = textParent;
179
+ let styledText = textParent.content;
180
+ if (anchor && anchor instanceof TextNode) {
181
+ const anchorIndex = styledText.chunks.indexOf(anchor.chunk);
182
+ if (anchorIndex === -1) {
183
+ log("anchor not found");
184
+ return;
185
+ }
186
+ styledText = styledText.insert(this.chunk, anchorIndex);
194
187
  } else {
195
- styledText.insert(node.chunk);
188
+ const firstChunk = textParent.content.chunks[0];
189
+ if (firstChunk && !ChunkToTextNodeMap.has(firstChunk)) {
190
+ styledText = styledText.replace(this.chunk, firstChunk);
191
+ } else {
192
+ styledText = styledText.insert(this.chunk);
193
+ }
196
194
  }
195
+ textParent.content = styledText;
196
+ this.parent = parent;
197
197
  }
198
- textParent.content = styledText;
199
- node.parent = parent;
200
- return;
201
- }
202
- function removeTextNode(parent, node) {
203
- if (!(parent instanceof Renderable)) {
204
- ChunkToTextNodeMap.delete(node.chunk);
205
- return;
198
+ remove(parent) {
199
+ if (!(parent instanceof Renderable)) {
200
+ ChunkToTextNodeMap.delete(this.chunk);
201
+ return;
202
+ }
203
+ if (parent === this.textParent && parent instanceof TextRenderable2) {
204
+ ChunkToTextNodeMap.delete(this.chunk);
205
+ parent.content = parent.content.remove(this.chunk);
206
+ return;
207
+ }
208
+ if (this.textParent) {
209
+ ChunkToTextNodeMap.delete(this.chunk);
210
+ let styledText = this.textParent.content;
211
+ styledText = styledText.remove(this.chunk);
212
+ if (styledText.chunks.length > 0) {
213
+ this.textParent.content = styledText;
214
+ } else {
215
+ this.parent?.remove(this.textParent.id);
216
+ this.textParent.destroyRecursively();
217
+ }
218
+ }
206
219
  }
207
- if (parent === node.textParent && parent instanceof TextRenderable2) {
208
- ChunkToTextNodeMap.delete(node.chunk);
209
- const styledText = parent.content;
210
- styledText.remove(node.chunk);
211
- parent.content = styledText;
212
- } else if (node.textParent) {
213
- ChunkToTextNodeMap.delete(node.chunk);
214
- const styledText = node.textParent.content;
215
- styledText.remove(node.chunk);
216
- if (styledText.chunks.length > 0) {
217
- node.textParent.content = styledText;
218
- } else {
219
- node.parent?.remove(node.textParent.id);
220
- node.textParent.destroyRecursively();
220
+ getOrCreateTextGhostNode(parent, anchor) {
221
+ if (anchor instanceof TextNode && anchor.textParent) {
222
+ return anchor.textParent;
221
223
  }
224
+ const children = parent.getChildren();
225
+ if (anchor instanceof Renderable) {
226
+ const anchorIndex = children.findIndex((el) => el.id === anchor.id);
227
+ const beforeAnchor = children[anchorIndex - 1];
228
+ if (beforeAnchor instanceof GhostTextRenderable) {
229
+ return beforeAnchor;
230
+ }
231
+ }
232
+ const lastChild = children.at(-1);
233
+ if (lastChild instanceof GhostTextRenderable) {
234
+ return lastChild;
235
+ }
236
+ const ghostNode = new GhostTextRenderable(getNextId(GHOST_NODE_TAG), {});
237
+ insertNode(parent, ghostNode, anchor);
238
+ return ghostNode;
239
+ }
240
+ }
241
+
242
+ class GhostTextRenderable extends TextRenderable2 {
243
+ constructor(id, options) {
244
+ super(id, options);
245
+ }
246
+ static isGhostNode(node) {
247
+ return node instanceof GhostTextRenderable;
222
248
  }
223
249
  }
250
+
251
+ // src/reconciler.ts
224
252
  function _insertNode(parent, node, anchor) {
225
253
  log("Inserting node:", node.id, "into parent:", parent.id, "with anchor:", anchor?.id);
226
254
  if (node instanceof TextNode) {
227
- return insertTextNode(parent, node, anchor);
255
+ return node.insert(parent, anchor);
228
256
  }
229
- if (!(parent instanceof Renderable)) {
257
+ if (!(parent instanceof Renderable2)) {
230
258
  return;
231
259
  }
232
260
  if (anchor) {
@@ -244,9 +272,9 @@ function _insertNode(parent, node, anchor) {
244
272
  function _removeNode(parent, node) {
245
273
  log("Removing node:", node.id, "from parent:", parent.id);
246
274
  if (node instanceof TextNode) {
247
- return removeTextNode(parent, node);
275
+ return node.remove(parent);
248
276
  }
249
- if (parent instanceof Renderable && node instanceof Renderable) {
277
+ if (parent instanceof Renderable2 && node instanceof Renderable2) {
250
278
  parent.remove(node.id);
251
279
  node.destroyRecursively();
252
280
  }
@@ -280,30 +308,18 @@ var {
280
308
  plainText: `${value}`
281
309
  };
282
310
  const textNode = new TextNode(chunk);
283
- ChunkToTextNodeMap.set(chunk, textNode);
284
311
  return textNode;
285
312
  },
286
313
  replaceText(textNode, value) {
287
314
  log("Replacing text:", value, "in node:", textNode.id);
288
- if (textNode instanceof Renderable)
315
+ if (textNode instanceof Renderable2)
289
316
  return;
290
317
  const newChunk = {
291
318
  __isChunk: true,
292
319
  text: new TextEncoder().encode(value),
293
320
  plainText: value
294
321
  };
295
- const textParent = textNode.textParent;
296
- if (!textParent) {
297
- log("No parent found for text node:", textNode.id);
298
- return;
299
- }
300
- if (textParent instanceof TextRenderable2) {
301
- const styledText = textParent.content;
302
- styledText.replace(newChunk, textNode.chunk);
303
- textParent.content = styledText;
304
- textNode.chunk = newChunk;
305
- ChunkToTextNodeMap.set(newChunk, textNode);
306
- }
322
+ textNode.replaceText(newChunk);
307
323
  },
308
324
  setProperty(node, name, value, prev) {
309
325
  if (node instanceof TextNode) {
@@ -416,10 +432,10 @@ var {
416
432
  },
417
433
  getFirstChild(node) {
418
434
  log("Getting first child of node:", node.id);
419
- if (node instanceof TextRenderable2) {
435
+ if (node instanceof TextRenderable3) {
420
436
  const chunk = node.content.chunks[0];
421
437
  if (chunk) {
422
- return ChunkToTextNodeMap.get(chunk);
438
+ return TextNode.getTextNodeFromChunk(chunk);
423
439
  } else {
424
440
  return;
425
441
  }
@@ -443,7 +459,7 @@ var {
443
459
  return;
444
460
  }
445
461
  if (node instanceof TextNode) {
446
- if (parent instanceof TextRenderable2) {
462
+ if (parent instanceof TextRenderable3) {
447
463
  const siblings2 = parent.content.chunks;
448
464
  const index2 = siblings2.indexOf(node.chunk);
449
465
  if (index2 === -1 || index2 === siblings2.length - 1) {
@@ -455,7 +471,7 @@ var {
455
471
  log("Next sibling is null for node:", node.id);
456
472
  return;
457
473
  }
458
- return ChunkToTextNodeMap.get(nextSibling2);
474
+ return TextNode.getTextNodeFromChunk(nextSibling2);
459
475
  }
460
476
  console.warn("Text parent is not a text node:", node.id);
461
477
  return;
package/jsx-runtime.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Renderable } from "@opentui/core";
1
+ import { Renderable } from "@opentui/core"
2
2
  import {
3
3
  ASCIIFontElementProps,
4
4
  BoxElementProps,
@@ -7,25 +7,25 @@ import {
7
7
  SelectElementProps,
8
8
  TabSelectElementProps,
9
9
  TextElementProps,
10
- } from "./src/elements/index";
10
+ } from "./src/elements/index"
11
11
 
12
12
  declare namespace JSX {
13
13
  // Replace Node with Renderable
14
- type Element = Renderable | ArrayElement | (string & {}) | number | boolean | null | undefined;
14
+ type Element = Renderable | ArrayElement | (string & {}) | number | boolean | null | undefined
15
15
 
16
16
  interface ArrayElement extends Array<Element> {}
17
17
 
18
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;
19
+ ascii_font: ASCIIFontElementProps
20
+ box: BoxElementProps
21
+ group: GroupElementProps
22
+ input: InputElementProps
23
+ select: SelectElementProps
24
+ tab_select: TabSelectElementProps
25
+ text: TextElementProps
26
26
  }
27
27
 
28
28
  interface ElementChildrenAttribute {
29
- children: {};
29
+ children: {}
30
30
  }
31
31
  }
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.1.7",
7
+ "version": "0.1.9",
8
8
  "description": "SolidJS renderer for OpenTUI",
9
9
  "license": "MIT",
10
10
  "repository": {
@@ -30,18 +30,18 @@
30
30
  "./jsx-dev-runtime": "./jsx-runtime.d.ts"
31
31
  },
32
32
  "dependencies": {
33
- "@opentui/core": "0.1.7"
33
+ "@opentui/core": "0.1.9"
34
34
  },
35
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",
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
41
  "@types/bun": "latest"
42
42
  },
43
43
  "peerDependencies": {
44
- "solid-js": "^1.9.9",
44
+ "solid-js": "1.9.9",
45
45
  "typescript": "^5"
46
46
  }
47
47
  }
@@ -1,4 +1,4 @@
1
- import solidTransformPlugin from "./solid-plugin";
2
- import { plugin, type BunPlugin } from "bun";
1
+ import solidTransformPlugin from "./solid-plugin"
2
+ import { plugin, type BunPlugin } from "bun"
3
3
 
4
- plugin(solidTransformPlugin);
4
+ plugin(solidTransformPlugin)
@@ -1,33 +1,24 @@
1
- import { transformAsync } from "@babel/core";
1
+ import { transformAsync } from "@babel/core"
2
2
  // @ts-expect-error - Types not important.
3
- import ts from "@babel/preset-typescript";
3
+ import ts from "@babel/preset-typescript"
4
4
  // @ts-expect-error - Types not important.
5
- import solid from "babel-preset-solid";
6
- import { type BunPlugin } from "bun";
5
+ import solid from "babel-preset-solid"
6
+ import { type BunPlugin } from "bun"
7
7
 
8
8
  const solidTransformPlugin: BunPlugin = {
9
9
  name: "bun-plugin-solid",
10
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
- // });
11
+ build.onLoad({ filter: /\/node_modules\/solid-js\/dist\/server\.js$/ }, async (args) => {
12
+ const { readFile } = await import("node:fs/promises")
13
+ const path = args.path.replace("server.js", "solid.js")
14
+ const file = Bun.file(path)
15
+ const code = await file.text()
16
+ return { contents: code, loader: "js" }
17
+ })
28
18
  build.onLoad({ filter: /\.(js|ts)x$/ }, async (args) => {
29
- const { readFile } = await import("node:fs/promises");
30
- const code = await readFile(args.path, "utf8");
19
+ const { readFile } = await import("node:fs/promises")
20
+ const file = Bun.file(args.path)
21
+ const code = await file.text()
31
22
  const transforms = await transformAsync(code, {
32
23
  filename: args.path,
33
24
  // env: {
@@ -46,13 +37,13 @@ const solidTransformPlugin: BunPlugin = {
46
37
  ],
47
38
  [ts],
48
39
  ],
49
- });
40
+ })
50
41
  return {
51
42
  contents: transforms?.code ?? "",
52
43
  loader: "js",
53
- };
54
- });
44
+ }
45
+ })
55
46
  },
56
- };
47
+ }
57
48
 
58
- export default solidTransformPlugin;
49
+ export default solidTransformPlugin
@@ -17,12 +17,12 @@ type ElementProps<T extends RenderableOptions, K extends Renderable = Renderable
17
17
  style?: Omit<T, NonStyleKeys | RenderableNonStyleKeys>;
18
18
  ref?: Ref<K>;
19
19
  } & T;
20
- type ContianerProps = {
20
+ type ContainerProps = {
21
21
  children?: JSX.Element;
22
22
  };
23
- export type BoxElementProps = ElementProps<BoxOptions, BoxRenderable, "title"> & ContianerProps;
23
+ export type BoxElementProps = ElementProps<BoxOptions, BoxRenderable, "title"> & ContainerProps;
24
24
  export type BoxStyle = BoxElementProps["style"];
25
- export type GroupElementProps = ElementProps<RenderableOptions, GroupRenderable> & ContianerProps;
25
+ export type GroupElementProps = ElementProps<RenderableOptions, GroupRenderable> & ContainerProps;
26
26
  export type GroupStyle = GroupElementProps["style"];
27
27
  export type InputElementProps = ElementProps<InputRenderableOptions, InputRenderable, "value" | "maxLength" | "placeholder"> & {
28
28
  onInput?: (value: string) => void;
@@ -0,0 +1,47 @@
1
+ import { Renderable, TextRenderable, type TextChunk, type TextOptions } from "@opentui/core";
2
+ import { type DomNode } from "../reconciler";
3
+ /**
4
+ * Represents a text node in the SolidJS reconciler.
5
+ */
6
+ export declare class TextNode {
7
+ id: string;
8
+ chunk: TextChunk;
9
+ parent?: Renderable;
10
+ textParent?: TextRenderable | GhostTextRenderable;
11
+ constructor(chunk: TextChunk);
12
+ /**
13
+ * Replaces the current text chunk with a new one.
14
+ * @param newChunk The new text chunk to replace with.
15
+ */
16
+ replaceText(newChunk: TextChunk): void;
17
+ /**
18
+ * Retrieves the TextNode associated with a given TextChunk.
19
+ * @param chunk The text chunk to look up.
20
+ * @returns The associated TextNode or undefined if not found.
21
+ */
22
+ static getTextNodeFromChunk(chunk: TextChunk): TextNode | undefined;
23
+ /**
24
+ * Inserts this text node into the DOM structure.
25
+ * @param parent The parent DOM node.
26
+ * @param anchor The anchor node for positioning.
27
+ */
28
+ insert(parent: DomNode, anchor?: DomNode): void;
29
+ /**
30
+ * Removes this text node from the DOM structure.
31
+ * @param parent The parent DOM node.
32
+ */
33
+ remove(parent: DomNode): void;
34
+ /**
35
+ * Gets or creates a ghost text node for rendering text content.
36
+ * @param parent The parent renderable.
37
+ * @param anchor The anchor node for positioning.
38
+ * @returns The text renderable ghost node.
39
+ * @private
40
+ */
41
+ private getOrCreateTextGhostNode;
42
+ }
43
+ declare class GhostTextRenderable extends TextRenderable {
44
+ constructor(id: string, options: TextOptions);
45
+ static isGhostNode(node: DomNode): node is GhostTextRenderable;
46
+ }
47
+ export {};
@@ -1,12 +1,5 @@
1
- import { Renderable, TextRenderable, type TextChunk } from "@opentui/core";
2
- declare class TextNode {
3
- id: string;
4
- chunk: TextChunk;
5
- parent?: Renderable;
6
- textParent?: TextRenderable;
7
- constructor(chunk: TextChunk);
8
- }
9
- type DomNode = Renderable | TextNode;
1
+ import { Renderable } from "@opentui/core";
2
+ import { TextNode } from "./elements/text-node";
3
+ export type DomNode = Renderable | TextNode;
10
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, 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;
11
5
  export declare const insert: typeof solidUniversalInsert;
12
- export {};
package/src/reconciler.js CHANGED
@@ -3,13 +3,13 @@
3
3
  import {
4
4
  InputRenderable as InputRenderable2,
5
5
  InputRenderableEvents,
6
- Renderable,
6
+ Renderable as Renderable2,
7
7
  SelectRenderable as SelectRenderable2,
8
8
  SelectRenderableEvents,
9
9
  StyledText,
10
10
  TabSelectRenderable as TabSelectRenderable2,
11
11
  TabSelectRenderableEvents,
12
- TextRenderable as TextRenderable2
12
+ TextRenderable as TextRenderable3
13
13
  } from "@opentui/core";
14
14
  import { createRenderer } from "solid-js/universal";
15
15
 
@@ -43,6 +43,9 @@ var elements = {
43
43
  text: TextRenderable
44
44
  };
45
45
 
46
+ // src/elements/text-node.ts
47
+ import { Renderable, TextRenderable as TextRenderable2 } from "@opentui/core";
48
+
46
49
  // src/utils/id-counter.ts
47
50
  var idCounter = new Map;
48
51
  function getNextId(elementType) {
@@ -54,8 +57,14 @@ function getNextId(elementType) {
54
57
  return `${elementType}-${value}`;
55
58
  }
56
59
 
57
- // src/reconciler.ts
60
+ // src/utils/log.ts
61
+ var log = (...args) => {
62
+ console.log("[Reconciler]", ...args);
63
+ };
64
+
65
+ // src/elements/text-node.ts
58
66
  var GHOST_NODE_TAG = "text-ghost";
67
+ var ChunkToTextNodeMap = new WeakMap;
59
68
 
60
69
  class TextNode {
61
70
  id;
@@ -65,93 +74,112 @@ class TextNode {
65
74
  constructor(chunk) {
66
75
  this.id = getNextId("text-node");
67
76
  this.chunk = chunk;
77
+ ChunkToTextNodeMap.set(chunk, this);
68
78
  }
69
- }
70
- var ChunkToTextNodeMap = new WeakMap;
71
- var log = (...args) => {
72
- console.log("[Reconciler]", ...args);
73
- };
74
- function getOrCreateTextGhostNode(parent, anchor) {
75
- if (anchor instanceof TextNode && anchor.textParent) {
76
- return anchor.textParent;
77
- }
78
- const children = parent.getChildren();
79
- if (anchor instanceof Renderable) {
80
- const anchorIndex = children.findIndex((el) => el.id === anchor.id);
81
- const beforeAnchor = children[anchorIndex - 1];
82
- if (beforeAnchor instanceof TextRenderable2 && beforeAnchor.id.startsWith(GHOST_NODE_TAG)) {
83
- return beforeAnchor;
79
+ replaceText(newChunk) {
80
+ const textParent = this.textParent;
81
+ if (!textParent) {
82
+ log("No parent found for text node:", this.id);
83
+ return;
84
84
  }
85
+ textParent.content = textParent.content.replace(newChunk, this.chunk);
86
+ this.chunk = newChunk;
87
+ ChunkToTextNodeMap.set(newChunk, this);
85
88
  }
86
- const lastChild = children.at(-1);
87
- if (lastChild instanceof TextRenderable2 && lastChild.id.startsWith(GHOST_NODE_TAG)) {
88
- return lastChild;
89
- }
90
- const ghostNode = new TextRenderable2(getNextId(GHOST_NODE_TAG), {});
91
- _insertNode(parent, ghostNode, anchor);
92
- return ghostNode;
93
- }
94
- function insertTextNode(parent, node, anchor) {
95
- if (!(parent instanceof Renderable)) {
96
- console.warn("Attaching text node to parent text node, impossible");
97
- return;
98
- }
99
- log("Inserting text node:", node.id, "into parent:", parent.id, "with anchor:", anchor?.id);
100
- let textParent;
101
- if (!(parent instanceof TextRenderable2)) {
102
- textParent = getOrCreateTextGhostNode(parent, anchor);
103
- } else {
104
- textParent = parent;
89
+ static getTextNodeFromChunk(chunk) {
90
+ return ChunkToTextNodeMap.get(chunk);
105
91
  }
106
- node.textParent = textParent;
107
- const styledText = textParent.content;
108
- if (anchor && anchor instanceof TextNode) {
109
- const anchorIndex = styledText.chunks.indexOf(anchor.chunk);
110
- if (anchorIndex == -1) {
111
- console.log("anchor not found");
92
+ insert(parent, anchor) {
93
+ if (!(parent instanceof Renderable)) {
94
+ log("Attaching text node to parent text node, impossible");
112
95
  return;
113
96
  }
114
- styledText.insert(node.chunk, anchorIndex);
115
- } else {
116
- const firstChunk = textParent.content.chunks[0];
117
- if (firstChunk && !ChunkToTextNodeMap.has(firstChunk)) {
118
- styledText.replace(node.chunk, firstChunk);
97
+ let textParent;
98
+ if (!(parent instanceof TextRenderable2)) {
99
+ textParent = this.getOrCreateTextGhostNode(parent, anchor);
100
+ } else {
101
+ textParent = parent;
102
+ }
103
+ this.textParent = textParent;
104
+ let styledText = textParent.content;
105
+ if (anchor && anchor instanceof TextNode) {
106
+ const anchorIndex = styledText.chunks.indexOf(anchor.chunk);
107
+ if (anchorIndex === -1) {
108
+ log("anchor not found");
109
+ return;
110
+ }
111
+ styledText = styledText.insert(this.chunk, anchorIndex);
119
112
  } else {
120
- styledText.insert(node.chunk);
113
+ const firstChunk = textParent.content.chunks[0];
114
+ if (firstChunk && !ChunkToTextNodeMap.has(firstChunk)) {
115
+ styledText = styledText.replace(this.chunk, firstChunk);
116
+ } else {
117
+ styledText = styledText.insert(this.chunk);
118
+ }
121
119
  }
120
+ textParent.content = styledText;
121
+ this.parent = parent;
122
122
  }
123
- textParent.content = styledText;
124
- node.parent = parent;
125
- return;
126
- }
127
- function removeTextNode(parent, node) {
128
- if (!(parent instanceof Renderable)) {
129
- ChunkToTextNodeMap.delete(node.chunk);
130
- return;
123
+ remove(parent) {
124
+ if (!(parent instanceof Renderable)) {
125
+ ChunkToTextNodeMap.delete(this.chunk);
126
+ return;
127
+ }
128
+ if (parent === this.textParent && parent instanceof TextRenderable2) {
129
+ ChunkToTextNodeMap.delete(this.chunk);
130
+ parent.content = parent.content.remove(this.chunk);
131
+ return;
132
+ }
133
+ if (this.textParent) {
134
+ ChunkToTextNodeMap.delete(this.chunk);
135
+ let styledText = this.textParent.content;
136
+ styledText = styledText.remove(this.chunk);
137
+ if (styledText.chunks.length > 0) {
138
+ this.textParent.content = styledText;
139
+ } else {
140
+ this.parent?.remove(this.textParent.id);
141
+ this.textParent.destroyRecursively();
142
+ }
143
+ }
131
144
  }
132
- if (parent === node.textParent && parent instanceof TextRenderable2) {
133
- ChunkToTextNodeMap.delete(node.chunk);
134
- const styledText = parent.content;
135
- styledText.remove(node.chunk);
136
- parent.content = styledText;
137
- } else if (node.textParent) {
138
- ChunkToTextNodeMap.delete(node.chunk);
139
- const styledText = node.textParent.content;
140
- styledText.remove(node.chunk);
141
- if (styledText.chunks.length > 0) {
142
- node.textParent.content = styledText;
143
- } else {
144
- node.parent?.remove(node.textParent.id);
145
- node.textParent.destroyRecursively();
145
+ getOrCreateTextGhostNode(parent, anchor) {
146
+ if (anchor instanceof TextNode && anchor.textParent) {
147
+ return anchor.textParent;
146
148
  }
149
+ const children = parent.getChildren();
150
+ if (anchor instanceof Renderable) {
151
+ const anchorIndex = children.findIndex((el) => el.id === anchor.id);
152
+ const beforeAnchor = children[anchorIndex - 1];
153
+ if (beforeAnchor instanceof GhostTextRenderable) {
154
+ return beforeAnchor;
155
+ }
156
+ }
157
+ const lastChild = children.at(-1);
158
+ if (lastChild instanceof GhostTextRenderable) {
159
+ return lastChild;
160
+ }
161
+ const ghostNode = new GhostTextRenderable(getNextId(GHOST_NODE_TAG), {});
162
+ insertNode(parent, ghostNode, anchor);
163
+ return ghostNode;
164
+ }
165
+ }
166
+
167
+ class GhostTextRenderable extends TextRenderable2 {
168
+ constructor(id, options) {
169
+ super(id, options);
170
+ }
171
+ static isGhostNode(node) {
172
+ return node instanceof GhostTextRenderable;
147
173
  }
148
174
  }
175
+
176
+ // src/reconciler.ts
149
177
  function _insertNode(parent, node, anchor) {
150
178
  log("Inserting node:", node.id, "into parent:", parent.id, "with anchor:", anchor?.id);
151
179
  if (node instanceof TextNode) {
152
- return insertTextNode(parent, node, anchor);
180
+ return node.insert(parent, anchor);
153
181
  }
154
- if (!(parent instanceof Renderable)) {
182
+ if (!(parent instanceof Renderable2)) {
155
183
  return;
156
184
  }
157
185
  if (anchor) {
@@ -169,9 +197,9 @@ function _insertNode(parent, node, anchor) {
169
197
  function _removeNode(parent, node) {
170
198
  log("Removing node:", node.id, "from parent:", parent.id);
171
199
  if (node instanceof TextNode) {
172
- return removeTextNode(parent, node);
200
+ return node.remove(parent);
173
201
  }
174
- if (parent instanceof Renderable && node instanceof Renderable) {
202
+ if (parent instanceof Renderable2 && node instanceof Renderable2) {
175
203
  parent.remove(node.id);
176
204
  node.destroyRecursively();
177
205
  }
@@ -205,30 +233,18 @@ var {
205
233
  plainText: `${value}`
206
234
  };
207
235
  const textNode = new TextNode(chunk);
208
- ChunkToTextNodeMap.set(chunk, textNode);
209
236
  return textNode;
210
237
  },
211
238
  replaceText(textNode, value) {
212
239
  log("Replacing text:", value, "in node:", textNode.id);
213
- if (textNode instanceof Renderable)
240
+ if (textNode instanceof Renderable2)
214
241
  return;
215
242
  const newChunk = {
216
243
  __isChunk: true,
217
244
  text: new TextEncoder().encode(value),
218
245
  plainText: value
219
246
  };
220
- const textParent = textNode.textParent;
221
- if (!textParent) {
222
- log("No parent found for text node:", textNode.id);
223
- return;
224
- }
225
- if (textParent instanceof TextRenderable2) {
226
- const styledText = textParent.content;
227
- styledText.replace(newChunk, textNode.chunk);
228
- textParent.content = styledText;
229
- textNode.chunk = newChunk;
230
- ChunkToTextNodeMap.set(newChunk, textNode);
231
- }
247
+ textNode.replaceText(newChunk);
232
248
  },
233
249
  setProperty(node, name, value, prev) {
234
250
  if (node instanceof TextNode) {
@@ -341,10 +357,10 @@ var {
341
357
  },
342
358
  getFirstChild(node) {
343
359
  log("Getting first child of node:", node.id);
344
- if (node instanceof TextRenderable2) {
360
+ if (node instanceof TextRenderable3) {
345
361
  const chunk = node.content.chunks[0];
346
362
  if (chunk) {
347
- return ChunkToTextNodeMap.get(chunk);
363
+ return TextNode.getTextNodeFromChunk(chunk);
348
364
  } else {
349
365
  return;
350
366
  }
@@ -368,7 +384,7 @@ var {
368
384
  return;
369
385
  }
370
386
  if (node instanceof TextNode) {
371
- if (parent instanceof TextRenderable2) {
387
+ if (parent instanceof TextRenderable3) {
372
388
  const siblings2 = parent.content.chunks;
373
389
  const index2 = siblings2.indexOf(node.chunk);
374
390
  if (index2 === -1 || index2 === siblings2.length - 1) {
@@ -380,7 +396,7 @@ var {
380
396
  log("Next sibling is null for node:", node.id);
381
397
  return;
382
398
  }
383
- return ChunkToTextNodeMap.get(nextSibling2);
399
+ return TextNode.getTextNodeFromChunk(nextSibling2);
384
400
  }
385
401
  console.warn("Text parent is not a text node:", node.id);
386
402
  return;
@@ -407,13 +423,13 @@ var insertStyledText = (parent, value, current, marker) => {
407
423
  return current;
408
424
  if (current) {
409
425
  if (typeof current === "object" && "__isChunk" in current) {
410
- const node = ChunkToTextNodeMap.get(current);
426
+ const node = TextNode.getTextNodeFromChunk(current);
411
427
  if (node) {
412
428
  _removeNode(parent, node);
413
429
  }
414
430
  } else if (current instanceof StyledText) {
415
431
  for (const chunk of current.chunks) {
416
- const chunkNode = ChunkToTextNodeMap.get(chunk);
432
+ const chunkNode = TextNode.getTextNodeFromChunk(chunk);
417
433
  if (!chunkNode)
418
434
  continue;
419
435
  _removeNode(parent, chunkNode);
@@ -0,0 +1 @@
1
+ export declare const log: (...args: any[]) => void;