@opentui/solid 0.1.23 → 0.1.25

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/index.js CHANGED
@@ -1,21 +1,23 @@
1
1
  // @bun
2
2
  // index.ts
3
3
  import { createCliRenderer } from "@opentui/core";
4
+ import { createTestRenderer } from "@opentui/core/testing";
4
5
 
5
6
  // src/elements/index.ts
6
7
  import {
7
8
  ASCIIFontRenderable,
8
9
  BoxRenderable,
9
- InputRenderable,
10
+ InputRenderable as InputRenderable2,
10
11
  ScrollBoxRenderable,
11
- SelectRenderable,
12
- TabSelectRenderable,
13
- TextRenderable
12
+ SelectRenderable as SelectRenderable2,
13
+ TabSelectRenderable as TabSelectRenderable2,
14
+ TextAttributes,
15
+ TextNodeRenderable as TextNodeRenderable3,
16
+ TextRenderable as TextRenderable3
14
17
  } from "@opentui/core";
15
18
 
16
19
  // src/elements/hooks.ts
17
20
  import {
18
- getKeyHandler,
19
21
  Timeline
20
22
  } from "@opentui/core";
21
23
  import { createContext, createSignal, onCleanup, onMount, useContext } from "solid-js";
@@ -46,7 +48,8 @@ var useTerminalDimensions = () => {
46
48
  return terminalDimensions;
47
49
  };
48
50
  var useKeyboard = (callback) => {
49
- const keyHandler = getKeyHandler();
51
+ const renderer = useRenderer();
52
+ const keyHandler = renderer.keyInput;
50
53
  onMount(() => {
51
54
  keyHandler.on("keypress", callback);
52
55
  });
@@ -54,6 +57,16 @@ var useKeyboard = (callback) => {
54
57
  keyHandler.off("keypress", callback);
55
58
  });
56
59
  };
60
+ var usePaste = (callback) => {
61
+ const renderer = useRenderer();
62
+ const keyHandler = renderer.keyInput;
63
+ onMount(() => {
64
+ keyHandler.on("paste", callback);
65
+ });
66
+ onCleanup(() => {
67
+ keyHandler.off("paste", callback);
68
+ });
69
+ };
57
70
  var useKeyHandler = useKeyboard;
58
71
  var useSelectionHandler = (callback) => {
59
72
  const renderer = useRenderer();
@@ -93,285 +106,421 @@ var useTimeline = (timeline, initialValue, targetValue, options, startTime = 0)
93
106
  }, startTime);
94
107
  return store;
95
108
  };
96
-
97
- // src/elements/index.ts
98
- var baseComponents = {
99
- box: BoxRenderable,
100
- text: TextRenderable,
101
- input: InputRenderable,
102
- select: SelectRenderable,
103
- ascii_font: ASCIIFontRenderable,
104
- tab_select: TabSelectRenderable,
105
- scrollbox: ScrollBoxRenderable
106
- };
107
- var componentCatalogue = { ...baseComponents };
108
- function extend(objects) {
109
- Object.assign(componentCatalogue, objects);
110
- }
111
- function getComponentCatalogue() {
112
- return componentCatalogue;
113
- }
109
+ // src/elements/extras.ts
110
+ import { createEffect, createMemo as createMemo2, getOwner, onCleanup as onCleanup2, runWithOwner, splitProps, untrack as untrack2 } from "solid-js";
114
111
 
115
112
  // src/reconciler.ts
116
113
  import {
117
- InputRenderable as InputRenderable2,
114
+ BaseRenderable,
115
+ createTextAttributes,
116
+ InputRenderable,
118
117
  InputRenderableEvents,
119
- Renderable as Renderable2,
120
- SelectRenderable as SelectRenderable2,
118
+ isTextNodeRenderable,
119
+ parseColor,
120
+ Renderable,
121
+ RootTextNodeRenderable,
122
+ SelectRenderable,
121
123
  SelectRenderableEvents,
122
- StyledText,
123
- TabSelectRenderable as TabSelectRenderable2,
124
+ TabSelectRenderable,
124
125
  TabSelectRenderableEvents,
125
- TextRenderable as TextRenderable3
126
+ TextNodeRenderable,
127
+ TextRenderable
126
128
  } from "@opentui/core";
127
129
  import { useContext as useContext2 } from "solid-js";
128
- import { createRenderer } from "solid-js/universal";
129
-
130
- // src/elements/text-node.ts
131
- import { Renderable, TextRenderable as TextRenderable2 } from "@opentui/core";
132
130
 
133
- // src/utils/id-counter.ts
134
- var idCounter = new Map;
135
- function getNextId(elementType) {
136
- if (!idCounter.has(elementType)) {
137
- idCounter.set(elementType, 0);
131
+ // src/renderer/universal.js
132
+ import { createRoot, createRenderEffect, createMemo, createComponent, untrack, mergeProps } from "solid-js";
133
+ var memo = (fn) => createMemo(() => fn());
134
+ function createRenderer({
135
+ createElement,
136
+ createTextNode,
137
+ createSlotNode,
138
+ isTextNode,
139
+ replaceText,
140
+ insertNode,
141
+ removeNode,
142
+ setProperty,
143
+ getParentNode,
144
+ getFirstChild,
145
+ getNextSibling
146
+ }) {
147
+ function insert(parent, accessor, marker, initial) {
148
+ if (marker !== undefined && !initial)
149
+ initial = [];
150
+ if (typeof accessor !== "function")
151
+ return insertExpression(parent, accessor, initial, marker);
152
+ createRenderEffect((current) => insertExpression(parent, accessor(), current, marker), initial);
138
153
  }
139
- const value = idCounter.get(elementType) + 1;
140
- idCounter.set(elementType, value);
141
- return `${elementType}-${value}`;
142
- }
143
-
144
- // src/utils/log.ts
145
- var log = (...args) => {
146
- if (process.env.DEBUG) {
147
- console.log("[Reconciler]", ...args);
154
+ function insertExpression(parent, value, current, marker, unwrapArray) {
155
+ while (typeof current === "function")
156
+ current = current();
157
+ if (value === current)
158
+ return current;
159
+ const t = typeof value, multi = marker !== undefined;
160
+ if (t === "string" || t === "number") {
161
+ if (t === "number")
162
+ value = value.toString();
163
+ if (multi) {
164
+ let node = current[0];
165
+ if (node && isTextNode(node)) {
166
+ replaceText(node, value);
167
+ } else
168
+ node = createTextNode(value);
169
+ current = cleanChildren(parent, current, marker, node);
170
+ } else {
171
+ if (current !== "" && typeof current === "string") {
172
+ replaceText(getFirstChild(parent), current = value);
173
+ } else {
174
+ cleanChildren(parent, current, marker, createTextNode(value));
175
+ current = value;
176
+ }
177
+ }
178
+ } else if (value == null || t === "boolean") {
179
+ current = cleanChildren(parent, current, marker);
180
+ } else if (t === "function") {
181
+ createRenderEffect(() => {
182
+ let v = value();
183
+ while (typeof v === "function")
184
+ v = v();
185
+ current = insertExpression(parent, v, current, marker);
186
+ });
187
+ return () => current;
188
+ } else if (Array.isArray(value)) {
189
+ const array = [];
190
+ if (normalizeIncomingArray(array, value, unwrapArray)) {
191
+ createRenderEffect(() => current = insertExpression(parent, array, current, marker, true));
192
+ return () => current;
193
+ }
194
+ if (array.length === 0) {
195
+ const replacement = cleanChildren(parent, current, marker);
196
+ if (multi)
197
+ return current = replacement;
198
+ } else {
199
+ if (Array.isArray(current)) {
200
+ if (current.length === 0) {
201
+ appendNodes(parent, array, marker);
202
+ } else
203
+ reconcileArrays(parent, current, array);
204
+ } else if (current == null || current === "") {
205
+ appendNodes(parent, array);
206
+ } else {
207
+ reconcileArrays(parent, multi && current || [getFirstChild(parent)], array);
208
+ }
209
+ }
210
+ current = array;
211
+ } else {
212
+ if (Array.isArray(current)) {
213
+ if (multi)
214
+ return current = cleanChildren(parent, current, marker, value);
215
+ cleanChildren(parent, current, null, value);
216
+ } else if (current == null || current === "" || !getFirstChild(parent)) {
217
+ insertNode(parent, value);
218
+ } else
219
+ replaceNode(parent, value, getFirstChild(parent));
220
+ current = value;
221
+ }
222
+ return current;
148
223
  }
149
- };
150
-
151
- // src/elements/text-node.ts
152
- var GHOST_NODE_TAG = "text-ghost";
153
- var ChunkToTextNodeMap = new WeakMap;
154
- var isTextChunk = (node) => {
155
- return typeof node === "object" && "__isChunk" in node;
156
- };
157
-
158
- class TextNode {
159
- id;
160
- chunk;
161
- parent;
162
- textParent;
163
- constructor(chunk) {
164
- this.id = getNextId("text-node");
165
- this.chunk = chunk;
166
- ChunkToTextNodeMap.set(chunk, this);
167
- }
168
- replaceText(newChunk) {
169
- const textParent = this.textParent;
170
- if (!textParent) {
171
- log("No parent found for text node:", this.id);
172
- return;
224
+ function normalizeIncomingArray(normalized, array, unwrap) {
225
+ let dynamic = false;
226
+ for (let i = 0, len = array.length;i < len; i++) {
227
+ let item = array[i], t;
228
+ if (item == null || item === true || item === false)
229
+ ;
230
+ else if (Array.isArray(item)) {
231
+ dynamic = normalizeIncomingArray(normalized, item) || dynamic;
232
+ } else if ((t = typeof item) === "string" || t === "number") {
233
+ normalized.push(createTextNode(item));
234
+ } else if (t === "function") {
235
+ if (unwrap) {
236
+ while (typeof item === "function")
237
+ item = item();
238
+ dynamic = normalizeIncomingArray(normalized, Array.isArray(item) ? item : [item]) || dynamic;
239
+ } else {
240
+ normalized.push(item);
241
+ dynamic = true;
242
+ }
243
+ } else
244
+ normalized.push(item);
173
245
  }
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);
246
+ return dynamic;
180
247
  }
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);
248
+ function reconcileArrays(parentNode, a, b) {
249
+ let bLength = b.length, aEnd = a.length, bEnd = bLength, aStart = 0, bStart = 0, after = getNextSibling(a[aEnd - 1]), map = null;
250
+ while (aStart < aEnd || bStart < bEnd) {
251
+ if (a[aStart] === b[bStart]) {
252
+ aStart++;
253
+ bStart++;
254
+ continue;
200
255
  }
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);
256
+ while (a[aEnd - 1] === b[bEnd - 1]) {
257
+ aEnd--;
258
+ bEnd--;
207
259
  }
208
- } else {
209
- const firstChunk = textParent.content.chunks[0];
210
- if (firstChunk && !ChunkToTextNodeMap.has(firstChunk)) {
211
- styledText = styledText.replace(this.chunk, firstChunk);
260
+ if (aEnd === aStart) {
261
+ const node = bEnd < bLength ? bStart ? getNextSibling(b[bStart - 1]) : b[bEnd - bStart] : after;
262
+ while (bStart < bEnd)
263
+ insertNode(parentNode, b[bStart++], node);
264
+ } else if (bEnd === bStart) {
265
+ while (aStart < aEnd) {
266
+ if (!map || !map.has(a[aStart]))
267
+ removeNode(parentNode, a[aStart]);
268
+ aStart++;
269
+ }
270
+ } else if (a[aStart] === b[bEnd - 1] && b[bStart] === a[aEnd - 1]) {
271
+ const node = getNextSibling(a[--aEnd]);
272
+ insertNode(parentNode, b[bStart++], getNextSibling(a[aStart++]));
273
+ insertNode(parentNode, b[--bEnd], node);
274
+ a[aEnd] = b[bEnd];
212
275
  } else {
213
- styledText = styledText.insert(this.chunk);
276
+ if (!map) {
277
+ map = new Map;
278
+ let i = bStart;
279
+ while (i < bEnd)
280
+ map.set(b[i], i++);
281
+ }
282
+ const index = map.get(a[aStart]);
283
+ if (index != null) {
284
+ if (bStart < index && index < bEnd) {
285
+ let i = aStart, sequence = 1, t;
286
+ while (++i < aEnd && i < bEnd) {
287
+ if ((t = map.get(a[i])) == null || t !== index + sequence)
288
+ break;
289
+ sequence++;
290
+ }
291
+ if (sequence > index - bStart) {
292
+ const node = a[aStart];
293
+ while (bStart < index)
294
+ insertNode(parentNode, b[bStart++], node);
295
+ } else
296
+ replaceNode(parentNode, b[bStart++], a[aStart++]);
297
+ } else
298
+ aStart++;
299
+ } else
300
+ removeNode(parentNode, a[aStart++]);
214
301
  }
215
302
  }
216
- textParent.content = styledText;
217
- textParent.visible = textParent.textLength !== 0;
218
- this.parent = parent;
219
303
  }
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
- });
304
+ function cleanChildren(parent, current, marker, replacement) {
305
+ if (marker === undefined) {
306
+ let removed;
307
+ while (removed = getFirstChild(parent))
308
+ removeNode(parent, removed);
309
+ replacement && insertNode(parent, replacement);
310
+ return replacement;
311
+ }
312
+ const node = replacement || createSlotNode();
313
+ if (current.length) {
314
+ let inserted = false;
315
+ for (let i = current.length - 1;i >= 0; i--) {
316
+ const el = current[i];
317
+ if (node !== el) {
318
+ const isParent = getParentNode(el) === parent;
319
+ if (!inserted && !i)
320
+ isParent ? replaceNode(parent, node, el) : insertNode(parent, node, marker);
321
+ else
322
+ isParent && removeNode(parent, el);
323
+ } else
324
+ inserted = true;
245
325
  }
246
- }
326
+ } else
327
+ insertNode(parent, node, marker);
328
+ return [node];
247
329
  }
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;
330
+ function appendNodes(parent, array, marker) {
331
+ for (let i = 0, len = array.length;i < len; i++)
332
+ insertNode(parent, array[i], marker);
333
+ }
334
+ function replaceNode(parent, newNode, oldNode) {
335
+ insertNode(parent, newNode, oldNode);
336
+ removeNode(parent, oldNode);
337
+ }
338
+ function spreadExpression(node, props, prevProps = {}, skipChildren) {
339
+ props || (props = {});
340
+ if (!skipChildren) {
341
+ createRenderEffect(() => prevProps.children = insertExpression(node, props.children, prevProps.children));
342
+ }
343
+ createRenderEffect(() => props.ref && props.ref(node));
344
+ createRenderEffect(() => {
345
+ for (const prop in props) {
346
+ if (prop === "children" || prop === "ref")
347
+ continue;
348
+ const value = props[prop];
349
+ if (value === prevProps[prop])
350
+ continue;
351
+ setProperty(node, prop, value, prevProps[prop]);
352
+ prevProps[prop] = value;
258
353
  }
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
354
  });
267
- insertNode(parent, ghostNode, anchor);
268
- return ghostNode;
355
+ return prevProps;
269
356
  }
357
+ return {
358
+ render(code, element) {
359
+ let disposer;
360
+ createRoot((dispose) => {
361
+ disposer = dispose;
362
+ insert(element, code());
363
+ });
364
+ return disposer;
365
+ },
366
+ insert,
367
+ spread(node, accessor, skipChildren) {
368
+ if (typeof accessor === "function") {
369
+ createRenderEffect((current) => spreadExpression(node, accessor(), current, skipChildren));
370
+ } else
371
+ spreadExpression(node, accessor, undefined, skipChildren);
372
+ },
373
+ createElement,
374
+ createTextNode,
375
+ insertNode,
376
+ setProp(node, name, value, prev) {
377
+ setProperty(node, name, value, prev);
378
+ return value;
379
+ },
380
+ mergeProps,
381
+ effect: createRenderEffect,
382
+ memo,
383
+ createComponent,
384
+ use(fn, element, arg) {
385
+ return untrack(() => fn(element, arg));
386
+ }
387
+ };
270
388
  }
271
389
 
272
- class GhostTextRenderable extends TextRenderable2 {
273
- constructor(ctx, options) {
274
- super(ctx, options);
275
- }
276
- static isGhostNode(node) {
277
- return node instanceof GhostTextRenderable;
390
+ // src/renderer/index.ts
391
+ import { mergeProps as mergeProps2 } from "solid-js";
392
+ function createRenderer2(options) {
393
+ const renderer = createRenderer(options);
394
+ renderer.mergeProps = mergeProps2;
395
+ return renderer;
396
+ }
397
+
398
+ // src/utils/id-counter.ts
399
+ var idCounter = new Map;
400
+ function getNextId(elementType) {
401
+ if (!idCounter.has(elementType)) {
402
+ idCounter.set(elementType, 0);
278
403
  }
404
+ const value = idCounter.get(elementType) + 1;
405
+ idCounter.set(elementType, value);
406
+ return `${elementType}-${value}`;
279
407
  }
280
408
 
409
+ // src/utils/log.ts
410
+ var log = (...args) => {
411
+ if (process.env.DEBUG) {
412
+ console.log("[Reconciler]", ...args);
413
+ }
414
+ };
415
+
281
416
  // src/reconciler.ts
417
+ class TextNode extends TextNodeRenderable {
418
+ static fromString(text, options = {}) {
419
+ const node = new TextNode(options);
420
+ node.add(text);
421
+ return node;
422
+ }
423
+ }
282
424
  var logId = (node) => {
283
425
  if (!node)
284
426
  return;
285
- if (isTextChunk(node)) {
286
- return node.text;
287
- }
288
427
  return node.id;
289
428
  };
429
+ var getNodeChildren = (node) => {
430
+ let children;
431
+ if (node instanceof TextRenderable) {
432
+ children = node.getTextChildren();
433
+ } else {
434
+ children = node.getChildren();
435
+ }
436
+ return children;
437
+ };
290
438
  function _insertNode(parent, node, anchor) {
291
439
  log("Inserting node:", logId(node), "into parent:", logId(parent), "with anchor:", logId(anchor), node instanceof TextNode);
292
- if (node instanceof StyledText) {
293
- log("Inserting styled text:", node.toString());
294
- for (const chunk of node.chunks) {
295
- _insertNode(parent, _createTextNode(chunk), anchor);
296
- return;
297
- }
440
+ if (node instanceof SlotRenderable) {
441
+ node.parent = parent;
442
+ node = node.getSlotChild(parent);
298
443
  }
299
- if (isTextChunk(node)) {
300
- _insertNode(parent, _createTextNode(node), anchor);
301
- return;
444
+ if (anchor && anchor instanceof SlotRenderable) {
445
+ anchor = anchor.getSlotChild(parent);
302
446
  }
303
- if (node instanceof TextNode) {
304
- return node.insert(parent, anchor);
447
+ if (isTextNodeRenderable(node)) {
448
+ if (!(parent instanceof TextRenderable) && !isTextNodeRenderable(parent)) {
449
+ throw new Error(`Orphan text error: "${node.toChunks().map((c) => c.text).join("")}" must have a <text> as a parent: ${parent.id} above ${node.id}`);
450
+ }
305
451
  }
306
- if (!(parent instanceof Renderable2)) {
307
- return;
452
+ if (!(parent instanceof BaseRenderable)) {
453
+ console.error("[INSERT]", "Tried to mount a non base renderable");
454
+ throw new Error("Tried to mount a non base renderable");
308
455
  }
309
- if (anchor) {
310
- if (isTextChunk(anchor)) {
311
- console.warn("Cannot add non text node with text chunk anchor");
312
- return;
313
- }
314
- const anchorIndex = parent.getChildren().findIndex((el) => {
315
- if (anchor instanceof TextNode) {
316
- return el.id === anchor.textParent?.id;
317
- }
318
- return el.id === anchor.id;
319
- });
320
- parent.add(node, anchorIndex);
321
- } else {
456
+ if (!anchor) {
322
457
  parent.add(node);
458
+ return;
459
+ }
460
+ const children = getNodeChildren(parent);
461
+ const anchorIndex = children.findIndex((el) => el.id === anchor.id);
462
+ if (anchorIndex === -1) {
463
+ log("[INSERT]", "Could not find anchor", logId(parent), logId(anchor), "[children]", ...children.map((c) => c.id));
323
464
  }
465
+ parent.add(node, anchorIndex);
324
466
  }
325
467
  function _removeNode(parent, node) {
326
468
  log("Removing node:", logId(node), "from parent:", logId(parent));
327
- if (isTextChunk(node)) {
328
- const textNode = TextNode.getTextNodeFromChunk(node);
329
- if (textNode) {
330
- _removeNode(parent, textNode);
331
- }
332
- } else if (node instanceof StyledText) {
333
- for (const chunk of node.chunks) {
334
- const textNode = TextNode.getTextNodeFromChunk(chunk);
335
- if (!textNode)
336
- continue;
337
- _removeNode(parent, textNode);
338
- }
469
+ if (node instanceof SlotRenderable) {
470
+ node.parent = null;
471
+ node = node.getSlotChild(parent);
339
472
  }
340
- if (node instanceof TextNode) {
341
- return node.remove(parent);
342
- }
343
- if (parent instanceof Renderable2 && node instanceof Renderable2) {
473
+ if (isTextNodeRenderable(parent)) {
474
+ if (typeof node !== "string" && !isTextNodeRenderable(node)) {
475
+ console.warn("Node not a valid child of TextNode");
476
+ } else {
477
+ parent.remove(node);
478
+ }
479
+ } else {
344
480
  parent.remove(node.id);
345
- process.nextTick(() => {
346
- if (!node.parent) {
347
- node.destroyRecursively();
348
- }
349
- });
350
481
  }
482
+ process.nextTick(() => {
483
+ if (node instanceof BaseRenderable && !node.parent) {
484
+ node.destroyRecursively();
485
+ return;
486
+ }
487
+ });
351
488
  }
352
489
  function _createTextNode(value) {
353
490
  log("Creating text node:", value);
354
- const chunk = value && isTextChunk(value) ? value : {
355
- __isChunk: true,
356
- text: `${value}`
357
- };
358
- const textNode = new TextNode(chunk);
359
- return textNode;
491
+ const id = getNextId("text-node");
492
+ if (typeof value === "number") {
493
+ value = value.toString();
494
+ }
495
+ return TextNode.fromString(value, { id });
496
+ }
497
+ function createSlotNode() {
498
+ const id = getNextId("slot-node");
499
+ log("Creating slot node", id);
500
+ return new SlotRenderable(id);
501
+ }
502
+ function _getParentNode(childNode) {
503
+ log("Getting parent of node:", logId(childNode));
504
+ let parent = childNode.parent ?? undefined;
505
+ if (parent instanceof RootTextNodeRenderable) {
506
+ parent = parent.textParent ?? undefined;
507
+ }
508
+ return parent;
360
509
  }
361
510
  var {
362
511
  render: _render,
363
512
  effect,
364
- memo,
365
- createComponent,
513
+ memo: memo2,
514
+ createComponent: createComponent2,
366
515
  createElement,
367
516
  createTextNode,
368
517
  insertNode,
369
518
  insert,
370
519
  spread,
371
520
  setProp,
372
- mergeProps,
521
+ mergeProps: mergeProps3,
373
522
  use
374
- } = createRenderer({
523
+ } = createRenderer2({
375
524
  createElement(tagName) {
376
525
  log("Creating element:", tagName);
377
526
  const id = getNextId(tagName);
@@ -388,25 +537,14 @@ var {
388
537
  return element;
389
538
  },
390
539
  createTextNode: _createTextNode,
540
+ createSlotNode,
391
541
  replaceText(textNode, value) {
392
542
  log("Replacing text:", value, "in node:", logId(textNode));
393
- if (textNode instanceof Renderable2)
394
- return;
395
- if (isTextChunk(textNode)) {
396
- console.warn("Cannot replace text on text chunk", logId(textNode));
543
+ if (!(textNode instanceof TextNode))
397
544
  return;
398
- }
399
- const newChunk = {
400
- __isChunk: true,
401
- text: value
402
- };
403
- textNode.replaceText(newChunk);
545
+ textNode.replace(value, 0);
404
546
  },
405
547
  setProperty(node, name, value, prev) {
406
- if (node instanceof TextNode || isTextChunk(node)) {
407
- console.warn("Cannot set property on text node:", logId(node));
408
- return;
409
- }
410
548
  if (name.startsWith("on:")) {
411
549
  const eventName = name.slice(3);
412
550
  if (value) {
@@ -417,8 +555,23 @@ var {
417
555
  }
418
556
  return;
419
557
  }
558
+ if (isTextNodeRenderable(node)) {
559
+ if (name !== "style") {
560
+ return;
561
+ }
562
+ node.attributes |= createTextAttributes(value);
563
+ node.fg = value.fg ? parseColor(value.fg) : node.fg;
564
+ node.bg = value.bg ? parseColor(value.bg) : node.bg;
565
+ return;
566
+ }
420
567
  switch (name) {
568
+ case "id":
569
+ log("Id mapped", node.id, "=", value);
570
+ node[name] = value;
571
+ break;
421
572
  case "focused":
573
+ if (!(node instanceof Renderable))
574
+ return;
422
575
  if (value) {
423
576
  node.focus();
424
577
  } else {
@@ -427,11 +580,11 @@ var {
427
580
  break;
428
581
  case "onChange":
429
582
  let event = undefined;
430
- if (node instanceof SelectRenderable2) {
583
+ if (node instanceof SelectRenderable) {
431
584
  event = SelectRenderableEvents.SELECTION_CHANGED;
432
- } else if (node instanceof TabSelectRenderable2) {
585
+ } else if (node instanceof TabSelectRenderable) {
433
586
  event = TabSelectRenderableEvents.SELECTION_CHANGED;
434
- } else if (node instanceof InputRenderable2) {
587
+ } else if (node instanceof InputRenderable) {
435
588
  event = InputRenderableEvents.CHANGE;
436
589
  }
437
590
  if (!event)
@@ -444,7 +597,7 @@ var {
444
597
  }
445
598
  break;
446
599
  case "onInput":
447
- if (node instanceof InputRenderable2) {
600
+ if (node instanceof InputRenderable) {
448
601
  if (value) {
449
602
  node.on(InputRenderableEvents.INPUT, value);
450
603
  }
@@ -454,7 +607,7 @@ var {
454
607
  }
455
608
  break;
456
609
  case "onSubmit":
457
- if (node instanceof InputRenderable2) {
610
+ if (node instanceof InputRenderable) {
458
611
  if (value) {
459
612
  node.on(InputRenderableEvents.ENTER, value);
460
613
  }
@@ -464,14 +617,14 @@ var {
464
617
  }
465
618
  break;
466
619
  case "onSelect":
467
- if (node instanceof SelectRenderable2) {
620
+ if (node instanceof SelectRenderable) {
468
621
  if (value) {
469
622
  node.on(SelectRenderableEvents.ITEM_SELECTED, value);
470
623
  }
471
624
  if (prev) {
472
625
  node.off(SelectRenderableEvents.ITEM_SELECTED, prev);
473
626
  }
474
- } else if (node instanceof TabSelectRenderable2) {
627
+ } else if (node instanceof TabSelectRenderable) {
475
628
  if (value) {
476
629
  node.on(TabSelectRenderableEvents.ITEM_SELECTED, value);
477
630
  }
@@ -501,37 +654,10 @@ var {
501
654
  },
502
655
  insertNode: _insertNode,
503
656
  removeNode: _removeNode,
504
- getParentNode(childNode) {
505
- log("Getting parent of node:", logId(childNode));
506
- let node = childNode;
507
- if (isTextChunk(childNode)) {
508
- const parentTextNode = TextNode.getTextNodeFromChunk(childNode);
509
- if (!parentTextNode)
510
- return;
511
- node = parentTextNode;
512
- }
513
- const parent = node.parent;
514
- if (!parent) {
515
- log("No parent found for node:", logId(node));
516
- return;
517
- }
518
- log("Parent found:", logId(parent), "for node:", logId(node));
519
- return parent;
520
- },
657
+ getParentNode: _getParentNode,
521
658
  getFirstChild(node) {
522
659
  log("Getting first child of node:", logId(node));
523
- if (node instanceof TextRenderable3) {
524
- const chunk = node.content.chunks[0];
525
- if (chunk) {
526
- return TextNode.getTextNodeFromChunk(chunk);
527
- } else {
528
- return;
529
- }
530
- }
531
- if (node instanceof TextNode || isTextChunk(node)) {
532
- return;
533
- }
534
- const firstChild = node.getChildren()[0];
660
+ const firstChild = getNodeChildren(node)[0];
535
661
  if (!firstChild) {
536
662
  log("No first child found for node:", logId(node));
537
663
  return;
@@ -541,34 +667,12 @@ var {
541
667
  },
542
668
  getNextSibling(node) {
543
669
  log("Getting next sibling of node:", logId(node));
544
- if (isTextChunk(node)) {
545
- console.warn("Cannot get next sibling of text chunk");
546
- return;
547
- }
548
- const parent = node.parent;
670
+ const parent = _getParentNode(node);
549
671
  if (!parent) {
550
672
  log("No parent found for node:", logId(node));
551
673
  return;
552
674
  }
553
- if (node instanceof TextNode) {
554
- if (parent instanceof TextRenderable3) {
555
- const siblings2 = parent.content.chunks;
556
- const index2 = siblings2.indexOf(node.chunk);
557
- if (index2 === -1 || index2 === siblings2.length - 1) {
558
- log("No next sibling found for node:", logId(node));
559
- return;
560
- }
561
- const nextSibling2 = siblings2[index2 + 1];
562
- if (!nextSibling2) {
563
- log("Next sibling is null for node:", logId(node));
564
- return;
565
- }
566
- return TextNode.getTextNodeFromChunk(nextSibling2);
567
- }
568
- console.warn("Text parent is not a text node:", logId(node));
569
- return;
570
- }
571
- const siblings = parent.getChildren();
675
+ const siblings = getNodeChildren(parent);
572
676
  const index = siblings.indexOf(node);
573
677
  if (index === -1 || index === siblings.length - 1) {
574
678
  log("No next sibling found for node:", logId(node));
@@ -584,43 +688,293 @@ var {
584
688
  }
585
689
  });
586
690
 
691
+ // src/elements/extras.ts
692
+ function Portal(props) {
693
+ const renderer = useRenderer();
694
+ const marker = createSlotNode(), mount = () => props.mount || renderer.root, owner = getOwner();
695
+ let content;
696
+ createEffect(() => {
697
+ content || (content = runWithOwner(owner, () => createMemo2(() => props.children)));
698
+ const el = mount();
699
+ const container = createElement("box"), renderRoot = container;
700
+ Object.defineProperty(container, "_$host", {
701
+ get() {
702
+ return marker.parent;
703
+ },
704
+ configurable: true
705
+ });
706
+ insert(renderRoot, content);
707
+ el.add(container);
708
+ props.ref && props.ref(container);
709
+ onCleanup2(() => el.remove(container.id));
710
+ }, undefined, { render: true });
711
+ return marker;
712
+ }
713
+ function createDynamic(component, props) {
714
+ const cached = createMemo2(component);
715
+ return createMemo2(() => {
716
+ const component2 = cached();
717
+ switch (typeof component2) {
718
+ case "function":
719
+ return untrack2(() => component2(props));
720
+ case "string":
721
+ const el = createElement(component2);
722
+ spread(el, props);
723
+ return el;
724
+ default:
725
+ break;
726
+ }
727
+ });
728
+ }
729
+ function Dynamic(props) {
730
+ const [, others] = splitProps(props, ["component"]);
731
+ return createDynamic(() => props.component, others);
732
+ }
733
+ // src/elements/slot.ts
734
+ import { BaseRenderable as BaseRenderable2, isTextNodeRenderable as isTextNodeRenderable2, TextNodeRenderable as TextNodeRenderable2, TextRenderable as TextRenderable2, Yoga } from "@opentui/core";
735
+
736
+ class SlotBaseRenderable extends BaseRenderable2 {
737
+ constructor(id) {
738
+ super({
739
+ id
740
+ });
741
+ }
742
+ add(obj, index) {
743
+ throw new Error("Can't add children on an Slot renderable");
744
+ }
745
+ getChildren() {
746
+ return [];
747
+ }
748
+ remove(id) {}
749
+ insertBefore(obj, anchor) {
750
+ throw new Error("Can't add children on an Slot renderable");
751
+ }
752
+ getRenderable(id) {
753
+ return;
754
+ }
755
+ getChildrenCount() {
756
+ return 0;
757
+ }
758
+ requestRender() {}
759
+ }
760
+
761
+ class TextSlotRenderable extends TextNodeRenderable2 {
762
+ slotParent;
763
+ destroyed = false;
764
+ constructor(id, parent) {
765
+ super({ id });
766
+ this._visible = false;
767
+ this.slotParent = parent;
768
+ }
769
+ destroy() {
770
+ if (this.destroyed) {
771
+ return;
772
+ }
773
+ this.destroyed = true;
774
+ this.slotParent?.destroy();
775
+ super.destroy();
776
+ }
777
+ }
778
+
779
+ class LayoutSlotRenderable extends SlotBaseRenderable {
780
+ yogaNode;
781
+ slotParent;
782
+ destroyed = false;
783
+ constructor(id, parent) {
784
+ super(id);
785
+ this._visible = false;
786
+ this.slotParent = parent;
787
+ this.yogaNode = Yoga.default.Node.create();
788
+ this.yogaNode.setDisplay(Yoga.Display.None);
789
+ }
790
+ getLayoutNode() {
791
+ return this.yogaNode;
792
+ }
793
+ updateFromLayout() {}
794
+ updateLayout() {}
795
+ onRemove() {}
796
+ destroy() {
797
+ if (this.destroyed) {
798
+ return;
799
+ }
800
+ this.destroyed = true;
801
+ super.destroy();
802
+ this.slotParent?.destroy();
803
+ }
804
+ }
805
+
806
+ class SlotRenderable extends SlotBaseRenderable {
807
+ layoutNode;
808
+ textNode;
809
+ destroyed = false;
810
+ constructor(id) {
811
+ super(id);
812
+ this._visible = false;
813
+ }
814
+ getSlotChild(parent) {
815
+ if (isTextNodeRenderable2(parent) || parent instanceof TextRenderable2) {
816
+ if (!this.textNode) {
817
+ this.textNode = new TextSlotRenderable(`slot-text-${this.id}`, this);
818
+ }
819
+ return this.textNode;
820
+ }
821
+ if (!this.layoutNode) {
822
+ this.layoutNode = new LayoutSlotRenderable(`slot-layout-${this.id}`, this);
823
+ }
824
+ return this.layoutNode;
825
+ }
826
+ destroy() {
827
+ if (this.destroyed) {
828
+ return;
829
+ }
830
+ this.destroyed = true;
831
+ if (this.layoutNode) {
832
+ this.layoutNode.destroy();
833
+ }
834
+ if (this.textNode) {
835
+ this.textNode.destroy();
836
+ }
837
+ }
838
+ }
839
+
840
+ // src/elements/index.ts
841
+ class SpanRenderable extends TextNodeRenderable3 {
842
+ _ctx;
843
+ constructor(_ctx, options) {
844
+ super(options);
845
+ this._ctx = _ctx;
846
+ }
847
+ }
848
+ var textNodeKeys = ["span", "b", "strong", "i", "em", "u"];
849
+
850
+ class TextModifierRenderable extends SpanRenderable {
851
+ constructor(options, modifier) {
852
+ super(null, options);
853
+ if (modifier === "b" || modifier === "strong") {
854
+ this.attributes = (this.attributes || 0) | TextAttributes.BOLD;
855
+ } else if (modifier === "i" || modifier === "em") {
856
+ this.attributes = (this.attributes || 0) | TextAttributes.ITALIC;
857
+ } else if (modifier === "u") {
858
+ this.attributes = (this.attributes || 0) | TextAttributes.UNDERLINE;
859
+ }
860
+ }
861
+ }
862
+
863
+ class BoldSpanRenderable extends TextModifierRenderable {
864
+ constructor(options) {
865
+ super(options, "b");
866
+ }
867
+ }
868
+
869
+ class ItalicSpanRenderable extends TextModifierRenderable {
870
+ constructor(options) {
871
+ super(options, "i");
872
+ }
873
+ }
874
+
875
+ class UnderlineSpanRenderable extends TextModifierRenderable {
876
+ constructor(options) {
877
+ super(options, "u");
878
+ }
879
+ }
880
+
881
+ class LineBreakRenderable extends SpanRenderable {
882
+ constructor(_ctx, options) {
883
+ super(null, options);
884
+ this.add();
885
+ }
886
+ add() {
887
+ return super.add(`
888
+ `);
889
+ }
890
+ }
891
+ var baseComponents = {
892
+ box: BoxRenderable,
893
+ text: TextRenderable3,
894
+ input: InputRenderable2,
895
+ select: SelectRenderable2,
896
+ ascii_font: ASCIIFontRenderable,
897
+ tab_select: TabSelectRenderable2,
898
+ scrollbox: ScrollBoxRenderable,
899
+ span: SpanRenderable,
900
+ strong: BoldSpanRenderable,
901
+ b: BoldSpanRenderable,
902
+ em: ItalicSpanRenderable,
903
+ i: ItalicSpanRenderable,
904
+ u: UnderlineSpanRenderable,
905
+ br: LineBreakRenderable
906
+ };
907
+ var componentCatalogue = { ...baseComponents };
908
+ function extend(objects) {
909
+ Object.assign(componentCatalogue, objects);
910
+ }
911
+ function getComponentCatalogue() {
912
+ return componentCatalogue;
913
+ }
914
+
587
915
  // index.ts
588
916
  var render = async (node, renderConfig = {}) => {
589
917
  const renderer = await createCliRenderer(renderConfig);
590
- _render(() => createComponent(RendererContext.Provider, {
918
+ _render(() => createComponent2(RendererContext.Provider, {
591
919
  get value() {
592
920
  return renderer;
593
921
  },
594
922
  get children() {
595
- return createComponent(node, {});
923
+ return createComponent2(node, {});
596
924
  }
597
925
  }), renderer.root);
598
926
  };
927
+ var testRender = async (node, renderConfig = {}) => {
928
+ const testSetup = await createTestRenderer(renderConfig);
929
+ _render(() => createComponent2(RendererContext.Provider, {
930
+ get value() {
931
+ return testSetup.renderer;
932
+ },
933
+ get children() {
934
+ return createComponent2(node, {});
935
+ }
936
+ }), testSetup.renderer.root);
937
+ return testSetup;
938
+ };
599
939
  export {
600
940
  useTimeline,
601
941
  useTerminalDimensions,
602
942
  useSelectionHandler,
603
943
  useRenderer,
944
+ usePaste,
604
945
  useKeyboard,
605
946
  useKeyHandler,
606
947
  use,
948
+ textNodeKeys,
949
+ testRender,
607
950
  spread,
608
951
  setProp,
609
952
  render,
610
953
  onResize,
611
- mergeProps,
612
- memo,
954
+ mergeProps3 as mergeProps,
955
+ memo2 as memo,
613
956
  insertNode,
614
957
  insert,
615
958
  getComponentCatalogue,
616
959
  extend,
617
960
  effect,
618
961
  createTextNode,
962
+ createSlotNode,
619
963
  createElement,
964
+ createDynamic,
620
965
  createComponentTimeline,
621
- createComponent,
966
+ createComponent2 as createComponent,
622
967
  componentCatalogue,
623
968
  baseComponents,
624
969
  _render,
625
- RendererContext
970
+ UnderlineSpanRenderable,
971
+ TextSlotRenderable,
972
+ SlotRenderable,
973
+ RendererContext,
974
+ Portal,
975
+ LineBreakRenderable,
976
+ LayoutSlotRenderable,
977
+ ItalicSpanRenderable,
978
+ Dynamic,
979
+ BoldSpanRenderable
626
980
  };