@effect-tui/react 0.15.0 → 0.15.2
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/dist/src/hosts/box.d.ts.map +1 -1
- package/dist/src/hosts/box.js +29 -15
- package/dist/src/hosts/box.js.map +1 -1
- package/dist/src/hosts/codeblock.d.ts +5 -2
- package/dist/src/hosts/codeblock.d.ts.map +1 -1
- package/dist/src/hosts/codeblock.js +24 -7
- package/dist/src/hosts/codeblock.js.map +1 -1
- package/dist/src/hosts/spacer.d.ts +1 -2
- package/dist/src/hosts/spacer.d.ts.map +1 -1
- package/dist/src/hosts/spacer.js +0 -3
- package/dist/src/hosts/spacer.js.map +1 -1
- package/dist/src/reconciler/host-config.d.ts.map +1 -1
- package/dist/src/reconciler/host-config.js +0 -7
- package/dist/src/reconciler/host-config.js.map +1 -1
- package/dist/src/renderer.d.ts.map +1 -1
- package/dist/src/renderer.js +31 -1
- package/dist/src/renderer.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/hosts/box.ts +30 -16
- package/src/hosts/codeblock.ts +28 -9
- package/src/hosts/spacer.ts +1 -5
- package/src/reconciler/host-config.ts +0 -7
- package/src/renderer.ts +30 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-tui/react",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.2",
|
|
4
4
|
"description": "React bindings for @effect-tui/core",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"prepublishOnly": "bun run typecheck && bun run build"
|
|
84
84
|
},
|
|
85
85
|
"dependencies": {
|
|
86
|
-
"@effect-tui/core": "^0.15.
|
|
86
|
+
"@effect-tui/core": "^0.15.2",
|
|
87
87
|
"@effect/platform": "^0.94.1",
|
|
88
88
|
"@effect/platform-bun": "^0.87.0",
|
|
89
89
|
"@effect/rpc": "^0.73.0",
|
package/src/hosts/box.ts
CHANGED
|
@@ -59,8 +59,10 @@ export class BoxHost extends SingleChildHost {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
private get insetY(): number {
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
// When titleDivider is enabled: top border + title row + divider line = 3
|
|
63
|
+
// When title but no divider: title embedded in top border = 1
|
|
64
|
+
const titleHeight = this.titleDivider && this.title && this.border !== "none" ? 2 : 0 // title row + divider
|
|
65
|
+
return this.borderThickness + titleHeight + this.padding.top + this.padding.bottom + this.borderThickness
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
measure(maxW: number, maxH: number): Size {
|
|
@@ -93,10 +95,11 @@ export class BoxHost extends SingleChildHost {
|
|
|
93
95
|
const layoutRect = this.layoutWithConstraints(rect)
|
|
94
96
|
|
|
95
97
|
const t = this.borderThickness
|
|
96
|
-
|
|
98
|
+
// When titleDivider: title row (1) + divider line (1) = 2
|
|
99
|
+
const titleHeight = this.titleDivider && this.title && this.border !== "none" ? 2 : 0
|
|
97
100
|
const innerRect: Rect = {
|
|
98
101
|
x: layoutRect.x + t + this.padding.left,
|
|
99
|
-
y: layoutRect.y + t +
|
|
102
|
+
y: layoutRect.y + t + titleHeight + this.padding.top,
|
|
100
103
|
w: Math.max(0, layoutRect.w - this.insetX),
|
|
101
104
|
h: Math.max(0, layoutRect.h - this.insetY),
|
|
102
105
|
}
|
|
@@ -123,29 +126,40 @@ export class BoxHost extends SingleChildHost {
|
|
|
123
126
|
const borderStyle = palette.id({ fg: borderFg })
|
|
124
127
|
drawBorder(buffer, x, y, w, h, chars, borderStyle)
|
|
125
128
|
|
|
126
|
-
// Draw title
|
|
129
|
+
// Draw title if present
|
|
127
130
|
if (this.title && w >= 7) {
|
|
128
131
|
const titleFg = toColorValue(this.titleColor) ?? borderFg
|
|
129
132
|
const titleStyle = palette.id({ fg: titleFg, bold: this.titleBold })
|
|
130
|
-
// Reserve
|
|
131
|
-
const maxTitleLen = w -
|
|
133
|
+
// Reserve space for borders and padding: 4 chars (│ + space + space + │)
|
|
134
|
+
const maxTitleLen = w - 4
|
|
132
135
|
const displayTitle =
|
|
133
136
|
this.title.length > maxTitleLen ? this.title.slice(0, maxTitleLen - 1) + "…" : this.title
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
137
|
+
|
|
138
|
+
if (this.titleDivider && h >= 4) {
|
|
139
|
+
// Title in its own row: │ Title │
|
|
140
|
+
const titleRowY = y + 1
|
|
141
|
+
const titleText = ` ${displayTitle}`
|
|
142
|
+
buffer.drawText(x, titleRowY, chars.v, borderStyle, 1)
|
|
143
|
+
buffer.drawText(x + 1, titleRowY, titleText, titleStyle, titleText.length)
|
|
144
|
+
// Fill remaining space and draw right border
|
|
145
|
+
for (let dx = 1 + titleText.length; dx < w - 1; dx++) {
|
|
146
|
+
buffer.drawText(x + dx, titleRowY, " ", borderStyle, 1)
|
|
147
|
+
}
|
|
148
|
+
buffer.drawText(x + w - 1, titleRowY, chars.v, borderStyle, 1)
|
|
149
|
+
|
|
150
|
+
// Divider line: ├───┤
|
|
151
|
+
const dividerY = y + 2
|
|
142
152
|
const tableChars = tableBorderChars(this.border)
|
|
143
|
-
// Draw ├ at left, ─ across, ┤ at right
|
|
144
153
|
buffer.drawText(x, dividerY, tableChars.lt, borderStyle, 1)
|
|
145
154
|
for (let dx = 1; dx < w - 1; dx++) {
|
|
146
155
|
buffer.drawText(x + dx, dividerY, tableChars.h, borderStyle, 1)
|
|
147
156
|
}
|
|
148
157
|
buffer.drawText(x + w - 1, dividerY, tableChars.rt, borderStyle, 1)
|
|
158
|
+
} else {
|
|
159
|
+
// Title embedded in top border: ┌─ Title ─┐
|
|
160
|
+
const titleX = x + 2
|
|
161
|
+
const titleText = ` ${displayTitle} `
|
|
162
|
+
buffer.drawText(titleX, y, titleText, titleStyle, titleText.length)
|
|
149
163
|
}
|
|
150
164
|
}
|
|
151
165
|
}
|
package/src/hosts/codeblock.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CellBuffer, type Color, Colors, displayWidth, type Palette } from "@effect-tui/core"
|
|
2
2
|
import type { HighlightLine } from "../highlight.js"
|
|
3
|
-
import type { CommonProps, HostContext,
|
|
3
|
+
import type { CommonProps, HostContext, Size } from "../reconciler/types.js"
|
|
4
4
|
import { type Padding, type PaddingInput, resolveBgStyle, resolvePadding, styleIdFromProps } from "../utils/index.js"
|
|
5
5
|
import { BaseHost } from "./base.js"
|
|
6
6
|
|
|
@@ -27,6 +27,7 @@ export class CodeBlockHost extends BaseHost {
|
|
|
27
27
|
|
|
28
28
|
private cachedLineWidths: number[] = []
|
|
29
29
|
private gutterWidth = 0
|
|
30
|
+
private prepared = false
|
|
30
31
|
|
|
31
32
|
constructor(props: CodeBlockProps, ctx: HostContext) {
|
|
32
33
|
super("codeblock", props, ctx)
|
|
@@ -49,10 +50,24 @@ export class CodeBlockHost extends BaseHost {
|
|
|
49
50
|
return this.padding.top + this.padding.bottom
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
private prepareMetrics(): void {
|
|
54
|
+
this.cachedLineWidths = this.lines.map((line) => lineDisplayWidth(line))
|
|
55
|
+
this.gutterWidth = this.computeGutterWidth()
|
|
56
|
+
this.prepared = true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private ensurePrepared(): void {
|
|
60
|
+
if (this.prepared) return
|
|
61
|
+
this.prepareMetrics()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected override prepareSelf(): void {
|
|
65
|
+
this.ensurePrepared()
|
|
66
|
+
}
|
|
67
|
+
|
|
52
68
|
measure(maxW: number, maxH: number): Size {
|
|
53
69
|
const constrained = this.constrainProposal(maxW, maxH)
|
|
54
|
-
this.
|
|
55
|
-
this.gutterWidth = this.computeGutterWidth()
|
|
70
|
+
this.ensurePrepared()
|
|
56
71
|
|
|
57
72
|
const maxLineW = this.cachedLineWidths.reduce((max, w) => (w > max ? w : max), 0)
|
|
58
73
|
const contentW = maxLineW + this.insetX
|
|
@@ -65,10 +80,6 @@ export class CodeBlockHost extends BaseHost {
|
|
|
65
80
|
})
|
|
66
81
|
}
|
|
67
82
|
|
|
68
|
-
override layout(rect: Rect): void {
|
|
69
|
-
super.layout(rect)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
83
|
render(buffer: CellBuffer, palette: Palette): void {
|
|
73
84
|
if (!this.rect) return
|
|
74
85
|
|
|
@@ -120,11 +131,19 @@ export class CodeBlockHost extends BaseHost {
|
|
|
120
131
|
|
|
121
132
|
override updateProps(props: Record<string, unknown>): void {
|
|
122
133
|
super.updateProps(props)
|
|
123
|
-
|
|
124
|
-
if (props.
|
|
134
|
+
let invalidate = false
|
|
135
|
+
if (props.lines !== undefined) {
|
|
136
|
+
this.lines = props.lines as HighlightLine[]
|
|
137
|
+
invalidate = true
|
|
138
|
+
}
|
|
139
|
+
if (props.lineNumbers !== undefined) {
|
|
140
|
+
this.lineNumbers = !!props.lineNumbers
|
|
141
|
+
invalidate = true
|
|
142
|
+
}
|
|
125
143
|
if (props.padding !== undefined) this.padding = resolvePadding(props.padding as CodeBlockProps["padding"])
|
|
126
144
|
if (props.background !== undefined) this.background = props.background as Color
|
|
127
145
|
if (props.lineNumberColor !== undefined) this.lineNumberColor = props.lineNumberColor as Color
|
|
128
146
|
if (props.lineNumberBackground !== undefined) this.lineNumberBackground = props.lineNumberBackground as Color
|
|
147
|
+
if (invalidate) this.prepared = false
|
|
129
148
|
}
|
|
130
149
|
}
|
package/src/hosts/spacer.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CellBuffer, Palette } from "@effect-tui/core"
|
|
2
|
-
import type { CommonProps, HostContext,
|
|
2
|
+
import type { CommonProps, HostContext, Size } from "../reconciler/types.js"
|
|
3
3
|
import { BaseHost } from "./base.js"
|
|
4
4
|
|
|
5
5
|
export interface SpacerProps extends CommonProps {
|
|
@@ -26,10 +26,6 @@ export class SpacerHost extends BaseHost {
|
|
|
26
26
|
return { w: this.minWidth, h: this.minHeight }
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
override layout(rect: Rect): void {
|
|
30
|
-
super.layout(rect)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
29
|
render(_buffer: CellBuffer, _palette: Palette): void {
|
|
34
30
|
// Spacers render nothing
|
|
35
31
|
}
|
|
@@ -57,9 +57,6 @@ const hostConfig = {
|
|
|
57
57
|
supportsHydration: false,
|
|
58
58
|
|
|
59
59
|
createInstance(type: Type, props: Props, rootContainer: Container) {
|
|
60
|
-
if (type === "vstack") {
|
|
61
|
-
console.log("createInstance vstack props", props)
|
|
62
|
-
}
|
|
63
60
|
const instance = createHostInstance(type, props, rootContainer.ctx)
|
|
64
61
|
|
|
65
62
|
// Track static content (from <Static> component)
|
|
@@ -67,7 +64,6 @@ const hostConfig = {
|
|
|
67
64
|
instance.__static = true
|
|
68
65
|
rootContainer.staticRoot = instance
|
|
69
66
|
rootContainer.staticDirty = true
|
|
70
|
-
console.log("set staticRoot", Boolean(rootContainer.staticRoot))
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
return instance
|
|
@@ -98,9 +94,6 @@ const hostConfig = {
|
|
|
98
94
|
},
|
|
99
95
|
|
|
100
96
|
removeChildFromContainer(container: Container, child: Instance) {
|
|
101
|
-
if (child.__static) {
|
|
102
|
-
console.log("removeChildFromContainer static")
|
|
103
|
-
}
|
|
104
97
|
if (container.root) {
|
|
105
98
|
container.root.removeChild(child)
|
|
106
99
|
child.destroy()
|
package/src/renderer.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { performance } from "node:perf_hooks"
|
|
|
2
2
|
import { fileURLToPath } from "node:url"
|
|
3
3
|
import { ANSI, bufferToString, type KeyMsg, type MouseMsg } from "@effect-tui/core"
|
|
4
4
|
import React, { type ReactNode } from "react"
|
|
5
|
-
import { createTerminalWriter } from "./console/ConsoleCapture.js"
|
|
5
|
+
import { createTerminalWriter, writeToTerminal } from "./console/ConsoleCapture.js"
|
|
6
6
|
import { DEFAULT_FPS } from "./constants.js"
|
|
7
7
|
import { requestExit } from "./exit.js"
|
|
8
8
|
import * as Prof from "./profiler.js"
|
|
@@ -272,6 +272,8 @@ export function createRenderer(options?: RendererOptions): TuiRenderer {
|
|
|
272
272
|
|
|
273
273
|
let onExit: (() => void) | null = null
|
|
274
274
|
let onSignal: ((signal: NodeJS.Signals) => void) | null = null
|
|
275
|
+
let onUncaughtException: ((err: Error) => void) | null = null
|
|
276
|
+
let onUnhandledRejection: ((reason: unknown) => void) | null = null
|
|
275
277
|
|
|
276
278
|
// Build renderer object
|
|
277
279
|
const renderer: TuiRenderer = {
|
|
@@ -300,6 +302,14 @@ export function createRenderer(options?: RendererOptions): TuiRenderer {
|
|
|
300
302
|
process.off("SIGTERM", onSignal)
|
|
301
303
|
onSignal = null
|
|
302
304
|
}
|
|
305
|
+
if (onUncaughtException) {
|
|
306
|
+
process.off("uncaughtException", onUncaughtException)
|
|
307
|
+
onUncaughtException = null
|
|
308
|
+
}
|
|
309
|
+
if (onUnhandledRejection) {
|
|
310
|
+
process.off("unhandledRejection", onUnhandledRejection)
|
|
311
|
+
onUnhandledRejection = null
|
|
312
|
+
}
|
|
303
313
|
if (state.loop) {
|
|
304
314
|
clearInterval(state.loop)
|
|
305
315
|
state.loop = null
|
|
@@ -333,6 +343,7 @@ export function createRenderer(options?: RendererOptions): TuiRenderer {
|
|
|
333
343
|
if (!manualMode) renderFrame()
|
|
334
344
|
},
|
|
335
345
|
dispatchResize(width: number, height: number) {
|
|
346
|
+
renderMode.handleResize(width, height, state.lastWidth)
|
|
336
347
|
state.updateDimensions(width, height)
|
|
337
348
|
state.invalidateBuffers()
|
|
338
349
|
state.markDirty()
|
|
@@ -403,6 +414,24 @@ export function createRenderer(options?: RendererOptions): TuiRenderer {
|
|
|
403
414
|
}
|
|
404
415
|
process.on("SIGINT", onSignal)
|
|
405
416
|
process.on("SIGTERM", onSignal)
|
|
417
|
+
|
|
418
|
+
// Handle uncaught exceptions - ensure error is visible before exit
|
|
419
|
+
onUncaughtException = (err: Error) => {
|
|
420
|
+
cleanup()
|
|
421
|
+
// Write directly to terminal, bypassing console capture
|
|
422
|
+
writeToTerminal(`\n[effect-tui] Uncaught exception:\n${err.stack || err.message}\n`)
|
|
423
|
+
process.exit(1)
|
|
424
|
+
}
|
|
425
|
+
process.on("uncaughtException", onUncaughtException)
|
|
426
|
+
|
|
427
|
+
// Handle unhandled promise rejections
|
|
428
|
+
onUnhandledRejection = (reason: unknown) => {
|
|
429
|
+
cleanup()
|
|
430
|
+
const message = reason instanceof Error ? reason.stack || reason.message : String(reason)
|
|
431
|
+
writeToTerminal(`\n[effect-tui] Unhandled rejection:\n${message}\n`)
|
|
432
|
+
process.exit(1)
|
|
433
|
+
}
|
|
434
|
+
process.on("unhandledRejection", onUnhandledRejection)
|
|
406
435
|
}
|
|
407
436
|
|
|
408
437
|
;(renderer as TuiRendererInternal)._container = null
|