@effect-tui/react 0.1.5 → 0.1.7
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/dev.js +2 -2
- package/dist/src/dev.js.map +1 -1
- package/dist/src/hosts/base.d.ts +5 -0
- package/dist/src/hosts/base.d.ts.map +1 -1
- package/dist/src/hosts/base.js +21 -7
- package/dist/src/hosts/base.js.map +1 -1
- package/dist/src/hosts/box.d.ts.map +1 -1
- package/dist/src/hosts/box.js +3 -5
- package/dist/src/hosts/box.js.map +1 -1
- package/dist/src/hosts/overlay-item.d.ts +2 -2
- package/dist/src/hosts/overlay-item.d.ts.map +1 -1
- package/dist/src/hosts/overlay-item.js +7 -22
- package/dist/src/hosts/overlay-item.js.map +1 -1
- package/dist/src/hosts/overlay.d.ts.map +1 -1
- package/dist/src/hosts/overlay.js +3 -35
- package/dist/src/hosts/overlay.js.map +1 -1
- package/dist/src/hosts/scroll.d.ts.map +1 -1
- package/dist/src/hosts/scroll.js +3 -5
- package/dist/src/hosts/scroll.js.map +1 -1
- package/dist/src/hosts/text.js +2 -2
- package/dist/src/hosts/text.js.map +1 -1
- package/dist/src/hosts/zstack.d.ts +3 -2
- package/dist/src/hosts/zstack.d.ts.map +1 -1
- package/dist/src/hosts/zstack.js +3 -18
- package/dist/src/hosts/zstack.js.map +1 -1
- package/dist/src/remote/Procedures.d.ts +4 -0
- package/dist/src/remote/Procedures.d.ts.map +1 -1
- package/dist/src/remote/Procedures.js +4 -0
- package/dist/src/remote/Procedures.js.map +1 -1
- package/dist/src/remote/Router.d.ts +2 -0
- package/dist/src/remote/Router.d.ts.map +1 -1
- package/dist/src/remote/Router.js.map +1 -1
- package/dist/src/remote/index.d.ts +8 -2
- package/dist/src/remote/index.d.ts.map +1 -1
- package/dist/src/remote/index.js +31 -3
- package/dist/src/remote/index.js.map +1 -1
- package/dist/src/renderer/modes/FullscreenRenderer.d.ts.map +1 -1
- package/dist/src/renderer/modes/FullscreenRenderer.js +8 -6
- package/dist/src/renderer/modes/FullscreenRenderer.js.map +1 -1
- package/dist/src/renderer/modes/InlineRenderer.d.ts.map +1 -1
- package/dist/src/renderer/modes/InlineRenderer.js +9 -7
- package/dist/src/renderer/modes/InlineRenderer.js.map +1 -1
- package/dist/src/utils/alignment.d.ts +15 -0
- package/dist/src/utils/alignment.d.ts.map +1 -0
- package/dist/src/utils/alignment.js +37 -0
- package/dist/src/utils/alignment.js.map +1 -0
- package/dist/src/utils/index.d.ts +2 -1
- package/dist/src/utils/index.d.ts.map +1 -1
- package/dist/src/utils/index.js +2 -1
- package/dist/src/utils/index.js.map +1 -1
- package/dist/src/utils/styles.d.ts +9 -0
- package/dist/src/utils/styles.d.ts.map +1 -1
- package/dist/src/utils/styles.js +9 -0
- package/dist/src/utils/styles.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/dev.tsx +2 -2
- package/src/hosts/base.ts +21 -7
- package/src/hosts/box.ts +3 -6
- package/src/hosts/overlay-item.ts +7 -22
- package/src/hosts/overlay.ts +3 -37
- package/src/hosts/scroll.ts +3 -5
- package/src/hosts/text.ts +3 -3
- package/src/hosts/zstack.ts +5 -18
- package/src/remote/Procedures.ts +4 -0
- package/src/remote/Router.ts +7 -1
- package/src/remote/index.ts +41 -3
- package/src/renderer/modes/FullscreenRenderer.ts +8 -6
- package/src/renderer/modes/InlineRenderer.ts +9 -7
- package/src/utils/alignment.ts +50 -0
- package/src/utils/index.ts +10 -1
- package/src/utils/styles.ts +16 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@effect-tui/react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
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.1.
|
|
86
|
+
"@effect-tui/core": "^0.1.7",
|
|
87
87
|
"@effect/platform": "^0.94.0",
|
|
88
88
|
"@effect/platform-bun": "^0.87.0",
|
|
89
89
|
"@effect/rpc": "^0.73.0",
|
package/src/dev.tsx
CHANGED
|
@@ -395,8 +395,8 @@ export async function devRender(entryPath: string, options?: DevRenderOptions):
|
|
|
395
395
|
const renderer = createRenderer(options)
|
|
396
396
|
const root = createRoot(renderer)
|
|
397
397
|
|
|
398
|
-
// Dev mode always enables remote control
|
|
399
|
-
const stopRemote = enableRemote(renderer)
|
|
398
|
+
// Dev mode always enables remote control with entry path for identification
|
|
399
|
+
const stopRemote = enableRemote(renderer, { entryPath })
|
|
400
400
|
|
|
401
401
|
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
|
402
402
|
let version = 0
|
package/src/hosts/base.ts
CHANGED
|
@@ -147,13 +147,13 @@ export abstract class BaseHost implements HostInstance {
|
|
|
147
147
|
this.flexGrow = (props.flexGrow as number | undefined) ?? 0
|
|
148
148
|
this.flexShrink = (props.flexShrink as number | undefined) ?? 1
|
|
149
149
|
|
|
150
|
-
// Frame constraints
|
|
151
|
-
this.frameWidth = props.width
|
|
152
|
-
this.frameHeight = props.height
|
|
153
|
-
this.frameMinWidth = props.minWidth
|
|
154
|
-
this.frameMaxWidth = props.maxWidth
|
|
155
|
-
this.frameMinHeight = props.minHeight
|
|
156
|
-
this.frameMaxHeight = props.maxHeight
|
|
150
|
+
// Frame constraints - only accept valid numbers (ignore strings like "100%")
|
|
151
|
+
this.frameWidth = typeof props.width === "number" ? props.width : undefined
|
|
152
|
+
this.frameHeight = typeof props.height === "number" ? props.height : undefined
|
|
153
|
+
this.frameMinWidth = typeof props.minWidth === "number" ? props.minWidth : undefined
|
|
154
|
+
this.frameMaxWidth = typeof props.maxWidth === "number" ? props.maxWidth : undefined
|
|
155
|
+
this.frameMinHeight = typeof props.minHeight === "number" ? props.minHeight : undefined
|
|
156
|
+
this.frameMaxHeight = typeof props.maxHeight === "number" ? props.maxHeight : undefined
|
|
157
157
|
}
|
|
158
158
|
|
|
159
159
|
destroy(): void {
|
|
@@ -183,4 +183,18 @@ export abstract class BaseHost implements HostInstance {
|
|
|
183
183
|
}
|
|
184
184
|
child.parent = this
|
|
185
185
|
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Render a child with optional clipping to its rect.
|
|
189
|
+
* Common pattern used by containers (ZStack, Overlay, etc.)
|
|
190
|
+
*/
|
|
191
|
+
protected renderChildWithClip(child: HostInstance, buffer: CellBuffer, palette: Palette): void {
|
|
192
|
+
if (child.rect) {
|
|
193
|
+
buffer.withClip(child.rect.x, child.rect.y, child.rect.w, child.rect.h, () => {
|
|
194
|
+
child.render(buffer, palette)
|
|
195
|
+
})
|
|
196
|
+
} else {
|
|
197
|
+
child.render(buffer, palette)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
186
200
|
}
|
package/src/hosts/box.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { CellBuffer, Palette, Color } from "@effect-tui/core"
|
|
2
2
|
import { Colors } from "@effect-tui/core"
|
|
3
3
|
import type { HostContext, Rect, Size, CommonProps } from "../reconciler/types.js"
|
|
4
|
-
import { getInheritedBg } from "./base.js"
|
|
5
4
|
import { SingleChildHost } from "./single-child.js"
|
|
6
5
|
import {
|
|
7
6
|
type BorderKind,
|
|
@@ -10,7 +9,7 @@ import {
|
|
|
10
9
|
type Padding,
|
|
11
10
|
type PaddingInput,
|
|
12
11
|
resolvePadding,
|
|
13
|
-
|
|
12
|
+
resolveInheritedBgStyle,
|
|
14
13
|
toColorValue,
|
|
15
14
|
} from "../utils/index.js"
|
|
16
15
|
|
|
@@ -94,11 +93,9 @@ export class BoxHost extends SingleChildHost {
|
|
|
94
93
|
if (!this.rect) return
|
|
95
94
|
const { x, y, w, h } = this.rect
|
|
96
95
|
|
|
97
|
-
// If no explicit bg, inherit from parent (so child boxes are transparent over parents)
|
|
98
|
-
const rawBg: Color | undefined = this.bg ?? getInheritedBg(this.parent)
|
|
99
|
-
|
|
100
96
|
// Always clear our rect to ensure stale backgrounds are removed.
|
|
101
|
-
|
|
97
|
+
// If no explicit bg, inherit from parent (so child boxes are transparent over parents)
|
|
98
|
+
const { styleId: bgStyleId } = resolveInheritedBgStyle(palette, this.bg, this.parent)
|
|
102
99
|
buffer.fillRect(x, y, w, h, " ".codePointAt(0)!, bgStyleId)
|
|
103
100
|
|
|
104
101
|
// Draw border
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import type { CellBuffer, Palette, Rect, Size } from "@effect-tui/core"
|
|
9
9
|
import type { HostContext, CommonProps } from "../reconciler/types.js"
|
|
10
|
-
import {
|
|
10
|
+
import { SingleChildHost } from "./single-child.js"
|
|
11
11
|
|
|
12
12
|
type HAlign = "left" | "center" | "right"
|
|
13
13
|
type VAlign = "top" | "center" | "bottom"
|
|
@@ -17,7 +17,7 @@ export interface OverlayItemProps extends CommonProps {
|
|
|
17
17
|
alignment?: { h?: HAlign; v?: VAlign }
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
export class OverlayItemHost extends
|
|
20
|
+
export class OverlayItemHost extends SingleChildHost {
|
|
21
21
|
/** Stored alignment for parent Overlay to read */
|
|
22
22
|
alignment: { h?: HAlign; v?: VAlign } = {}
|
|
23
23
|
|
|
@@ -29,37 +29,22 @@ export class OverlayItemHost extends BaseHost {
|
|
|
29
29
|
override measure(maxW: number, maxH: number): Size {
|
|
30
30
|
const constrained = this.constrainProposal(maxW, maxH)
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
const child = this.children[0]
|
|
34
|
-
if (!child) {
|
|
32
|
+
if (!this.child) {
|
|
35
33
|
return this.constrainResult({ w: 0, h: 0 })
|
|
36
34
|
}
|
|
37
35
|
|
|
38
|
-
const childSize = child.measure(constrained.w, constrained.h)
|
|
36
|
+
const childSize = this.child.measure(constrained.w, constrained.h)
|
|
39
37
|
return this.constrainResult(childSize)
|
|
40
38
|
}
|
|
41
39
|
|
|
42
40
|
override layout(rect: Rect): void {
|
|
43
41
|
this.rect = rect
|
|
44
|
-
|
|
45
|
-
// Pass rect to child
|
|
46
|
-
const child = this.children[0]
|
|
47
|
-
if (child) {
|
|
48
|
-
child.layout(rect)
|
|
49
|
-
}
|
|
42
|
+
this.child?.layout(rect)
|
|
50
43
|
}
|
|
51
44
|
|
|
52
45
|
override render(buffer: CellBuffer, palette: Palette): void {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (child) {
|
|
56
|
-
if (child.rect) {
|
|
57
|
-
buffer.withClip(child.rect.x, child.rect.y, child.rect.w, child.rect.h, () => {
|
|
58
|
-
child.render(buffer, palette)
|
|
59
|
-
})
|
|
60
|
-
} else {
|
|
61
|
-
child.render(buffer, palette)
|
|
62
|
-
}
|
|
46
|
+
if (this.child) {
|
|
47
|
+
this.renderChildWithClip(this.child, buffer, palette)
|
|
63
48
|
}
|
|
64
49
|
}
|
|
65
50
|
|
package/src/hosts/overlay.ts
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import type { CellBuffer, Palette, Rect, Size } from "@effect-tui/core"
|
|
20
20
|
import type { HostContext, CommonProps } from "../reconciler/types.js"
|
|
21
21
|
import { BaseHost } from "./base.js"
|
|
22
|
+
import { alignInRect } from "../utils/index.js"
|
|
22
23
|
import type { OverlayItemHost } from "./overlay-item.js"
|
|
23
24
|
|
|
24
25
|
export interface OverlayProps extends CommonProps {}
|
|
@@ -70,36 +71,7 @@ export class OverlayHost extends BaseHost {
|
|
|
70
71
|
|
|
71
72
|
// Read alignment from OverlayItemHost (use type check, not instanceof, for bundler compatibility)
|
|
72
73
|
const alignment = child.type === "overlayItem" ? (child as OverlayItemHost).alignment : {}
|
|
73
|
-
const
|
|
74
|
-
const vAlign = alignment.v ?? "center"
|
|
75
|
-
|
|
76
|
-
// Calculate position based on alignment
|
|
77
|
-
let x = rect.x
|
|
78
|
-
let y = rect.y
|
|
79
|
-
|
|
80
|
-
switch (hAlign) {
|
|
81
|
-
case "left":
|
|
82
|
-
x = rect.x
|
|
83
|
-
break
|
|
84
|
-
case "center":
|
|
85
|
-
x = rect.x + Math.floor((rect.w - size.w) / 2)
|
|
86
|
-
break
|
|
87
|
-
case "right":
|
|
88
|
-
x = rect.x + rect.w - size.w
|
|
89
|
-
break
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
switch (vAlign) {
|
|
93
|
-
case "top":
|
|
94
|
-
y = rect.y
|
|
95
|
-
break
|
|
96
|
-
case "center":
|
|
97
|
-
y = rect.y + Math.floor((rect.h - size.h) / 2)
|
|
98
|
-
break
|
|
99
|
-
case "bottom":
|
|
100
|
-
y = rect.y + rect.h - size.h
|
|
101
|
-
break
|
|
102
|
-
}
|
|
74
|
+
const { x, y } = alignInRect(rect, size, alignment.h ?? "center", alignment.v ?? "center")
|
|
103
75
|
|
|
104
76
|
child.layout({
|
|
105
77
|
x,
|
|
@@ -113,13 +85,7 @@ export class OverlayHost extends BaseHost {
|
|
|
113
85
|
override render(buffer: CellBuffer, palette: Palette): void {
|
|
114
86
|
// Render all children in order (base first, then overlays)
|
|
115
87
|
for (const child of this.children) {
|
|
116
|
-
|
|
117
|
-
buffer.withClip(child.rect.x, child.rect.y, child.rect.w, child.rect.h, () => {
|
|
118
|
-
child.render(buffer, palette)
|
|
119
|
-
})
|
|
120
|
-
} else {
|
|
121
|
-
child.render(buffer, palette)
|
|
122
|
-
}
|
|
88
|
+
this.renderChildWithClip(child, buffer, palette)
|
|
123
89
|
}
|
|
124
90
|
}
|
|
125
91
|
}
|
package/src/hosts/scroll.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// scroll.ts — Scrollable container host
|
|
2
2
|
import type { CellBuffer, Palette, Color } from "@effect-tui/core"
|
|
3
3
|
import type { HostContext, Rect, Size, CommonProps } from "../reconciler/types.js"
|
|
4
|
-
import { getInheritedBg } from "./base.js"
|
|
5
4
|
import { SingleChildHost } from "./single-child.js"
|
|
6
|
-
import {
|
|
5
|
+
import { resolveInheritedBgStyle } from "../utils/index.js"
|
|
7
6
|
|
|
8
7
|
export interface ScrollProps extends CommonProps {
|
|
9
8
|
/** Scroll axis: "vertical" (default), "horizontal", or "both" */
|
|
@@ -175,9 +174,8 @@ export class ScrollHost extends SingleChildHost {
|
|
|
175
174
|
if (!this.rect) return
|
|
176
175
|
const { x, y, w, h } = this.rect
|
|
177
176
|
|
|
178
|
-
// Fill background
|
|
179
|
-
const
|
|
180
|
-
const { value: bgValue, styleId: bgStyleId } = resolveBgStyle(palette, rawBg)
|
|
177
|
+
// Fill background (inherit from parent if not explicitly set)
|
|
178
|
+
const { value: bgValue, styleId: bgStyleId } = resolveInheritedBgStyle(palette, this.bg, this.parent)
|
|
181
179
|
if (bgValue !== undefined) {
|
|
182
180
|
buffer.fillRect(x, y, w, h, " ".codePointAt(0)!, bgStyleId)
|
|
183
181
|
}
|
package/src/hosts/text.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { displayWidth, type CellBuffer, type Palette, type Color } from "@effect-tui/core"
|
|
2
2
|
import type { HostContext, Rect, Size, CommonProps } from "../reconciler/types.js"
|
|
3
3
|
import { BaseHost, getInheritedBg } from "./base.js"
|
|
4
|
-
import {
|
|
4
|
+
import { resolveInheritedBgStyle, styleIdFromProps } from "../utils/index.js"
|
|
5
5
|
|
|
6
6
|
export interface TextProps extends CommonProps {
|
|
7
7
|
fg?: Color
|
|
@@ -114,8 +114,8 @@ export class TextHost extends BaseHost {
|
|
|
114
114
|
if (!this.rect) return
|
|
115
115
|
|
|
116
116
|
// If text has no bg, inherit from parent box for proper highlight rendering
|
|
117
|
-
const
|
|
118
|
-
const
|
|
117
|
+
const { value: bgValue, styleId: bgStyleId } = resolveInheritedBgStyle(palette, this.bg, this.parent)
|
|
118
|
+
const inheritedBg = this.bg ?? getInheritedBg(this.parent)
|
|
119
119
|
|
|
120
120
|
const styleId = styleIdFromProps(palette, {
|
|
121
121
|
fg: this.fg,
|
package/src/hosts/zstack.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CellBuffer, Palette } from "@effect-tui/core"
|
|
2
2
|
import type { HostContext, Rect, Size, CommonProps } from "../reconciler/types.js"
|
|
3
3
|
import { BaseHost } from "./base.js"
|
|
4
|
+
import { alignInRect, type HAlign, type VAlign } from "../utils/index.js"
|
|
4
5
|
|
|
5
6
|
export interface ZStackProps extends CommonProps {
|
|
6
7
|
alignment?: { h?: "leading" | "center" | "trailing"; v?: "top" | "center" | "bottom" }
|
|
@@ -8,8 +9,8 @@ export interface ZStackProps extends CommonProps {
|
|
|
8
9
|
|
|
9
10
|
// Overlay children in the same rect, honoring alignment for each child.
|
|
10
11
|
export class ZStackHost extends BaseHost {
|
|
11
|
-
alignmentH:
|
|
12
|
-
alignmentV:
|
|
12
|
+
alignmentH: HAlign = "center"
|
|
13
|
+
alignmentV: VAlign = "center"
|
|
13
14
|
private cachedSizes: Size[] = []
|
|
14
15
|
|
|
15
16
|
constructor(props: ZStackProps, ctx: HostContext) {
|
|
@@ -47,15 +48,7 @@ export class ZStackHost extends BaseHost {
|
|
|
47
48
|
for (let i = 0; i < this.children.length; i++) {
|
|
48
49
|
const child = this.children[i]
|
|
49
50
|
const size = this.cachedSizes[i] ?? child.measure(rect.w, rect.h)
|
|
50
|
-
|
|
51
|
-
let x = rect.x
|
|
52
|
-
let y = rect.y
|
|
53
|
-
|
|
54
|
-
if (this.alignmentH === "center") x += Math.floor((rect.w - size.w) / 2)
|
|
55
|
-
else if (this.alignmentH === "trailing") x += Math.max(0, rect.w - size.w)
|
|
56
|
-
|
|
57
|
-
if (this.alignmentV === "center") y += Math.floor((rect.h - size.h) / 2)
|
|
58
|
-
else if (this.alignmentV === "bottom") y += Math.max(0, rect.h - size.h)
|
|
51
|
+
const { x, y } = alignInRect(rect, size, this.alignmentH, this.alignmentV)
|
|
59
52
|
|
|
60
53
|
child.layout({
|
|
61
54
|
x,
|
|
@@ -68,13 +61,7 @@ export class ZStackHost extends BaseHost {
|
|
|
68
61
|
|
|
69
62
|
render(buffer: CellBuffer, palette: Palette): void {
|
|
70
63
|
for (const child of this.children) {
|
|
71
|
-
|
|
72
|
-
buffer.withClip(child.rect.x, child.rect.y, child.rect.w, child.rect.h, () => {
|
|
73
|
-
child.render(buffer, palette)
|
|
74
|
-
})
|
|
75
|
-
} else {
|
|
76
|
-
child.render(buffer, palette)
|
|
77
|
-
}
|
|
64
|
+
this.renderChildWithClip(child, buffer, palette)
|
|
78
65
|
}
|
|
79
66
|
}
|
|
80
67
|
|
package/src/remote/Procedures.ts
CHANGED
|
@@ -39,6 +39,10 @@ const Info = Rpc.make("Info", {
|
|
|
39
39
|
pid: Schema.Number,
|
|
40
40
|
width: Schema.Number,
|
|
41
41
|
height: Schema.Number,
|
|
42
|
+
/** Entry file path (e.g., /Users/kit/code/my-app/src/tui.tsx) */
|
|
43
|
+
entryPath: Schema.optional(Schema.String),
|
|
44
|
+
/** Short name derived from entry path (e.g., my-app/src/tui.tsx) */
|
|
45
|
+
name: Schema.optional(Schema.String),
|
|
42
46
|
}),
|
|
43
47
|
})
|
|
44
48
|
|
package/src/remote/Router.ts
CHANGED
|
@@ -10,7 +10,13 @@ export interface TuiSessionImpl {
|
|
|
10
10
|
readonly dispatchKey: (key: KeyMsg) => void
|
|
11
11
|
readonly dispatchPaste: (text: string) => void
|
|
12
12
|
readonly dispatchResize: (width: number, height: number) => void
|
|
13
|
-
readonly getInfo: () => {
|
|
13
|
+
readonly getInfo: () => {
|
|
14
|
+
pid: number
|
|
15
|
+
width: number
|
|
16
|
+
height: number
|
|
17
|
+
entryPath?: string
|
|
18
|
+
name?: string
|
|
19
|
+
}
|
|
14
20
|
}
|
|
15
21
|
|
|
16
22
|
export class TuiSession extends Context.Tag("TuiSession")<
|
package/src/remote/index.ts
CHANGED
|
@@ -17,19 +17,55 @@ import type { TuiRenderer } from "../renderer-types.js"
|
|
|
17
17
|
import type { TuiSessionImpl } from "./Router.js"
|
|
18
18
|
import { makeServerLayer, getSocketPath } from "./Server.js"
|
|
19
19
|
|
|
20
|
+
export interface EnableRemoteOptions {
|
|
21
|
+
/** Custom socket path (defaults to /tmp/effect-tui-sessions/<pid>.sock) */
|
|
22
|
+
socketPath?: string
|
|
23
|
+
/** Entry file path for identification (e.g., import.meta.path) */
|
|
24
|
+
entryPath?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Derive a short name from the entry path.
|
|
29
|
+
* Uses the last directory + filename (e.g., "/Users/kit/code/my-app/src/tui.tsx" -> "my-app/src/tui.tsx")
|
|
30
|
+
*/
|
|
31
|
+
function deriveSessionName(entryPath: string): string {
|
|
32
|
+
const parts = entryPath.split("/")
|
|
33
|
+
// Find the last directory that looks like a project name (not src, lib, etc)
|
|
34
|
+
const commonDirs = ["src", "lib", "dist", "build", "packages"]
|
|
35
|
+
let srcIdx = -1
|
|
36
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
37
|
+
if (commonDirs.includes(parts[i])) {
|
|
38
|
+
srcIdx = i
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (srcIdx > 0) {
|
|
43
|
+
// Include project name + relative path from there
|
|
44
|
+
return parts.slice(srcIdx - 1).join("/")
|
|
45
|
+
}
|
|
46
|
+
// Fallback: last 3 path segments
|
|
47
|
+
return parts.slice(-3).join("/")
|
|
48
|
+
}
|
|
49
|
+
|
|
20
50
|
/**
|
|
21
51
|
* Enable remote control for a TUI renderer.
|
|
22
52
|
* Starts an RPC server on a Unix socket at /tmp/effect-tui-sessions/<pid>.sock
|
|
23
53
|
*
|
|
24
54
|
* @param renderer - The TUI renderer to control
|
|
25
|
-
* @param
|
|
55
|
+
* @param options - Socket path and entry path for session identification
|
|
26
56
|
* @returns A cleanup function to stop the server
|
|
27
57
|
*/
|
|
28
58
|
export function enableRemote(
|
|
29
59
|
renderer: TuiRenderer,
|
|
30
|
-
|
|
60
|
+
options?: EnableRemoteOptions | string,
|
|
31
61
|
): () => void {
|
|
32
|
-
|
|
62
|
+
// Support old API: enableRemote(renderer, socketPath?)
|
|
63
|
+
const opts: EnableRemoteOptions =
|
|
64
|
+
typeof options === "string" ? { socketPath: options } : options ?? {}
|
|
65
|
+
const actualPath = opts.socketPath ?? getSocketPath()
|
|
66
|
+
|
|
67
|
+
// Derive session name from entry path
|
|
68
|
+
const name = opts.entryPath ? deriveSessionName(opts.entryPath) : undefined
|
|
33
69
|
|
|
34
70
|
// Create session implementation from renderer
|
|
35
71
|
const session: TuiSessionImpl = {
|
|
@@ -41,6 +77,8 @@ export function enableRemote(
|
|
|
41
77
|
pid: process.pid,
|
|
42
78
|
width: renderer.width,
|
|
43
79
|
height: renderer.height,
|
|
80
|
+
entryPath: opts.entryPath,
|
|
81
|
+
name,
|
|
44
82
|
}),
|
|
45
83
|
}
|
|
46
84
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ANSI, emitRowWithReset,
|
|
1
|
+
import { ANSI, emitRowWithReset, rowChanged } from "@effect-tui/core"
|
|
2
2
|
import type { RendererMode, RenderContext, RenderOutput } from "./RendererMode.js"
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -15,20 +15,22 @@ export class FullscreenRenderer implements RendererMode {
|
|
|
15
15
|
// If a full clear was requested (e.g., on resize), prepend it to output
|
|
16
16
|
// so clear + content are written atomically (no visible flash)
|
|
17
17
|
if (this.needsFullClear) {
|
|
18
|
-
output += ANSI.cursor.to(1, 1)
|
|
18
|
+
output += ANSI.screen.clear + ANSI.cursor.to(1, 1)
|
|
19
19
|
this.needsFullClear = false
|
|
20
20
|
// After clear, we must redraw everything - can't rely on diff
|
|
21
21
|
for (let y = 0; y < frameHeight; y++) {
|
|
22
22
|
if (y > 0) output += ANSI.cursor.to(1, y + 1)
|
|
23
|
+
output += palette.sgr(0) + ANSI.line.clear
|
|
23
24
|
output += emitRowWithReset(nextBuffer, palette, y, frameWidth)
|
|
24
25
|
}
|
|
25
26
|
} else if (enableDiff && prevBuffer) {
|
|
26
27
|
// Diff per line for minimal writes
|
|
27
28
|
for (let y = 0; y < frameHeight; y++) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
output +=
|
|
29
|
+
if (!rowChanged(prevBuffer, nextBuffer, y, frameWidth)) continue
|
|
30
|
+
output += ANSI.cursor.to(1, y + 1)
|
|
31
|
+
// Clear the row using default style to avoid carrying stale backgrounds
|
|
32
|
+
output += palette.sgr(0) + ANSI.line.clear
|
|
33
|
+
output += emitRowWithReset(nextBuffer, palette, y, frameWidth)
|
|
32
34
|
}
|
|
33
35
|
} else {
|
|
34
36
|
// Full redraw (tests/manual mode)
|
|
@@ -32,6 +32,7 @@ export class InlineRenderer implements RendererMode {
|
|
|
32
32
|
output += ANSI.cursor.up(this.previousHeight)
|
|
33
33
|
}
|
|
34
34
|
output += ANSI.cursor.startOfLine
|
|
35
|
+
output += ANSI.screen.clearToEnd
|
|
35
36
|
this.previousStartRow = startRow
|
|
36
37
|
this.printedWidths = []
|
|
37
38
|
} else if (this.previousHeight > 0) {
|
|
@@ -80,7 +81,7 @@ export class InlineRenderer implements RendererMode {
|
|
|
80
81
|
const prevW = this.printedWidths[screenY] ?? 0
|
|
81
82
|
if (prevW > 0) {
|
|
82
83
|
moveToRow(screenY)
|
|
83
|
-
output += ANSI.cursor.toCol(1) + palette.sgr(0) +
|
|
84
|
+
output += ANSI.cursor.toCol(1) + palette.sgr(0) + ANSI.line.clear
|
|
84
85
|
this.printedWidths[screenY] = 0
|
|
85
86
|
}
|
|
86
87
|
continue
|
|
@@ -94,7 +95,7 @@ export class InlineRenderer implements RendererMode {
|
|
|
94
95
|
// No change; maybe need to clear tail if content shrunk
|
|
95
96
|
if (prevW > newW) {
|
|
96
97
|
moveToRow(screenY)
|
|
97
|
-
output += ANSI.cursor.toCol(newW + 1) + palette.sgr(0) +
|
|
98
|
+
output += ANSI.cursor.toCol(newW + 1) + palette.sgr(0) + ANSI.line.clearToEnd
|
|
98
99
|
this.printedWidths[screenY] = newW
|
|
99
100
|
}
|
|
100
101
|
continue
|
|
@@ -108,9 +109,9 @@ export class InlineRenderer implements RendererMode {
|
|
|
108
109
|
// Clear tail if shrunk
|
|
109
110
|
const effectiveW = Math.max(newW, change.right + 1)
|
|
110
111
|
if (prevW > effectiveW) {
|
|
111
|
-
output += ANSI.cursor.toCol(effectiveW + 1) +
|
|
112
|
+
output += ANSI.cursor.toCol(effectiveW + 1) + ANSI.line.clearToEnd
|
|
112
113
|
}
|
|
113
|
-
this.printedWidths[screenY] =
|
|
114
|
+
this.printedWidths[screenY] = effectiveW
|
|
114
115
|
}
|
|
115
116
|
|
|
116
117
|
// Ensure cursor ends just after the dynamic block for next frame positioning
|
|
@@ -128,8 +129,9 @@ export class InlineRenderer implements RendererMode {
|
|
|
128
129
|
for (let bufferY = startRow; bufferY < endRow; bufferY++) {
|
|
129
130
|
const screenY = bufferY - startRow
|
|
130
131
|
const trimmedWidth = rowContentWidth(nextBuffer, bufferY, frameWidth)
|
|
131
|
-
//
|
|
132
|
-
output +=
|
|
132
|
+
// Clear ENTIRE line first to handle resize artifacts
|
|
133
|
+
output += ANSI.line.clear
|
|
134
|
+
output += emitRowWithReset(nextBuffer, palette, bufferY, frameWidth, 0, trimmedWidth)
|
|
133
135
|
output += "\r\n"
|
|
134
136
|
// Track line widths for resize reflow calculation
|
|
135
137
|
this.printedWidths[screenY] = trimmedWidth
|
|
@@ -137,7 +139,7 @@ export class InlineRenderer implements RendererMode {
|
|
|
137
139
|
|
|
138
140
|
// Clear any extra lines if content shrank
|
|
139
141
|
for (let screenY = rowCount; screenY < this.previousHeight; screenY++) {
|
|
140
|
-
output += palette.sgr(0) +
|
|
142
|
+
output += palette.sgr(0) + ANSI.line.clear + "\r\n"
|
|
141
143
|
this.printedWidths[screenY] = 0
|
|
142
144
|
}
|
|
143
145
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Alignment utilities for layout positioning.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Rect, Size } from "../reconciler/types.js"
|
|
6
|
+
|
|
7
|
+
export type HAlign = "left" | "center" | "right" | "leading" | "trailing"
|
|
8
|
+
export type VAlign = "top" | "center" | "bottom"
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Calculate position to align a child size within a container rect.
|
|
12
|
+
* Returns the x,y coordinates for the child's top-left corner.
|
|
13
|
+
*/
|
|
14
|
+
export function alignInRect(
|
|
15
|
+
rect: Rect,
|
|
16
|
+
size: Size,
|
|
17
|
+
hAlign: HAlign = "center",
|
|
18
|
+
vAlign: VAlign = "center",
|
|
19
|
+
): { x: number; y: number } {
|
|
20
|
+
let x = rect.x
|
|
21
|
+
let y = rect.y
|
|
22
|
+
|
|
23
|
+
switch (hAlign) {
|
|
24
|
+
case "left":
|
|
25
|
+
case "leading":
|
|
26
|
+
x = rect.x
|
|
27
|
+
break
|
|
28
|
+
case "center":
|
|
29
|
+
x = rect.x + Math.floor((rect.w - size.w) / 2)
|
|
30
|
+
break
|
|
31
|
+
case "right":
|
|
32
|
+
case "trailing":
|
|
33
|
+
x = rect.x + Math.max(0, rect.w - size.w)
|
|
34
|
+
break
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
switch (vAlign) {
|
|
38
|
+
case "top":
|
|
39
|
+
y = rect.y
|
|
40
|
+
break
|
|
41
|
+
case "center":
|
|
42
|
+
y = rect.y + Math.floor((rect.h - size.h) / 2)
|
|
43
|
+
break
|
|
44
|
+
case "bottom":
|
|
45
|
+
y = rect.y + Math.max(0, rect.h - size.h)
|
|
46
|
+
break
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { x, y }
|
|
50
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
+
export { type HAlign, type VAlign, alignInRect } from "./alignment.js"
|
|
1
2
|
export { type BorderKind, type BorderChars, borderChars, type ClipRect, drawBorder } from "./border.js"
|
|
2
3
|
export { type Padding, type PaddingInput, resolvePadding } from "./padding.js"
|
|
3
|
-
export {
|
|
4
|
+
export {
|
|
5
|
+
type StyleOptions,
|
|
6
|
+
type StyleInput,
|
|
7
|
+
toColorValue,
|
|
8
|
+
styleSpecFromProps,
|
|
9
|
+
styleIdFromProps,
|
|
10
|
+
resolveBgStyle,
|
|
11
|
+
resolveInheritedBgStyle,
|
|
12
|
+
} from "./styles.js"
|
|
4
13
|
export { type FlexAxis, type FlexAlignment, type FlexMeasureResult, measureFlex, layoutFlex } from "./flex-layout.js"
|
package/src/utils/styles.ts
CHANGED
|
@@ -55,3 +55,19 @@ export function resolveBgStyle(palette: Palette, bg?: Color): { value?: ColorVal
|
|
|
55
55
|
const styleId = value === undefined ? 0 : palette.id({ bg: value })
|
|
56
56
|
return { value, styleId }
|
|
57
57
|
}
|
|
58
|
+
|
|
59
|
+
import type { HostInstance } from "../reconciler/types.js"
|
|
60
|
+
import { getInheritedBg } from "../hosts/base.js"
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve background style, inheriting from parent if not explicitly set.
|
|
64
|
+
* Common pattern used by hosts that need background color inheritance.
|
|
65
|
+
*/
|
|
66
|
+
export function resolveInheritedBgStyle(
|
|
67
|
+
palette: Palette,
|
|
68
|
+
explicitBg: Color | undefined,
|
|
69
|
+
parent: HostInstance | null,
|
|
70
|
+
): { value?: ColorValue; styleId: number } {
|
|
71
|
+
const bg = explicitBg ?? getInheritedBg(parent)
|
|
72
|
+
return resolveBgStyle(palette, bg)
|
|
73
|
+
}
|