@octanejs/opentui 0.0.1

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/src/slot.ts ADDED
@@ -0,0 +1,322 @@
1
+ import {
2
+ createSlotRegistry,
3
+ type CliRenderer,
4
+ type Plugin,
5
+ type PluginContext,
6
+ type PluginErrorEvent,
7
+ type ResolvedSlotRenderer,
8
+ type SlotMode,
9
+ type SlotRegistry,
10
+ type SlotRegistryOptions,
11
+ } from '@opentui/core';
12
+ import {
13
+ defineUniversalComponent,
14
+ universalComponent,
15
+ universalKey,
16
+ universalTry,
17
+ useEffect,
18
+ useRef,
19
+ useState,
20
+ type UniversalComponent,
21
+ type UniversalRenderable,
22
+ } from 'octane/universal';
23
+ import { OPENTUI_RENDERER_ID } from './config.js';
24
+
25
+ export type { SlotMode };
26
+
27
+ type SlotMap = Record<string, object>;
28
+
29
+ export type OctanePlugin<
30
+ TSlots extends SlotMap,
31
+ TContext extends PluginContext = PluginContext,
32
+ > = Plugin<UniversalRenderable, TSlots, TContext>;
33
+
34
+ export type OctaneSlotProps<
35
+ TSlots extends SlotMap,
36
+ K extends keyof TSlots,
37
+ TContext extends PluginContext = PluginContext,
38
+ > = {
39
+ registry: SlotRegistry<UniversalRenderable, TSlots, TContext>;
40
+ name: K;
41
+ mode?: SlotMode;
42
+ children?: UniversalRenderable;
43
+ pluginFailurePlaceholder?: (failure: PluginErrorEvent) => UniversalRenderable;
44
+ } & TSlots[K];
45
+
46
+ export type OctaneBoundSlotProps<TSlots extends SlotMap, K extends keyof TSlots> = {
47
+ name: K;
48
+ mode?: SlotMode;
49
+ children?: UniversalRenderable;
50
+ } & TSlots[K];
51
+
52
+ export type OctaneRegistrySlotComponent<
53
+ TSlots extends SlotMap,
54
+ TContext extends PluginContext = PluginContext,
55
+ > = UniversalComponent<OctaneSlotProps<TSlots, keyof TSlots, TContext>> & {
56
+ <K extends keyof TSlots>(props: OctaneSlotProps<TSlots, K, TContext>): UniversalRenderable;
57
+ };
58
+
59
+ export type OctaneSlotComponent<TSlots extends SlotMap> = UniversalComponent<
60
+ OctaneBoundSlotProps<TSlots, keyof TSlots>
61
+ > & {
62
+ <K extends keyof TSlots>(props: OctaneBoundSlotProps<TSlots, K>): UniversalRenderable;
63
+ };
64
+
65
+ export interface OctaneSlotOptions {
66
+ pluginFailurePlaceholder?: (failure: PluginErrorEvent) => UniversalRenderable;
67
+ }
68
+
69
+ /** Source-compatible type aliases for code migrating from @opentui/react. */
70
+ export type ReactPlugin<
71
+ TSlots extends SlotMap,
72
+ TContext extends PluginContext = PluginContext,
73
+ > = OctanePlugin<TSlots, TContext>;
74
+ export type ReactSlotProps<
75
+ TSlots extends SlotMap,
76
+ K extends keyof TSlots,
77
+ TContext extends PluginContext = PluginContext,
78
+ > = OctaneSlotProps<TSlots, K, TContext>;
79
+ export type ReactBoundSlotProps<
80
+ TSlots extends SlotMap,
81
+ K extends keyof TSlots,
82
+ > = OctaneBoundSlotProps<TSlots, K>;
83
+ export type ReactRegistrySlotComponent<
84
+ TSlots extends SlotMap,
85
+ TContext extends PluginContext = PluginContext,
86
+ > = OctaneRegistrySlotComponent<TSlots, TContext>;
87
+ export type ReactSlotComponent<TSlots extends SlotMap> = OctaneSlotComponent<TSlots>;
88
+ export type ReactSlotOptions = OctaneSlotOptions;
89
+
90
+ export function createOctaneSlotRegistry<
91
+ TSlots extends SlotMap,
92
+ TContext extends PluginContext = PluginContext,
93
+ >(
94
+ renderer: CliRenderer,
95
+ context: TContext,
96
+ options: SlotRegistryOptions = {},
97
+ ): SlotRegistry<UniversalRenderable, TSlots, TContext> {
98
+ return createSlotRegistry<UniversalRenderable, TSlots, TContext>(
99
+ renderer,
100
+ 'octane:slot-registry',
101
+ context,
102
+ options,
103
+ );
104
+ }
105
+
106
+ /** Migration alias retained from the upstream package. */
107
+ export const createReactSlotRegistry = createOctaneSlotRegistry;
108
+
109
+ interface PendingRenderReport {
110
+ pluginId: string;
111
+ slot: string;
112
+ error: Error;
113
+ }
114
+
115
+ const SLOT_VERSION = Symbol('opentui.slot.version');
116
+ const SLOT_FAILURES = Symbol('opentui.slot.failures');
117
+ const SLOT_PENDING_REPORTS = Symbol('opentui.slot.pending-reports');
118
+ const SLOT_RESETS = Symbol('opentui.slot.resets');
119
+ const SLOT_RESET_VERSION = Symbol('opentui.slot.reset-version');
120
+ const SLOT_SUBSCRIPTION = Symbol('opentui.slot.subscription');
121
+ const SLOT_REPORT_EFFECT = Symbol('opentui.slot.report-effect');
122
+ const SLOT_RESET_EFFECT = Symbol('opentui.slot.reset-effect');
123
+
124
+ function normalizeError(error: unknown): Error {
125
+ if (error instanceof Error) return error;
126
+ return new Error(typeof error === 'string' ? error : String(error));
127
+ }
128
+
129
+ function isEmptyRenderable(value: UniversalRenderable): boolean {
130
+ return value === null || value === undefined || value === false;
131
+ }
132
+
133
+ function getSlotProps<
134
+ TSlots extends SlotMap,
135
+ K extends keyof TSlots,
136
+ TContext extends PluginContext,
137
+ >(props: OctaneSlotProps<TSlots, K, TContext>): TSlots[K] {
138
+ const {
139
+ children: _children,
140
+ mode: _mode,
141
+ name: _name,
142
+ registry: _registry,
143
+ pluginFailurePlaceholder: _pluginFailurePlaceholder,
144
+ ...slotProps
145
+ } = props;
146
+ return slotProps as TSlots[K];
147
+ }
148
+
149
+ function renderPluginFailurePlaceholder(
150
+ registry: SlotRegistry<UniversalRenderable, any, any>,
151
+ placeholder: ((failure: PluginErrorEvent) => UniversalRenderable) | undefined,
152
+ failure: PluginErrorEvent,
153
+ ): UniversalRenderable {
154
+ if (placeholder === undefined) return null;
155
+ try {
156
+ return placeholder(failure);
157
+ } catch (error) {
158
+ registry.reportPluginError({
159
+ pluginId: failure.pluginId,
160
+ slot: failure.slot,
161
+ phase: 'error_placeholder',
162
+ source: 'octane',
163
+ error,
164
+ });
165
+ return null;
166
+ }
167
+ }
168
+
169
+ const SlotImplementation = defineUniversalComponent<OctaneSlotProps<any, any, any>>(
170
+ OPENTUI_RENDERER_ID,
171
+ (props) => {
172
+ const [version, setVersion] = useState(0, SLOT_VERSION);
173
+ const failures = useRef<Map<string, PluginErrorEvent>>(new Map(), SLOT_FAILURES);
174
+ const pendingReports = useRef<Map<string, PendingRenderReport>>(
175
+ new Map(),
176
+ SLOT_PENDING_REPORTS,
177
+ );
178
+ const resets = useRef<Map<string, () => void>>(new Map(), SLOT_RESETS);
179
+ const resetVersion = useRef(version, SLOT_RESET_VERSION);
180
+ const registry = props.registry;
181
+ const slotName = String(props.name);
182
+
183
+ useEffect(
184
+ () => registry.subscribe(() => setVersion((current) => current + 1)),
185
+ [registry],
186
+ SLOT_SUBSCRIPTION,
187
+ );
188
+ useEffect(
189
+ () => {
190
+ if (pendingReports.current.size === 0) return;
191
+ const reports = [...pendingReports.current.values()];
192
+ pendingReports.current.clear();
193
+ for (const report of reports) {
194
+ const failure = registry.reportPluginError({
195
+ pluginId: report.pluginId,
196
+ slot: report.slot,
197
+ phase: 'render',
198
+ source: 'octane',
199
+ error: report.error,
200
+ });
201
+ failures.current.set(`${report.slot}:${report.pluginId}:render`, failure);
202
+ }
203
+ },
204
+ null,
205
+ SLOT_REPORT_EFFECT,
206
+ );
207
+ useEffect(
208
+ () => {
209
+ if (resetVersion.current === version) return;
210
+ resetVersion.current = version;
211
+ const pendingResets = [...resets.current.values()];
212
+ resets.current.clear();
213
+ for (const reset of pendingResets) reset();
214
+ },
215
+ [version],
216
+ SLOT_RESET_EFFECT,
217
+ );
218
+
219
+ const entries = registry.resolveEntries(props.name) as Array<
220
+ ResolvedSlotRenderer<UniversalRenderable, object, PluginContext>
221
+ >;
222
+ const slotProps = getSlotProps(props);
223
+
224
+ const renderFailure = (
225
+ entry: ResolvedSlotRenderer<UniversalRenderable, object, PluginContext>,
226
+ error: unknown,
227
+ fallback: UniversalRenderable = null,
228
+ ): UniversalRenderable => {
229
+ const normalized = normalizeError(error);
230
+ const failureKey = `${slotName}:${entry.id}:render`;
231
+ const previous = failures.current.get(failureKey);
232
+ const sameFailure = previous?.error.message === normalized.message;
233
+ if (!sameFailure) {
234
+ const pending = pendingReports.current.get(failureKey);
235
+ if (pending?.error.message !== normalized.message) {
236
+ pendingReports.current.set(failureKey, {
237
+ pluginId: entry.id,
238
+ slot: slotName,
239
+ error: normalized,
240
+ });
241
+ }
242
+ }
243
+ const failure =
244
+ sameFailure && previous !== undefined
245
+ ? previous
246
+ : {
247
+ pluginId: entry.id,
248
+ slot: slotName,
249
+ phase: 'render' as const,
250
+ source: 'octane',
251
+ error: normalized,
252
+ timestamp: Date.now(),
253
+ };
254
+ failures.current.set(failureKey, failure);
255
+ const placeholder = renderPluginFailurePlaceholder(
256
+ registry,
257
+ props.pluginFailurePlaceholder,
258
+ failure,
259
+ );
260
+ return isEmptyRenderable(placeholder) ? fallback : placeholder;
261
+ };
262
+
263
+ const renderEntry = (
264
+ entry: ResolvedSlotRenderer<UniversalRenderable, object, PluginContext>,
265
+ fallback: UniversalRenderable = null,
266
+ ): UniversalRenderable => {
267
+ const failureKey = `${slotName}:${entry.id}:render`;
268
+ let rendered: UniversalRenderable;
269
+ try {
270
+ rendered = entry.renderer(registry.context, slotProps);
271
+ } catch (error) {
272
+ const failed = renderFailure(entry, error, fallback);
273
+ return isEmptyRenderable(failed) ? failed : universalKey(`${slotName}:${entry.id}`, failed);
274
+ }
275
+ if (isEmptyRenderable(rendered)) return fallback;
276
+ return universalKey(
277
+ `${slotName}:${entry.id}`,
278
+ universalTry(
279
+ () => {
280
+ resets.current.delete(failureKey);
281
+ failures.current.delete(failureKey);
282
+ pendingReports.current.delete(failureKey);
283
+ return rendered;
284
+ },
285
+ null,
286
+ (error, reset) => {
287
+ resets.current.set(failureKey, reset);
288
+ return renderFailure(entry, error, fallback);
289
+ },
290
+ ),
291
+ );
292
+ };
293
+
294
+ if (entries.length === 0) return props.children;
295
+ if (props.mode === 'single_winner') return renderEntry(entries[0], props.children);
296
+ if (props.mode === 'replace') {
297
+ if (entries.length === 1) return renderEntry(entries[0], props.children);
298
+ const rendered = entries.map((entry) => renderEntry(entry));
299
+ return rendered.some((value) => !isEmptyRenderable(value)) ? rendered : props.children;
300
+ }
301
+ return [props.children, ...entries.map((entry) => renderEntry(entry))];
302
+ },
303
+ { module: '@octanejs/opentui' },
304
+ );
305
+
306
+ export const Slot = SlotImplementation as OctaneRegistrySlotComponent<any, any>;
307
+
308
+ export function createSlot<TSlots extends SlotMap, TContext extends PluginContext = PluginContext>(
309
+ registry: SlotRegistry<UniversalRenderable, TSlots, TContext>,
310
+ options: OctaneSlotOptions = {},
311
+ ): OctaneSlotComponent<TSlots> {
312
+ return defineUniversalComponent<OctaneBoundSlotProps<TSlots, keyof TSlots>>(
313
+ OPENTUI_RENDERER_ID,
314
+ (props) =>
315
+ universalComponent(OPENTUI_RENDERER_ID, SlotImplementation, {
316
+ ...props,
317
+ registry,
318
+ pluginFailurePlaceholder: options.pluginFailurePlaceholder,
319
+ }),
320
+ { module: '@octanejs/opentui' },
321
+ ) as OctaneSlotComponent<TSlots>;
322
+ }
package/src/slots.ts ADDED
@@ -0,0 +1,21 @@
1
+ const subSlotCache = new Map<symbol, Map<string, symbol>>();
2
+
3
+ export function subSlot(slot: symbol | undefined, tag: string): symbol | undefined {
4
+ if (slot === undefined) return undefined;
5
+ let byTag = subSlotCache.get(slot);
6
+ if (byTag === undefined) {
7
+ byTag = new Map();
8
+ subSlotCache.set(slot, byTag);
9
+ }
10
+ let result = byTag.get(tag);
11
+ if (result === undefined) {
12
+ result = Symbol.for(`${slot.description ?? ''}:@octanejs/opentui:${tag}`);
13
+ byTag.set(tag, result);
14
+ }
15
+ return result;
16
+ }
17
+
18
+ export function splitSlot(args: readonly unknown[]): [readonly unknown[], symbol | undefined] {
19
+ const tail = args.at(-1);
20
+ return typeof tail === 'symbol' ? [args.slice(0, -1), tail] : [args, undefined];
21
+ }
@@ -0,0 +1,38 @@
1
+ import { createTestRenderer, type TestRendererOptions } from '@opentui/core/testing';
2
+ import type { UniversalComponent } from 'octane/universal';
3
+ import { act } from './scheduling.js';
4
+ import { createRoot, type Root } from './root.js';
5
+
6
+ export function testRender(
7
+ component: UniversalComponent<void | undefined>,
8
+ options: TestRendererOptions,
9
+ ): ReturnType<typeof createTestRenderer>;
10
+ export function testRender<P>(
11
+ component: UniversalComponent<P>,
12
+ props: P,
13
+ options: TestRendererOptions,
14
+ ): ReturnType<typeof createTestRenderer>;
15
+ export async function testRender<P>(
16
+ component: UniversalComponent<P>,
17
+ propsOrOptions: P | TestRendererOptions,
18
+ maybeOptions?: TestRendererOptions,
19
+ ): ReturnType<typeof createTestRenderer> {
20
+ const props = maybeOptions === undefined ? undefined : (propsOrOptions as P);
21
+ const options = (maybeOptions ?? propsOrOptions) as TestRendererOptions;
22
+ let root: Root | null = null;
23
+ const testSetup = await createTestRenderer({
24
+ ...options,
25
+ onDestroy() {
26
+ root?.unmount();
27
+ root = null;
28
+ options.onDestroy?.();
29
+ },
30
+ });
31
+ root = createRoot(testSetup.renderer);
32
+ await act(() => {
33
+ if (root === null) return;
34
+ if (maybeOptions === undefined) root.render(component as UniversalComponent<void>);
35
+ else root.render(component, props as P);
36
+ });
37
+ return testSetup;
38
+ }
package/src/text.ts ADDED
@@ -0,0 +1,73 @@
1
+ import {
2
+ TextAttributes,
3
+ TextNodeRenderable,
4
+ type RenderContext,
5
+ type TextNodeOptions,
6
+ } from '@opentui/core';
7
+
8
+ export const textNodeKeys = ['span', 'b', 'strong', 'i', 'em', 'u', 'br', 'a'] as const;
9
+ export type TextNodeKey = (typeof textNodeKeys)[number];
10
+
11
+ export class SpanRenderable extends TextNodeRenderable {
12
+ constructor(
13
+ private readonly ctx: RenderContext | null,
14
+ options: TextNodeOptions,
15
+ ) {
16
+ super(options);
17
+ }
18
+ }
19
+
20
+ class TextModifierRenderable extends SpanRenderable {
21
+ constructor(options: TextNodeOptions, modifier?: TextNodeKey) {
22
+ super(null, options);
23
+ if (modifier === 'b' || modifier === 'strong') {
24
+ this.attributes = (this.attributes || 0) | TextAttributes.BOLD;
25
+ } else if (modifier === 'i' || modifier === 'em') {
26
+ this.attributes = (this.attributes || 0) | TextAttributes.ITALIC;
27
+ } else if (modifier === 'u') {
28
+ this.attributes = (this.attributes || 0) | TextAttributes.UNDERLINE;
29
+ }
30
+ }
31
+ }
32
+
33
+ export class BoldSpanRenderable extends TextModifierRenderable {
34
+ constructor(_ctx: RenderContext | null, options: TextNodeOptions) {
35
+ super(options, 'b');
36
+ }
37
+ }
38
+
39
+ export class ItalicSpanRenderable extends TextModifierRenderable {
40
+ constructor(_ctx: RenderContext | null, options: TextNodeOptions) {
41
+ super(options, 'i');
42
+ }
43
+ }
44
+
45
+ export class UnderlineSpanRenderable extends TextModifierRenderable {
46
+ constructor(_ctx: RenderContext | null, options: TextNodeOptions) {
47
+ super(options, 'u');
48
+ }
49
+ }
50
+
51
+ export class LineBreakRenderable extends SpanRenderable {
52
+ constructor(_ctx: RenderContext | null, options: TextNodeOptions) {
53
+ super(null, options);
54
+ this.add();
55
+ }
56
+
57
+ public override add(): number {
58
+ return super.add('\n');
59
+ }
60
+ }
61
+
62
+ export interface LinkOptions extends TextNodeOptions {
63
+ href: string;
64
+ }
65
+
66
+ export class LinkRenderable extends SpanRenderable {
67
+ constructor(_ctx: RenderContext | null, options: LinkOptions) {
68
+ super(null, {
69
+ ...options,
70
+ link: { url: options.href },
71
+ });
72
+ }
73
+ }
@@ -0,0 +1,23 @@
1
+ import type { TimeToFirstDrawRenderable } from '@opentui/core';
2
+ import {
3
+ defineUniversalComponent,
4
+ universalPlan,
5
+ universalProps,
6
+ universalValue,
7
+ } from 'octane/universal';
8
+ import { OPENTUI_RENDERER_ID } from './config.js';
9
+ import type { TimeToFirstDrawProps } from './types.js';
10
+
11
+ export type { TimeToFirstDrawProps } from './types.js';
12
+
13
+ const TIME_TO_FIRST_DRAW_PLAN = universalPlan(OPENTUI_RENDERER_ID, {
14
+ kind: 'host',
15
+ type: 'time-to-first-draw',
16
+ propsSlot: 0,
17
+ });
18
+
19
+ export const TimeToFirstDraw = defineUniversalComponent<TimeToFirstDrawProps>(
20
+ OPENTUI_RENDERER_ID,
21
+ (props) => universalValue(TIME_TO_FIRST_DRAW_PLAN, [universalProps([['spread', props]])]),
22
+ { module: '@octanejs/opentui' },
23
+ );
package/src/types.ts ADDED
@@ -0,0 +1,230 @@
1
+ import type {
2
+ ASCIIFontOptions,
3
+ ASCIIFontRenderable,
4
+ BaseRenderable,
5
+ BoxOptions,
6
+ BoxRenderable,
7
+ CodeOptions,
8
+ CodeRenderable,
9
+ ContentChangeEvent,
10
+ CursorChangeEvent,
11
+ DiffRenderable,
12
+ DiffRenderableOptions,
13
+ ImageRenderable,
14
+ ImageRenderableOptions,
15
+ InputRenderable,
16
+ InputRenderableOptions,
17
+ KeyEvent,
18
+ LineNumberOptions,
19
+ LineNumberRenderable,
20
+ MarkdownOptions,
21
+ MarkdownRenderable,
22
+ RenderContext,
23
+ RenderableOptions,
24
+ ScrollBoxOptions,
25
+ ScrollBoxRenderable,
26
+ SelectOption,
27
+ SelectRenderable,
28
+ SelectRenderableOptions,
29
+ TabSelectOption,
30
+ TabSelectRenderable,
31
+ TabSelectRenderableOptions,
32
+ TextareaOptions,
33
+ TextareaRenderable,
34
+ TextNodeOptions,
35
+ TextNodeRenderable,
36
+ TextOptions,
37
+ TextRenderable,
38
+ TimeToFirstDrawRenderable,
39
+ } from '@opentui/core';
40
+ import type { UniversalKey, UniversalRenderable } from 'octane/universal';
41
+
42
+ export type NonStyledProps =
43
+ | 'id'
44
+ | 'buffered'
45
+ | 'live'
46
+ | 'enableLayout'
47
+ | 'selectable'
48
+ | 'renderAfter'
49
+ | 'renderBefore'
50
+ | `on${string}`;
51
+
52
+ export type OpenTUIRef<T> =
53
+ ((value: T | null) => void | (() => void)) | { current: T | null } | readonly OpenTUIRef<T>[];
54
+
55
+ export type OctaneProps<TRenderable = unknown> = {
56
+ key?: UniversalKey;
57
+ ref?: OpenTUIRef<TRenderable>;
58
+ };
59
+
60
+ /** Source-compatible alias whose refs use Octane's refs-as-props contract. */
61
+ export type ReactProps<TRenderable = unknown> = OctaneProps<TRenderable>;
62
+
63
+ export type RenderableConstructor<TRenderable extends BaseRenderable = BaseRenderable> = new (
64
+ ctx: RenderContext,
65
+ options: any,
66
+ ) => TRenderable;
67
+
68
+ type ExtractRenderableOptions<TConstructor> = TConstructor extends new (
69
+ ctx: RenderContext,
70
+ options: infer TOptions,
71
+ ) => any
72
+ ? TOptions
73
+ : never;
74
+
75
+ type ExtractRenderable<TConstructor> = TConstructor extends new (
76
+ ctx: RenderContext,
77
+ options: any,
78
+ ) => infer TRenderable
79
+ ? TRenderable
80
+ : never;
81
+
82
+ export type GetNonStyledProperties<TConstructor> =
83
+ TConstructor extends RenderableConstructor<TextRenderable>
84
+ ? NonStyledProps | 'content'
85
+ : TConstructor extends RenderableConstructor<BoxRenderable>
86
+ ? NonStyledProps | 'title' | 'bottomTitle'
87
+ : TConstructor extends RenderableConstructor<ASCIIFontRenderable>
88
+ ? NonStyledProps | 'text' | 'selectable'
89
+ : TConstructor extends RenderableConstructor<InputRenderable>
90
+ ? NonStyledProps | 'placeholder' | 'value'
91
+ : TConstructor extends RenderableConstructor<TextareaRenderable>
92
+ ? NonStyledProps | 'placeholder' | 'initialValue'
93
+ : TConstructor extends RenderableConstructor<CodeRenderable>
94
+ ? | NonStyledProps
95
+ | 'content'
96
+ | 'filetype'
97
+ | 'syntaxStyle'
98
+ | 'treeSitterClient'
99
+ | 'conceal'
100
+ | 'drawUnstyledText'
101
+ : TConstructor extends RenderableConstructor<MarkdownRenderable>
102
+ ? | NonStyledProps
103
+ | 'content'
104
+ | 'syntaxStyle'
105
+ | 'treeSitterClient'
106
+ | 'conceal'
107
+ | 'renderNode'
108
+ : TConstructor extends RenderableConstructor<ImageRenderable>
109
+ ? NonStyledProps | 'source'
110
+ : NonStyledProps;
111
+
112
+ type ContainerProps<TOptions> = TOptions & { children?: UniversalRenderable };
113
+
114
+ type ComponentProps<
115
+ TOptions extends RenderableOptions<TRenderable>,
116
+ TRenderable extends BaseRenderable,
117
+ > = TOptions & {
118
+ style?: Partial<Omit<TOptions, GetNonStyledProperties<RenderableConstructor<TRenderable>>>>;
119
+ } & OctaneProps<TRenderable>;
120
+
121
+ type TextChildren = UniversalRenderable;
122
+
123
+ export type TextProps = ComponentProps<TextOptions, TextRenderable> & {
124
+ children?: TextChildren;
125
+ };
126
+
127
+ export type SpanProps = ComponentProps<TextNodeOptions, TextNodeRenderable> & {
128
+ children?: TextChildren;
129
+ };
130
+
131
+ export type LinkProps = SpanProps & { href: string };
132
+ export type LineBreakProps = Pick<SpanProps, 'id' | 'key' | 'ref'>;
133
+
134
+ export type BoxProps = ComponentProps<ContainerProps<BoxOptions>, BoxRenderable> & {
135
+ focused?: boolean;
136
+ };
137
+
138
+ export type InputProps = ComponentProps<InputRenderableOptions, InputRenderable> & {
139
+ focused?: boolean;
140
+ onInput?: (value: string) => void;
141
+ onChange?: (value: string) => void;
142
+ onSubmit?: (value: string) => void;
143
+ };
144
+
145
+ export type TextareaProps = ComponentProps<TextareaOptions, TextareaRenderable> & {
146
+ focused?: boolean;
147
+ onSubmit?: () => void;
148
+ onContentChange?: (event: ContentChangeEvent) => void;
149
+ onCursorChange?: (event: CursorChangeEvent) => void;
150
+ onKeyDown?: (event: KeyEvent) => void;
151
+ };
152
+
153
+ export type CodeProps = ComponentProps<CodeOptions, CodeRenderable>;
154
+ export type ImageProps = ComponentProps<ImageRenderableOptions, ImageRenderable>;
155
+ export type MarkdownProps = ComponentProps<MarkdownOptions, MarkdownRenderable>;
156
+ export type DiffProps = ComponentProps<DiffRenderableOptions, DiffRenderable>;
157
+
158
+ export type SelectProps = ComponentProps<SelectRenderableOptions, SelectRenderable> & {
159
+ focused?: boolean;
160
+ onChange?: (index: number, option: SelectOption | null) => void;
161
+ onSelect?: (index: number, option: SelectOption | null) => void;
162
+ };
163
+
164
+ export type ScrollBoxProps = ComponentProps<
165
+ ContainerProps<ScrollBoxOptions>,
166
+ ScrollBoxRenderable
167
+ > & {
168
+ focused?: boolean;
169
+ };
170
+
171
+ export type AsciiFontProps = ComponentProps<ASCIIFontOptions, ASCIIFontRenderable>;
172
+
173
+ export type TabSelectProps = ComponentProps<TabSelectRenderableOptions, TabSelectRenderable> & {
174
+ focused?: boolean;
175
+ onChange?: (index: number, option: TabSelectOption | null) => void;
176
+ onSelect?: (index: number, option: TabSelectOption | null) => void;
177
+ };
178
+
179
+ export type LineNumberProps = ComponentProps<
180
+ ContainerProps<LineNumberOptions>,
181
+ LineNumberRenderable
182
+ > & { focused?: boolean };
183
+
184
+ export type TimeToFirstDrawProps = ExtendedComponentProps<typeof TimeToFirstDrawRenderable>;
185
+
186
+ export type ExtendedComponentProps<
187
+ TConstructor extends RenderableConstructor,
188
+ TOptions = ExtractRenderableOptions<TConstructor>,
189
+ > = TOptions & {
190
+ children?: UniversalRenderable;
191
+ style?: Partial<Omit<TOptions, GetNonStyledProperties<TConstructor>>>;
192
+ } & OctaneProps<ExtractRenderable<TConstructor>>;
193
+
194
+ export type ExtendedIntrinsicElements<
195
+ TComponentCatalogue extends Record<string, RenderableConstructor>,
196
+ > = {
197
+ [TComponentName in keyof TComponentCatalogue]: ExtendedComponentProps<
198
+ TComponentCatalogue[TComponentName]
199
+ >;
200
+ };
201
+
202
+ /** Augment this interface to type application-specific tags registered with extend(). */
203
+ export interface OpenTUIComponents {
204
+ [componentName: string]: RenderableConstructor;
205
+ }
206
+
207
+ export interface OpenTUIIntrinsicElements extends ExtendedIntrinsicElements<OpenTUIComponents> {
208
+ box: BoxProps;
209
+ text: TextProps;
210
+ span: SpanProps;
211
+ code: CodeProps;
212
+ diff: DiffProps;
213
+ markdown: MarkdownProps;
214
+ input: InputProps;
215
+ textarea: TextareaProps;
216
+ select: SelectProps;
217
+ scrollbox: ScrollBoxProps;
218
+ 'ascii-font': AsciiFontProps;
219
+ 'tab-select': TabSelectProps;
220
+ 'line-number': LineNumberProps;
221
+ image: ImageProps;
222
+ 'time-to-first-draw': TimeToFirstDrawProps;
223
+ b: SpanProps;
224
+ i: SpanProps;
225
+ u: SpanProps;
226
+ strong: SpanProps;
227
+ em: SpanProps;
228
+ br: LineBreakProps;
229
+ a: LinkProps;
230
+ }