@effect-tui/react 0.1.6 → 0.2.0
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/hooks/index.d.ts +1 -0
- package/dist/src/hooks/index.d.ts.map +1 -1
- package/dist/src/hooks/index.js +1 -0
- package/dist/src/hooks/index.js.map +1 -1
- package/dist/src/hooks/use-quit.d.ts +21 -0
- package/dist/src/hooks/use-quit.d.ts.map +1 -0
- package/dist/src/hooks/use-quit.js +29 -0
- package/dist/src/hooks/use-quit.js.map +1 -0
- package/dist/src/hosts/base.d.ts +6 -2
- package/dist/src/hosts/base.d.ts.map +1 -1
- package/dist/src/hosts/base.js +56 -6
- 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/flex-container.d.ts.map +1 -1
- package/dist/src/hosts/flex-container.js +4 -1
- package/dist/src/hosts/flex-container.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 +1 -0
- package/dist/src/hosts/scroll.d.ts.map +1 -1
- package/dist/src/hosts/scroll.js +14 -7
- package/dist/src/hosts/scroll.js.map +1 -1
- package/dist/src/hosts/spacer.d.ts +1 -0
- package/dist/src/hosts/spacer.d.ts.map +1 -1
- package/dist/src/hosts/spacer.js +10 -9
- package/dist/src/hosts/spacer.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/reconciler/types.d.ts +9 -4
- package/dist/src/reconciler/types.d.ts.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/input/InputProcessor.d.ts +1 -0
- package/dist/src/renderer/input/InputProcessor.d.ts.map +1 -1
- package/dist/src/renderer/input/InputProcessor.js +6 -1
- package/dist/src/renderer/input/InputProcessor.js.map +1 -1
- package/dist/src/renderer/lifecycle/TerminalSetup.d.ts +1 -0
- package/dist/src/renderer/lifecycle/TerminalSetup.d.ts.map +1 -1
- package/dist/src/renderer/lifecycle/TerminalSetup.js +26 -17
- package/dist/src/renderer/lifecycle/TerminalSetup.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/renderer.d.ts +2 -0
- package/dist/src/renderer.d.ts.map +1 -1
- package/dist/src/renderer.js +39 -7
- package/dist/src/renderer.js.map +1 -1
- package/dist/src/test/render-tui.d.ts +5 -0
- package/dist/src/test/render-tui.d.ts.map +1 -1
- package/dist/src/test/render-tui.js +3 -0
- package/dist/src/test/render-tui.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/flex-layout.d.ts.map +1 -1
- package/dist/src/utils/flex-layout.js +20 -6
- package/dist/src/utils/flex-layout.js.map +1 -1
- 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 +3 -2
- package/src/dev.tsx +2 -2
- package/src/hooks/index.ts +1 -0
- package/src/hooks/use-quit.ts +33 -0
- package/src/hosts/base.ts +50 -6
- package/src/hosts/box.ts +3 -6
- package/src/hosts/flex-container.ts +3 -1
- package/src/hosts/overlay-item.ts +7 -22
- package/src/hosts/overlay.ts +3 -37
- package/src/hosts/scroll.ts +16 -7
- package/src/hosts/spacer.ts +11 -9
- package/src/hosts/text.ts +3 -3
- package/src/hosts/zstack.ts +5 -18
- package/src/reconciler/types.ts +9 -4
- package/src/remote/Procedures.ts +4 -0
- package/src/remote/Router.ts +7 -1
- package/src/remote/index.ts +41 -3
- package/src/renderer/input/InputProcessor.ts +6 -1
- package/src/renderer/lifecycle/TerminalSetup.ts +28 -17
- package/src/renderer/modes/FullscreenRenderer.ts +8 -6
- package/src/renderer/modes/InlineRenderer.ts +9 -7
- package/src/renderer.ts +44 -6
- package/src/test/render-tui.ts +7 -0
- package/src/utils/alignment.ts +50 -0
- package/src/utils/flex-layout.ts +19 -6
- package/src/utils/index.ts +10 -1
- package/src/utils/styles.ts +16 -0
|
@@ -70,36 +70,47 @@ export class TerminalSetup {
|
|
|
70
70
|
/**
|
|
71
71
|
* Restore terminal to normal state.
|
|
72
72
|
* Call this when stopping the renderer.
|
|
73
|
+
* Uses a single write call to ensure atomic output and reliable flushing.
|
|
73
74
|
*/
|
|
74
75
|
teardown(): void {
|
|
75
76
|
if (this.config.skipTerminalSetup) return
|
|
76
77
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
this.stdout.write(ANSI.reflow.enable)
|
|
82
|
-
this.stdout.write("\r\n")
|
|
78
|
+
// Disable raw mode FIRST - this is critical for proper terminal restoration
|
|
79
|
+
// Must happen before writing escape sequences so the terminal processes them correctly
|
|
80
|
+
if (this.stdin.isTTY && this.stdin.setRawMode) {
|
|
81
|
+
this.stdin.setRawMode(false)
|
|
83
82
|
}
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
// Build all escape sequences into a single string for atomic write
|
|
85
|
+
// This ensures all sequences are flushed together before process exit
|
|
86
|
+
let output = ""
|
|
87
|
+
|
|
88
|
+
// Disable enhanced keyboard protocols first
|
|
89
|
+
if (this.config.enableKittyKeyboard !== false) {
|
|
90
|
+
output += ANSI.keyboard.disable
|
|
91
|
+
output += ANSI.modifyOtherKeys.disable
|
|
87
92
|
}
|
|
88
93
|
|
|
89
94
|
if (this.config.enableMouse) {
|
|
90
|
-
|
|
95
|
+
output += ANSI.mouse.disable
|
|
91
96
|
}
|
|
92
97
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
this.stdout.write(ANSI.keyboard.disable)
|
|
96
|
-
this.stdout.write(ANSI.modifyOtherKeys.disable)
|
|
98
|
+
if (this.config.enablePaste) {
|
|
99
|
+
output += ANSI.paste.disable
|
|
97
100
|
}
|
|
98
101
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
// Exit fullscreen or re-enable reflow
|
|
103
|
+
if (this.config.mode === "fullscreen") {
|
|
104
|
+
output += Terminal.exitFullscreen
|
|
105
|
+
} else {
|
|
106
|
+
output += ANSI.reflow.enable
|
|
107
|
+
output += "\r\n"
|
|
103
108
|
}
|
|
109
|
+
|
|
110
|
+
// Always show cursor at the end
|
|
111
|
+
output += Terminal.showCursor
|
|
112
|
+
|
|
113
|
+
// Single atomic write - more reliable for process exit scenarios
|
|
114
|
+
this.stdout.write(output)
|
|
104
115
|
}
|
|
105
116
|
}
|
|
@@ -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
|
|
package/src/renderer.ts
CHANGED
|
@@ -71,6 +71,11 @@ export function createRenderer(options?: RendererOptions): TuiRenderer {
|
|
|
71
71
|
onInputProcessed: () => {
|
|
72
72
|
if (!manualMode) renderFrame()
|
|
73
73
|
},
|
|
74
|
+
onQuit: () => {
|
|
75
|
+
// Clean up terminal state before exiting
|
|
76
|
+
renderer.stop()
|
|
77
|
+
process.exit(0)
|
|
78
|
+
},
|
|
74
79
|
})
|
|
75
80
|
|
|
76
81
|
// The render frame logic
|
|
@@ -300,6 +305,27 @@ export function createRenderer(options?: RendererOptions): TuiRenderer {
|
|
|
300
305
|
}, frameMs)
|
|
301
306
|
}
|
|
302
307
|
|
|
308
|
+
// Process exit handlers - ensure terminal is restored on any exit
|
|
309
|
+
// These handlers are critical for proper cleanup when process.exit() is called
|
|
310
|
+
let cleanedUp = false
|
|
311
|
+
const cleanup = () => {
|
|
312
|
+
if (cleanedUp) return
|
|
313
|
+
cleanedUp = true
|
|
314
|
+
renderer.stop()
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Handle normal process exit (synchronous - runs before exit completes)
|
|
318
|
+
const onExit = () => cleanup()
|
|
319
|
+
process.on("exit", onExit)
|
|
320
|
+
|
|
321
|
+
// Handle SIGINT (Ctrl+C from shell, not from TUI input) and SIGTERM
|
|
322
|
+
const onSignal = () => {
|
|
323
|
+
cleanup()
|
|
324
|
+
process.exit(0)
|
|
325
|
+
}
|
|
326
|
+
process.on("SIGINT", onSignal)
|
|
327
|
+
process.on("SIGTERM", onSignal)
|
|
328
|
+
|
|
303
329
|
;(renderer as TuiRendererInternal)._container = null
|
|
304
330
|
return renderer
|
|
305
331
|
}
|
|
@@ -365,6 +391,8 @@ export interface RenderInstance {
|
|
|
365
391
|
rerender(element: ReactNode): void
|
|
366
392
|
unmount(): void
|
|
367
393
|
waitUntilExit(): Promise<void>
|
|
394
|
+
/** Cleanly exit the application, restoring terminal state before process.exit() */
|
|
395
|
+
quit(code?: number): void
|
|
368
396
|
}
|
|
369
397
|
|
|
370
398
|
export function render(element: ReactNode, options?: RendererOptions): RenderInstance {
|
|
@@ -383,18 +411,20 @@ export function render(element: ReactNode, options?: RendererOptions): RenderIns
|
|
|
383
411
|
}
|
|
384
412
|
})
|
|
385
413
|
|
|
414
|
+
// Resolve the exit promise on process exit
|
|
415
|
+
// Note: Terminal cleanup is handled by createRenderer's exit handlers
|
|
386
416
|
const onExit = () => {
|
|
387
|
-
|
|
388
|
-
renderer.stop()
|
|
389
|
-
resolveExit?.()
|
|
390
|
-
}
|
|
417
|
+
resolveExit?.()
|
|
391
418
|
}
|
|
392
419
|
process.once("exit", onExit)
|
|
393
420
|
|
|
394
421
|
const unmount = () => {
|
|
395
422
|
process.off("exit", onExit)
|
|
396
|
-
|
|
397
|
-
|
|
423
|
+
if (!resolved) {
|
|
424
|
+
resolved = true
|
|
425
|
+
renderer.stop()
|
|
426
|
+
resolveExit?.()
|
|
427
|
+
}
|
|
398
428
|
}
|
|
399
429
|
|
|
400
430
|
const rerender = (next: ReactNode) => {
|
|
@@ -402,11 +432,19 @@ export function render(element: ReactNode, options?: RendererOptions): RenderIns
|
|
|
402
432
|
root.render(next)
|
|
403
433
|
}
|
|
404
434
|
|
|
435
|
+
// Clean quit function - renderer.stop() + process.exit()
|
|
436
|
+
// The createRenderer exit handler will also run, but it's idempotent
|
|
437
|
+
const quit = (code = 0) => {
|
|
438
|
+
renderer.stop()
|
|
439
|
+
process.exit(code)
|
|
440
|
+
}
|
|
441
|
+
|
|
405
442
|
return {
|
|
406
443
|
renderer,
|
|
407
444
|
root,
|
|
408
445
|
rerender,
|
|
409
446
|
unmount,
|
|
410
447
|
waitUntilExit: () => exitPromise,
|
|
448
|
+
quit,
|
|
411
449
|
}
|
|
412
450
|
}
|
package/src/test/render-tui.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { MockStdout, MockStdin, stripAnsi, getVisibleLines } from "./mock-stream
|
|
|
7
7
|
export interface RenderTUIOptions {
|
|
8
8
|
width?: number
|
|
9
9
|
height?: number
|
|
10
|
+
mode?: "fullscreen" | "inline"
|
|
11
|
+
diff?: boolean
|
|
10
12
|
}
|
|
11
13
|
|
|
12
14
|
export interface RenderTUIResult {
|
|
@@ -26,6 +28,8 @@ export interface RenderTUIResult {
|
|
|
26
28
|
stdout: MockStdout
|
|
27
29
|
/** Access mock stdin for advanced input simulation */
|
|
28
30
|
stdin: MockStdin
|
|
31
|
+
/** Access renderer for advanced assertions */
|
|
32
|
+
renderer: ReturnType<typeof createRenderer>
|
|
29
33
|
/** Resize the terminal */
|
|
30
34
|
resize(width: number, height: number): void
|
|
31
35
|
}
|
|
@@ -60,6 +64,8 @@ export function renderTUI(element: ReactElement, options?: RenderTUIOptions): Re
|
|
|
60
64
|
stdin: stdin as any,
|
|
61
65
|
manualMode: true,
|
|
62
66
|
skipTerminalSetup: true,
|
|
67
|
+
mode: options?.mode,
|
|
68
|
+
diff: options?.diff,
|
|
63
69
|
})
|
|
64
70
|
|
|
65
71
|
const root = createRoot(renderer)
|
|
@@ -106,6 +112,7 @@ export function renderTUI(element: ReactElement, options?: RenderTUIOptions): Re
|
|
|
106
112
|
|
|
107
113
|
stdout,
|
|
108
114
|
stdin,
|
|
115
|
+
renderer,
|
|
109
116
|
|
|
110
117
|
resize(w: number, h: number) {
|
|
111
118
|
stdout.resize(w, h)
|
|
@@ -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/flex-layout.ts
CHANGED
|
@@ -53,6 +53,19 @@ export function measureFlex(
|
|
|
53
53
|
return { sizes, totalSize }
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Get the greedy weight for a child.
|
|
58
|
+
* - undefined/false = 0 (not greedy, hugs content)
|
|
59
|
+
* - true = 1
|
|
60
|
+
* - number = that weight
|
|
61
|
+
*/
|
|
62
|
+
function getGreedyWeight(child: HostInstance): number {
|
|
63
|
+
const greedy = (child as BaseHost).greedy
|
|
64
|
+
if (greedy === undefined || greedy === false) return 0
|
|
65
|
+
if (greedy === true) return 1
|
|
66
|
+
return greedy
|
|
67
|
+
}
|
|
68
|
+
|
|
56
69
|
/**
|
|
57
70
|
* Layout children along a flex axis using cached sizes.
|
|
58
71
|
*/
|
|
@@ -67,17 +80,17 @@ export function layoutFlex(
|
|
|
67
80
|
): void {
|
|
68
81
|
// Calculate totals
|
|
69
82
|
let totalNaturalMain = 0
|
|
70
|
-
let
|
|
83
|
+
let totalGreedyWeight = 0
|
|
71
84
|
const totalSpacing = Math.max(0, (children.length - 1) * spacing)
|
|
72
85
|
|
|
73
86
|
for (let i = 0; i < children.length; i++) {
|
|
74
87
|
const child = children[i]
|
|
75
88
|
const size = cachedSizes[i] ?? child.measure(rect.w, rect.h)
|
|
76
89
|
totalNaturalMain += mainSize(axis, size)
|
|
77
|
-
|
|
90
|
+
totalGreedyWeight += getGreedyWeight(child)
|
|
78
91
|
}
|
|
79
92
|
|
|
80
|
-
// Calculate extra space to distribute
|
|
93
|
+
// Calculate extra space to distribute to greedy children
|
|
81
94
|
const availableMain = mainDim(axis, rect)
|
|
82
95
|
const extraSpace = Math.max(0, availableMain - totalNaturalMain - totalSpacing)
|
|
83
96
|
|
|
@@ -89,10 +102,10 @@ export function layoutFlex(
|
|
|
89
102
|
for (let i = 0; i < children.length; i++) {
|
|
90
103
|
const child = children[i]
|
|
91
104
|
const size = cachedSizes[i] ?? { w: 0, h: 0 }
|
|
92
|
-
const
|
|
93
|
-
const
|
|
105
|
+
const greedyWeight = getGreedyWeight(child)
|
|
106
|
+
const greedyExtra = totalGreedyWeight > 0 ? (extraSpace * greedyWeight) / totalGreedyWeight : 0
|
|
94
107
|
|
|
95
|
-
const childMainDim = mainSize(axis, size) +
|
|
108
|
+
const childMainDim = mainSize(axis, size) + greedyExtra
|
|
96
109
|
const childCrossDim = crossSize(axis, size)
|
|
97
110
|
|
|
98
111
|
// Calculate cross position based on alignment
|
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
|
+
}
|