@opentui/solid 0.1.13 → 0.1.14

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.d.ts CHANGED
@@ -3,4 +3,5 @@ import type { JSX } from "./jsx-runtime";
3
3
  export declare const render: (node: () => JSX.Element, renderConfig?: CliRendererConfig) => Promise<void>;
4
4
  export * from "./src/reconciler";
5
5
  export * from "./src/elements";
6
+ export * from "./src/types/elements";
6
7
  export { type JSX };
package/index.js CHANGED
@@ -37,7 +37,7 @@ var onResize = (callback) => {
37
37
  };
38
38
  var useTerminalDimensions = () => {
39
39
  const renderer = useRenderer();
40
- const [terminalDimensions, setTerminalDimensions] = createSignal({ width: renderer.terminalWidth, height: renderer.terminalHeight });
40
+ const [terminalDimensions, setTerminalDimensions] = createSignal({ width: renderer.width, height: renderer.height });
41
41
  const callback = (width, height) => {
42
42
  setTerminalDimensions({ width, height });
43
43
  };
@@ -93,14 +93,21 @@ var useTimeline = (timeline, initialValue, targetValue, options, startTime = 0)
93
93
  };
94
94
 
95
95
  // src/elements/index.ts
96
- var elements = {
97
- ascii_font: ASCIIFontRenderable,
96
+ var baseComponents = {
98
97
  box: BoxRenderable,
98
+ text: TextRenderable,
99
99
  input: InputRenderable,
100
100
  select: SelectRenderable,
101
- tab_select: TabSelectRenderable,
102
- text: TextRenderable
101
+ ascii_font: ASCIIFontRenderable,
102
+ tab_select: TabSelectRenderable
103
103
  };
104
+ var componentCatalogue = { ...baseComponents };
105
+ function extend(objects) {
106
+ Object.assign(componentCatalogue, objects);
107
+ }
108
+ function getComponentCatalogue() {
109
+ return componentCatalogue;
110
+ }
104
111
 
105
112
  // src/reconciler.ts
106
113
  import {
@@ -114,6 +121,7 @@ import {
114
121
  TabSelectRenderableEvents,
115
122
  TextRenderable as TextRenderable3
116
123
  } from "@opentui/core";
124
+ import { useContext as useContext2 } from "solid-js";
117
125
  import { createRenderer } from "solid-js/universal";
118
126
 
119
127
  // src/elements/text-node.ts
@@ -140,6 +148,9 @@ var log = (...args) => {
140
148
  // src/elements/text-node.ts
141
149
  var GHOST_NODE_TAG = "text-ghost";
142
150
  var ChunkToTextNodeMap = new WeakMap;
151
+ var isTextChunk = (node) => {
152
+ return typeof node === "object" && "__isChunk" in node;
153
+ };
143
154
 
144
155
  class TextNode {
145
156
  id;
@@ -193,6 +204,7 @@ class TextNode {
193
204
  }
194
205
  }
195
206
  textParent.content = styledText;
207
+ textParent.visible = styledText.toString() !== "";
196
208
  this.parent = parent;
197
209
  }
198
210
  remove(parent) {
@@ -251,9 +263,27 @@ class GhostTextRenderable extends TextRenderable2 {
251
263
  }
252
264
 
253
265
  // src/reconciler.ts
254
- import { useContext as useContext2 } from "solid-js";
266
+ var logId = (node) => {
267
+ if (!node)
268
+ return;
269
+ if (isTextChunk(node)) {
270
+ return node.plainText;
271
+ }
272
+ return node.id;
273
+ };
255
274
  function _insertNode(parent, node, anchor) {
256
- log("Inserting node:", node.id, "into parent:", parent.id, "with anchor:", anchor?.id);
275
+ log("Inserting node:", logId(node), "into parent:", logId(parent), "with anchor:", logId(anchor), node instanceof TextNode);
276
+ if (node instanceof StyledText) {
277
+ log("Inserting styled text:", node.toString());
278
+ for (const chunk of node.chunks) {
279
+ _insertNode(parent, _createTextNode(chunk), anchor);
280
+ return;
281
+ }
282
+ }
283
+ if (isTextChunk(node)) {
284
+ _insertNode(parent, _createTextNode(node), anchor);
285
+ return;
286
+ }
257
287
  if (node instanceof TextNode) {
258
288
  return node.insert(parent, anchor);
259
289
  }
@@ -261,6 +291,10 @@ function _insertNode(parent, node, anchor) {
261
291
  return;
262
292
  }
263
293
  if (anchor) {
294
+ if (isTextChunk(anchor)) {
295
+ console.warn("Cannot add non text node with text chunk anchor");
296
+ return;
297
+ }
264
298
  const anchorIndex = parent.getChildren().findIndex((el) => {
265
299
  if (anchor instanceof TextNode) {
266
300
  return el.id === anchor.textParent?.id;
@@ -273,7 +307,20 @@ function _insertNode(parent, node, anchor) {
273
307
  }
274
308
  }
275
309
  function _removeNode(parent, node) {
276
- log("Removing node:", node.id, "from parent:", parent.id);
310
+ log("Removing node:", logId(node), "from parent:", logId(parent));
311
+ if (isTextChunk(node)) {
312
+ const textNode = TextNode.getTextNodeFromChunk(node);
313
+ if (textNode) {
314
+ _removeNode(parent, textNode);
315
+ }
316
+ } else if (node instanceof StyledText) {
317
+ for (const chunk of node.chunks) {
318
+ const textNode = TextNode.getTextNodeFromChunk(chunk);
319
+ if (!textNode)
320
+ continue;
321
+ _removeNode(parent, textNode);
322
+ }
323
+ }
277
324
  if (node instanceof TextNode) {
278
325
  return node.remove(parent);
279
326
  }
@@ -282,6 +329,16 @@ function _removeNode(parent, node) {
282
329
  node.destroyRecursively();
283
330
  }
284
331
  }
332
+ function _createTextNode(value) {
333
+ log("Creating text node:", value);
334
+ const chunk = value && isTextChunk(value) ? value : {
335
+ __isChunk: true,
336
+ text: new TextEncoder().encode(`${value}`),
337
+ plainText: `${value}`
338
+ };
339
+ const textNode = new TextNode(chunk);
340
+ return textNode;
341
+ }
285
342
  var {
286
343
  render: _render,
287
344
  effect,
@@ -290,7 +347,7 @@ var {
290
347
  createElement,
291
348
  createTextNode,
292
349
  insertNode,
293
- insert: solidUniversalInsert,
350
+ insert,
294
351
  spread,
295
352
  setProp,
296
353
  mergeProps,
@@ -303,24 +360,23 @@ var {
303
360
  if (!solidRenderer) {
304
361
  throw new Error("No renderer found");
305
362
  }
363
+ const elements = getComponentCatalogue();
364
+ if (!elements[tagName]) {
365
+ throw new Error(`[Reconciler] Unknown component type: ${tagName}`);
366
+ }
306
367
  const element = new elements[tagName](solidRenderer, { id });
307
368
  log("Element created with id:", id);
308
369
  return element;
309
370
  },
310
- createTextNode(value) {
311
- log("Creating text node:", value);
312
- const chunk = typeof value === "object" && "__isChunk" in value ? value : {
313
- __isChunk: true,
314
- text: new TextEncoder().encode(`${value}`),
315
- plainText: `${value}`
316
- };
317
- const textNode = new TextNode(chunk);
318
- return textNode;
319
- },
371
+ createTextNode: _createTextNode,
320
372
  replaceText(textNode, value) {
321
- log("Replacing text:", value, "in node:", textNode.id);
373
+ log("Replacing text:", value, "in node:", logId(textNode));
322
374
  if (textNode instanceof Renderable2)
323
375
  return;
376
+ if (isTextChunk(textNode)) {
377
+ console.warn("Cannot replace text on text chunk", logId(textNode));
378
+ return;
379
+ }
324
380
  const newChunk = {
325
381
  __isChunk: true,
326
382
  text: new TextEncoder().encode(value),
@@ -329,8 +385,8 @@ var {
329
385
  textNode.replaceText(newChunk);
330
386
  },
331
387
  setProperty(node, name, value, prev) {
332
- if (node instanceof TextNode) {
333
- console.warn("Cannot set property on text node:", node.id);
388
+ if (node instanceof TextNode || isTextChunk(node)) {
389
+ console.warn("Cannot set property on text node:", logId(node));
334
390
  return;
335
391
  }
336
392
  if (name.startsWith("on:")) {
@@ -427,18 +483,25 @@ var {
427
483
  },
428
484
  insertNode: _insertNode,
429
485
  removeNode: _removeNode,
430
- getParentNode(node) {
431
- log("Getting parent of node:", node.id);
486
+ getParentNode(childNode) {
487
+ log("Getting parent of node:", logId(childNode));
488
+ let node = childNode;
489
+ if (isTextChunk(childNode)) {
490
+ const parentTextNode = TextNode.getTextNodeFromChunk(childNode);
491
+ if (!parentTextNode)
492
+ return;
493
+ node = parentTextNode;
494
+ }
432
495
  const parent = node.parent;
433
496
  if (!parent) {
434
- log("No parent found for node:", node.id);
497
+ log("No parent found for node:", logId(node));
435
498
  return;
436
499
  }
437
- log("Parent found:", parent.id, "for node:", node.id);
500
+ log("Parent found:", logId(parent), "for node:", logId(node));
438
501
  return parent;
439
502
  },
440
503
  getFirstChild(node) {
441
- log("Getting first child of node:", node.id);
504
+ log("Getting first child of node:", logId(node));
442
505
  if (node instanceof TextRenderable3) {
443
506
  const chunk = node.content.chunks[0];
444
507
  if (chunk) {
@@ -447,22 +510,26 @@ var {
447
510
  return;
448
511
  }
449
512
  }
450
- if (node instanceof TextNode) {
513
+ if (node instanceof TextNode || isTextChunk(node)) {
451
514
  return;
452
515
  }
453
516
  const firstChild = node.getChildren()[0];
454
517
  if (!firstChild) {
455
- log("No first child found for node:", node.id);
518
+ log("No first child found for node:", logId(node));
456
519
  return;
457
520
  }
458
- log("First child found:", firstChild.id, "for node:", node.id);
521
+ log("First child found:", logId(firstChild), "for node:", logId(node));
459
522
  return firstChild;
460
523
  },
461
524
  getNextSibling(node) {
462
- log("Getting next sibling of node:", node.id);
525
+ log("Getting next sibling of node:", logId(node));
526
+ if (isTextChunk(node)) {
527
+ console.warn("Cannot get next sibling of text chunk");
528
+ return;
529
+ }
463
530
  const parent = node.parent;
464
531
  if (!parent) {
465
- log("No parent found for node:", node.id);
532
+ log("No parent found for node:", logId(node));
466
533
  return;
467
534
  }
468
535
  if (node instanceof TextNode) {
@@ -470,73 +537,34 @@ var {
470
537
  const siblings2 = parent.content.chunks;
471
538
  const index2 = siblings2.indexOf(node.chunk);
472
539
  if (index2 === -1 || index2 === siblings2.length - 1) {
473
- log("No next sibling found for node:", node.id);
540
+ log("No next sibling found for node:", logId(node));
474
541
  return;
475
542
  }
476
543
  const nextSibling2 = siblings2[index2 + 1];
477
544
  if (!nextSibling2) {
478
- log("Next sibling is null for node:", node.id);
545
+ log("Next sibling is null for node:", logId(node));
479
546
  return;
480
547
  }
481
548
  return TextNode.getTextNodeFromChunk(nextSibling2);
482
549
  }
483
- console.warn("Text parent is not a text node:", node.id);
550
+ console.warn("Text parent is not a text node:", logId(node));
484
551
  return;
485
552
  }
486
553
  const siblings = parent.getChildren();
487
554
  const index = siblings.indexOf(node);
488
555
  if (index === -1 || index === siblings.length - 1) {
489
- log("No next sibling found for node:", node.id);
556
+ log("No next sibling found for node:", logId(node));
490
557
  return;
491
558
  }
492
559
  const nextSibling = siblings[index + 1];
493
560
  if (!nextSibling) {
494
- log("Next sibling is null for node:", node.id);
561
+ log("Next sibling is null for node:", logId(node));
495
562
  return;
496
563
  }
497
- log("Next sibling found:", nextSibling.id, "for node:", node.id);
564
+ log("Next sibling found:", logId(nextSibling), "for node:", logId(node));
498
565
  return nextSibling;
499
566
  }
500
567
  });
501
- var insertStyledText = (parent, value, current, marker) => {
502
- while (typeof current === "function")
503
- current = current();
504
- if (value === current)
505
- return current;
506
- if (current) {
507
- if (typeof current === "object" && "__isChunk" in current) {
508
- const node = TextNode.getTextNodeFromChunk(current);
509
- if (node) {
510
- _removeNode(parent, node);
511
- }
512
- } else if (current instanceof StyledText) {
513
- for (const chunk of current.chunks) {
514
- const chunkNode = TextNode.getTextNodeFromChunk(chunk);
515
- if (!chunkNode)
516
- continue;
517
- _removeNode(parent, chunkNode);
518
- }
519
- }
520
- }
521
- if (value instanceof StyledText) {
522
- log("Inserting styled text:", value.toString());
523
- for (const chunk of value.chunks) {
524
- insertNode(parent, createTextNode(chunk), marker);
525
- }
526
- return value;
527
- } else if (value && typeof value === "object" && "__isChunk" in value) {
528
- insertNode(parent, createTextNode(value), marker);
529
- return value;
530
- }
531
- return solidUniversalInsert(parent, value, marker, current);
532
- };
533
- var insert = (parent, accessor, marker, initial) => {
534
- if (marker !== undefined && !initial)
535
- initial = [];
536
- if (typeof accessor !== "function")
537
- return insertStyledText(parent, accessor, initial, marker);
538
- effect((current) => insertStyledText(parent, accessor(), current, marker), initial);
539
- };
540
568
 
541
569
  // index.ts
542
570
  var render = async (node, renderConfig = {}) => {
@@ -558,7 +586,6 @@ export {
558
586
  useKeyHandler,
559
587
  use,
560
588
  spread,
561
- solidUniversalInsert,
562
589
  setProp,
563
590
  render,
564
591
  onResize,
@@ -566,12 +593,15 @@ export {
566
593
  memo,
567
594
  insertNode,
568
595
  insert,
569
- elements,
596
+ getComponentCatalogue,
597
+ extend,
570
598
  effect,
571
599
  createTextNode,
572
600
  createElement,
573
601
  createComponentTimeline,
574
602
  createComponent,
603
+ componentCatalogue,
604
+ baseComponents,
575
605
  _render,
576
606
  RendererContext
577
607
  };
package/jsx-runtime.d.ts CHANGED
@@ -1,12 +1,14 @@
1
1
  import { Renderable } from "@opentui/core"
2
- import {
3
- ASCIIFontElementProps,
4
- BoxElementProps,
5
- InputElementProps,
6
- SelectElementProps,
7
- TabSelectElementProps,
8
- TextElementProps,
9
- } from "./src/elements/index"
2
+ import type {
3
+ AsciiFontProps,
4
+ BoxProps,
5
+ ExtendedIntrinsicElements,
6
+ InputProps,
7
+ OpenTUIComponents,
8
+ SelectProps,
9
+ TabSelectProps,
10
+ TextProps,
11
+ } from "./src/types/elements"
10
12
 
11
13
  declare namespace JSX {
12
14
  // Replace Node with Renderable
@@ -14,13 +16,13 @@ declare namespace JSX {
14
16
 
15
17
  interface ArrayElement extends Array<Element> {}
16
18
 
17
- interface IntrinsicElements {
18
- ascii_font: ASCIIFontElementProps
19
- box: BoxElementProps
20
- input: InputElementProps
21
- select: SelectElementProps
22
- tab_select: TabSelectElementProps
23
- text: TextElementProps
19
+ interface IntrinsicElements extends ExtendedIntrinsicElements<OpenTUIComponents> {
20
+ box: BoxProps
21
+ text: TextProps
22
+ input: InputProps
23
+ select: SelectProps
24
+ ascii_font: AsciiFontProps
25
+ tab_select: TabSelectProps
24
26
  }
25
27
 
26
28
  interface ElementChildrenAttribute {
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "main": "index.js",
5
5
  "types": "index.d.ts",
6
6
  "type": "module",
7
- "version": "0.1.13",
7
+ "version": "0.1.14",
8
8
  "description": "SolidJS renderer for OpenTUI",
9
9
  "license": "MIT",
10
10
  "repository": {
@@ -25,7 +25,7 @@
25
25
  "./jsx-dev-runtime": "./jsx-runtime.d.ts"
26
26
  },
27
27
  "dependencies": {
28
- "@opentui/core": "0.1.13",
28
+ "@opentui/core": "0.1.14",
29
29
  "babel-plugin-module-resolver": "5.0.2",
30
30
  "@babel/core": "7.28.0",
31
31
  "@babel/preset-typescript": "7.27.1",
@@ -1,52 +1,28 @@
1
- import type { ASCIIFontOptions, BoxOptions, InputRenderableOptions, Renderable, RenderableOptions, SelectOption, SelectRenderableOptions, StyledText, TabSelectOption, TabSelectRenderableOptions, TextChunk, TextOptions } from "@opentui/core";
2
1
  import { ASCIIFontRenderable, BoxRenderable, InputRenderable, SelectRenderable, TabSelectRenderable, TextRenderable } from "@opentui/core";
3
- import type { JSX, Ref } from "solid-js";
2
+ import type { RenderableConstructor } from "../types/elements";
4
3
  export * from "./hooks";
5
- export declare const elements: {
6
- ascii_font: typeof ASCIIFontRenderable;
4
+ export declare const baseComponents: {
7
5
  box: typeof BoxRenderable;
6
+ text: typeof TextRenderable;
8
7
  input: typeof InputRenderable;
9
8
  select: typeof SelectRenderable;
9
+ ascii_font: typeof ASCIIFontRenderable;
10
10
  tab_select: typeof TabSelectRenderable;
11
- text: typeof TextRenderable;
12
- };
13
- export type Element = keyof typeof elements;
14
- type RenderableNonStyleKeys = "buffered";
15
- type ElementProps<T extends RenderableOptions<K>, K extends Renderable = Renderable, NonStyleKeys extends keyof T = RenderableNonStyleKeys> = {
16
- style?: Omit<T, NonStyleKeys | RenderableNonStyleKeys>;
17
- ref?: Ref<K>;
18
- } & T;
19
- type ContainerProps = {
20
- children?: JSX.Element;
21
- };
22
- export type BoxElementProps = ElementProps<BoxOptions, BoxRenderable, "title"> & ContainerProps;
23
- export type BoxStyle = BoxElementProps["style"];
24
- export type InputElementProps = ElementProps<InputRenderableOptions, InputRenderable, "value" | "maxLength" | "placeholder"> & {
25
- onInput?: (value: string) => void;
26
- onSubmit?: (value: string) => void;
27
- onChange?: (value: string) => void;
28
- focused?: boolean;
29
- };
30
- export type InputStyle = InputElementProps["style"];
31
- type TabSelectEventCallback = (index: number, option: TabSelectOption) => void;
32
- export type TabSelectElementProps = ElementProps<TabSelectRenderableOptions, TabSelectRenderable, "options" | "showScrollArrows" | "showDescription" | "wrapSelection"> & {
33
- onSelect?: TabSelectEventCallback;
34
- onChange?: TabSelectEventCallback;
35
- focused?: boolean;
36
- };
37
- export type TabSelectStyle = TabSelectElementProps["style"];
38
- type SelectEventCallback = (index: number, option: SelectOption) => void;
39
- export type SelectElementProps = ElementProps<SelectRenderableOptions, SelectRenderable, "options" | "showScrollIndicator" | "wrapSelection" | "fastScrollStep"> & {
40
- onSelect?: SelectEventCallback;
41
- onChange?: SelectEventCallback;
42
- focused?: boolean;
43
- };
44
- export type SelectStyle = SelectElementProps["style"];
45
- type TextChildTypes = (string & {}) | number | boolean | null | undefined;
46
- type TextProps = {
47
- children: TextChildTypes | StyledText | TextChunk | Array<TextChildTypes | TextChunk>;
48
11
  };
49
- export type ASCIIFontElementProps = ElementProps<ASCIIFontOptions, ASCIIFontRenderable, "text" | "selectable"> & {};
50
- export type ASCIIFontStyle = ASCIIFontElementProps["style"];
51
- export type TextElementProps = ElementProps<TextOptions, TextRenderable, "content" | "selectable"> & TextProps;
52
- export type TextStyle = TextElementProps["style"];
12
+ type ComponentCatalogue = Record<string, RenderableConstructor>;
13
+ export declare const componentCatalogue: ComponentCatalogue;
14
+ /**
15
+ * Extend the component catalogue with new renderable components
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * // Extend with an object of components
20
+ * extend({
21
+ * consoleButton: ConsoleButtonRenderable,
22
+ * customBox: CustomBoxRenderable
23
+ * })
24
+ * ```
25
+ */
26
+ export declare function extend<T extends ComponentCatalogue>(objects: T): void;
27
+ export declare function getComponentCatalogue(): ComponentCatalogue;
28
+ export type { ExtendedComponentProps, ExtendedIntrinsicElements, RenderableConstructor } from "../types/elements";
@@ -1,5 +1,6 @@
1
1
  import { Renderable, TextRenderable, type RenderContext, type TextChunk, type TextOptions } from "@opentui/core";
2
2
  import { type DomNode } from "../reconciler";
3
+ export declare const isTextChunk: (node: any) => node is TextChunk;
3
4
  /**
4
5
  * Represents a text node in the SolidJS reconciler.
5
6
  */
@@ -1,5 +1,4 @@
1
- import { Renderable } from "@opentui/core";
1
+ import { Renderable, type TextChunk } from "@opentui/core";
2
2
  import { TextNode } from "./elements/text-node";
3
- export type DomNode = Renderable | TextNode;
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;
5
- export declare const insert: typeof solidUniversalInsert;
3
+ export type DomNode = Renderable | TextNode | TextChunk;
4
+ export declare const _render: (code: () => DomNode, node: DomNode) => () => void, effect: <T>(fn: (prev?: T) => T, init?: T) => void, memo: <T>(fn: () => T, equal: boolean) => () => T, createComponent: <T>(Comp: (props: T) => DomNode, props: T) => DomNode, createElement: (tag: string) => DomNode, createTextNode: (value: string) => DomNode, insertNode: (parent: DomNode, node: DomNode, anchor?: DomNode | undefined) => void, insert: <T>(parent: any, accessor: T | (() => T), marker?: any | null, initial?: any) => DomNode, spread: <T>(node: any, accessor: (() => T) | T, skipChildren?: boolean) => void, setProp: <T>(node: DomNode, name: string, value: T, prev?: T | undefined) => T, mergeProps: (...sources: unknown[]) => unknown, use: <A, T>(fn: (element: DomNode, arg: A) => T, element: DomNode, arg: A) => T;
@@ -0,0 +1,64 @@
1
+ import type { ASCIIFontOptions, ASCIIFontRenderable, BoxOptions, BoxRenderable, InputRenderable, InputRenderableOptions, Renderable, RenderableOptions, RenderContext, SelectOption, SelectRenderable, SelectRenderableOptions, StyledText, TabSelectOption, TabSelectRenderable, TabSelectRenderableOptions, TextChunk, TextOptions, TextRenderable } from "@opentui/core";
2
+ import type { JSX, Ref } from "solid-js";
3
+ /** Properties that should not be included in the style prop */
4
+ export type NonStyledProps = "id" | "buffered" | "live" | "enableLayout" | "selectable" | "renderAfter" | "renderBefore" | `on${string}`;
5
+ /** Solid-specific props for all components */
6
+ export type ElementProps<TRenderable = unknown> = {
7
+ ref?: Ref<TRenderable>;
8
+ };
9
+ /** Base type for any renderable constructor */
10
+ export type RenderableConstructor<TRenderable extends Renderable = Renderable> = new (ctx: RenderContext, options: any) => TRenderable;
11
+ /** Extract the options type from a renderable constructor */
12
+ type ExtractRenderableOptions<TConstructor> = TConstructor extends new (ctx: RenderContext, options: infer TOptions) => any ? TOptions : never;
13
+ /** Extract the renderable type from a constructor */
14
+ type ExtractRenderable<TConstructor> = TConstructor extends new (ctx: RenderContext, options: any) => infer TRenderable ? TRenderable : never;
15
+ /** Determine which properties should be excluded from styling for different renderable types */
16
+ export type GetNonStyledProperties<TConstructor> = TConstructor extends RenderableConstructor<TextRenderable> ? NonStyledProps | "content" : TConstructor extends RenderableConstructor<BoxRenderable> ? NonStyledProps | "title" : TConstructor extends RenderableConstructor<ASCIIFontRenderable> ? NonStyledProps | "text" | "selectable" : TConstructor extends RenderableConstructor<InputRenderable> ? NonStyledProps | "placeholder" | "value" : NonStyledProps;
17
+ /** Base props for container components that accept children */
18
+ type ContainerProps<TOptions> = TOptions & {
19
+ children?: JSX.Element;
20
+ };
21
+ /** Smart component props that automatically determine excluded properties */
22
+ type ComponentProps<TOptions extends RenderableOptions<TRenderable>, TRenderable extends Renderable> = TOptions & {
23
+ style?: Partial<Omit<TOptions, GetNonStyledProperties<RenderableConstructor<TRenderable>>>>;
24
+ } & ElementProps<TRenderable>;
25
+ /** Valid text content types for Text component children */
26
+ type TextChildren = (string & {}) | number | boolean | null | undefined;
27
+ export type TextProps = ComponentProps<TextOptions, TextRenderable> & {
28
+ children?: TextChildren | StyledText | TextChunk | Array<TextChildren | StyledText | TextChunk>;
29
+ };
30
+ export type BoxProps = ComponentProps<ContainerProps<BoxOptions>, BoxRenderable>;
31
+ export type InputProps = ComponentProps<InputRenderableOptions, InputRenderable> & {
32
+ focused?: boolean;
33
+ onInput?: (value: string) => void;
34
+ onChange?: (value: string) => void;
35
+ onSubmit?: (value: string) => void;
36
+ };
37
+ export type SelectProps = ComponentProps<SelectRenderableOptions, SelectRenderable> & {
38
+ focused?: boolean;
39
+ onChange?: (index: number, option: SelectOption | null) => void;
40
+ onSelect?: (index: number, option: SelectOption | null) => void;
41
+ };
42
+ export type AsciiFontProps = ComponentProps<ASCIIFontOptions, ASCIIFontRenderable>;
43
+ export type TabSelectProps = ComponentProps<TabSelectRenderableOptions, TabSelectRenderable> & {
44
+ focused?: boolean;
45
+ onChange?: (index: number, option: TabSelectOption | null) => void;
46
+ onSelect?: (index: number, option: TabSelectOption | null) => void;
47
+ };
48
+ /** Convert renderable constructor to component props with proper style exclusions */
49
+ export type ExtendedComponentProps<TConstructor extends RenderableConstructor, TOptions = ExtractRenderableOptions<TConstructor>> = TOptions & {
50
+ children?: JSX.Element;
51
+ style?: Partial<Omit<TOptions, GetNonStyledProperties<TConstructor>>>;
52
+ } & ElementProps<ExtractRenderable<TConstructor>>;
53
+ /** Helper type to create JSX element properties from a component catalogue */
54
+ export type ExtendedIntrinsicElements<TComponentCatalogue extends Record<string, RenderableConstructor>> = {
55
+ [TComponentName in keyof TComponentCatalogue]: ExtendedComponentProps<TComponentCatalogue[TComponentName]>;
56
+ };
57
+ /**
58
+ * Global augmentation interface for extended components
59
+ * This will be augmented by user code using module augmentation
60
+ */
61
+ export interface OpenTUIComponents {
62
+ [componentName: string]: RenderableConstructor;
63
+ }
64
+ export {};