@opentui/solid 0.1.23 → 0.1.24

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