@effect-tui/react 0.15.2 → 2.0.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/README.md +11 -2
- package/dist/src/codeblock.d.ts +1 -1
- package/dist/src/codeblock.d.ts.map +1 -1
- package/dist/src/codeblock.js +2 -2
- package/dist/src/codeblock.js.map +1 -1
- package/dist/src/components/ListView.d.ts +4 -4
- package/dist/src/components/ListView.d.ts.map +1 -1
- package/dist/src/components/ListView.js +16 -17
- package/dist/src/components/ListView.js.map +1 -1
- package/dist/src/components/Markdown.js +3 -3
- package/dist/src/components/Markdown.js.map +1 -1
- package/dist/src/components/MultilineTextInput.d.ts.map +1 -1
- package/dist/src/components/MultilineTextInput.js +133 -305
- package/dist/src/components/MultilineTextInput.js.map +1 -1
- package/dist/src/components/TextInput.d.ts.map +1 -1
- package/dist/src/components/TextInput.js +51 -98
- package/dist/src/components/TextInput.js.map +1 -1
- package/dist/src/components/text-editing.d.ts +61 -0
- package/dist/src/components/text-editing.d.ts.map +1 -1
- package/dist/src/components/text-editing.js +131 -0
- package/dist/src/components/text-editing.js.map +1 -1
- package/dist/src/console/ConsolePopover.d.ts +7 -1
- package/dist/src/console/ConsolePopover.d.ts.map +1 -1
- package/dist/src/console/ConsolePopover.js +55 -74
- package/dist/src/console/ConsolePopover.js.map +1 -1
- package/dist/src/debug/DebugOverlay.d.ts.map +1 -1
- package/dist/src/debug/DebugOverlay.js +3 -57
- package/dist/src/debug/DebugOverlay.js.map +1 -1
- package/dist/src/debug/DiagnosticsPanel.js +1 -1
- package/dist/src/debug/DiagnosticsPanel.js.map +1 -1
- package/dist/src/dev.d.ts +5 -117
- package/dist/src/dev.d.ts.map +1 -1
- package/dist/src/dev.js +3 -333
- package/dist/src/dev.js.map +1 -1
- package/dist/src/hooks/use-scroll.d.ts +31 -35
- package/dist/src/hooks/use-scroll.d.ts.map +1 -1
- package/dist/src/hooks/use-scroll.js +51 -90
- package/dist/src/hooks/use-scroll.js.map +1 -1
- package/dist/src/hosts/base.d.ts +13 -2
- package/dist/src/hosts/base.d.ts.map +1 -1
- package/dist/src/hosts/base.js +74 -2
- package/dist/src/hosts/base.js.map +1 -1
- package/dist/src/hosts/box.d.ts +2 -2
- package/dist/src/hosts/box.d.ts.map +1 -1
- package/dist/src/hosts/box.js +29 -2
- package/dist/src/hosts/box.js.map +1 -1
- package/dist/src/hosts/canvas.d.ts +24 -4
- package/dist/src/hosts/canvas.d.ts.map +1 -1
- package/dist/src/hosts/canvas.js +107 -41
- package/dist/src/hosts/canvas.js.map +1 -1
- package/dist/src/hosts/codeblock.d.ts +10 -12
- package/dist/src/hosts/codeblock.d.ts.map +1 -1
- package/dist/src/hosts/codeblock.js +38 -35
- package/dist/src/hosts/codeblock.js.map +1 -1
- package/dist/src/hosts/flex-container.d.ts +3 -3
- package/dist/src/hosts/flex-container.d.ts.map +1 -1
- package/dist/src/hosts/flex-container.js +20 -5
- package/dist/src/hosts/flex-container.js.map +1 -1
- package/dist/src/hosts/index.d.ts +3 -2
- package/dist/src/hosts/index.d.ts.map +1 -1
- package/dist/src/hosts/index.js +2 -1
- package/dist/src/hosts/index.js.map +1 -1
- package/dist/src/hosts/layout-helpers.d.ts +10 -0
- package/dist/src/hosts/layout-helpers.d.ts.map +1 -0
- package/dist/src/hosts/layout-helpers.js +10 -0
- package/dist/src/hosts/layout-helpers.js.map +1 -0
- package/dist/src/hosts/leaf.d.ts +14 -0
- package/dist/src/hosts/leaf.d.ts.map +1 -0
- package/dist/src/hosts/leaf.js +31 -0
- package/dist/src/hosts/leaf.js.map +1 -0
- 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 -2
- package/dist/src/hosts/overlay-item.js.map +1 -1
- package/dist/src/hosts/overlay.d.ts +2 -2
- package/dist/src/hosts/overlay.d.ts.map +1 -1
- package/dist/src/hosts/overlay.js +6 -9
- package/dist/src/hosts/overlay.js.map +1 -1
- package/dist/src/hosts/scroll.d.ts +54 -26
- package/dist/src/hosts/scroll.d.ts.map +1 -1
- package/dist/src/hosts/scroll.js +185 -87
- package/dist/src/hosts/scroll.js.map +1 -1
- package/dist/src/hosts/single-child.d.ts.map +1 -1
- package/dist/src/hosts/single-child.js +2 -0
- package/dist/src/hosts/single-child.js.map +1 -1
- package/dist/src/hosts/spacer.d.ts +3 -3
- package/dist/src/hosts/spacer.d.ts.map +1 -1
- package/dist/src/hosts/spacer.js +8 -3
- package/dist/src/hosts/spacer.js.map +1 -1
- package/dist/src/hosts/text.d.ts +22 -18
- package/dist/src/hosts/text.d.ts.map +1 -1
- package/dist/src/hosts/text.js +108 -131
- package/dist/src/hosts/text.js.map +1 -1
- package/dist/src/hosts/vstack.js +1 -1
- package/dist/src/hosts/vstack.js.map +1 -1
- package/dist/src/hosts/zstack.d.ts +3 -3
- package/dist/src/hosts/zstack.d.ts.map +1 -1
- package/dist/src/hosts/zstack.js +13 -8
- package/dist/src/hosts/zstack.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/internal/dev/hmr.d.ts +20 -0
- package/dist/src/internal/dev/hmr.d.ts.map +1 -0
- package/dist/src/internal/dev/hmr.js +93 -0
- package/dist/src/internal/dev/hmr.js.map +1 -0
- package/dist/src/internal/dev/runtime.d.ts +24 -0
- package/dist/src/internal/dev/runtime.d.ts.map +1 -0
- package/dist/src/internal/dev/runtime.js +135 -0
- package/dist/src/internal/dev/runtime.js.map +1 -0
- package/dist/src/internal/dev/ui.d.ts +13 -0
- package/dist/src/internal/dev/ui.d.ts.map +1 -0
- package/dist/src/internal/dev/ui.js +51 -0
- package/dist/src/internal/dev/ui.js.map +1 -0
- package/dist/src/internal/renderer/context.d.ts +9 -0
- package/dist/src/internal/renderer/context.d.ts.map +1 -0
- package/dist/src/internal/renderer/context.js +22 -0
- package/dist/src/internal/renderer/context.js.map +1 -0
- package/dist/src/internal/renderer/core/FrameBuilder.d.ts +18 -0
- package/dist/src/internal/renderer/core/FrameBuilder.d.ts.map +1 -0
- package/dist/src/internal/renderer/core/FrameBuilder.js +40 -0
- package/dist/src/internal/renderer/core/FrameBuilder.js.map +1 -0
- package/dist/src/internal/renderer/core/RendererState.d.ts +41 -0
- package/dist/src/internal/renderer/core/RendererState.d.ts.map +1 -0
- package/dist/src/internal/renderer/core/RendererState.js +70 -0
- package/dist/src/internal/renderer/core/RendererState.js.map +1 -0
- package/dist/src/internal/renderer/core/index.d.ts +3 -0
- package/dist/src/internal/renderer/core/index.d.ts.map +1 -0
- package/dist/src/internal/renderer/core/index.js +3 -0
- package/dist/src/internal/renderer/core/index.js.map +1 -0
- package/dist/src/internal/renderer/index.d.ts +40 -0
- package/dist/src/internal/renderer/index.d.ts.map +1 -0
- package/dist/src/internal/renderer/index.js +543 -0
- package/dist/src/internal/renderer/index.js.map +1 -0
- package/dist/src/internal/renderer/input/InputProcessor.d.ts +30 -0
- package/dist/src/internal/renderer/input/InputProcessor.d.ts.map +1 -0
- package/dist/src/internal/renderer/input/InputProcessor.js +122 -0
- package/dist/src/internal/renderer/input/InputProcessor.js.map +1 -0
- package/dist/src/internal/renderer/input/index.d.ts +2 -0
- package/dist/src/internal/renderer/input/index.d.ts.map +1 -0
- package/dist/src/internal/renderer/input/index.js +2 -0
- package/dist/src/internal/renderer/input/index.js.map +1 -0
- package/dist/src/internal/renderer/lifecycle/EventBus.d.ts +42 -0
- package/dist/src/internal/renderer/lifecycle/EventBus.d.ts.map +1 -0
- package/dist/src/internal/renderer/lifecycle/EventBus.js +97 -0
- package/dist/src/internal/renderer/lifecycle/EventBus.js.map +1 -0
- package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.d.ts +13 -0
- package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.d.ts.map +1 -0
- package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.js +111 -0
- package/dist/src/internal/renderer/lifecycle/ProcessLifecycle.js.map +1 -0
- package/dist/src/internal/renderer/lifecycle/RenderCache.d.ts +3 -0
- package/dist/src/internal/renderer/lifecycle/RenderCache.d.ts.map +1 -0
- package/dist/src/internal/renderer/lifecycle/RenderCache.js +9 -0
- package/dist/src/internal/renderer/lifecycle/RenderCache.js.map +1 -0
- package/dist/src/internal/renderer/lifecycle/index.d.ts +4 -0
- package/dist/src/internal/renderer/lifecycle/index.d.ts.map +1 -0
- package/dist/src/internal/renderer/lifecycle/index.js +4 -0
- package/dist/src/internal/renderer/lifecycle/index.js.map +1 -0
- package/dist/src/internal/renderer/modes/FullscreenRenderer.d.ts +12 -0
- package/dist/src/internal/renderer/modes/FullscreenRenderer.d.ts.map +1 -0
- package/dist/src/internal/renderer/modes/FullscreenRenderer.js +54 -0
- package/dist/src/internal/renderer/modes/FullscreenRenderer.js.map +1 -0
- package/dist/src/internal/renderer/modes/InlineRenderer.d.ts +25 -0
- package/dist/src/internal/renderer/modes/InlineRenderer.d.ts.map +1 -0
- package/dist/src/internal/renderer/modes/InlineRenderer.js +166 -0
- package/dist/src/internal/renderer/modes/InlineRenderer.js.map +1 -0
- package/dist/src/internal/renderer/modes/RendererMode.d.ts +42 -0
- package/dist/src/internal/renderer/modes/RendererMode.d.ts.map +1 -0
- package/dist/src/internal/renderer/modes/RendererMode.js +2 -0
- package/dist/src/internal/renderer/modes/RendererMode.js.map +1 -0
- package/dist/src/internal/renderer/modes/StaticContentRenderer.d.ts +25 -0
- package/dist/src/internal/renderer/modes/StaticContentRenderer.d.ts.map +1 -0
- package/dist/src/internal/renderer/modes/StaticContentRenderer.js +49 -0
- package/dist/src/internal/renderer/modes/StaticContentRenderer.js.map +1 -0
- package/dist/src/internal/renderer/modes/index.d.ts +5 -0
- package/dist/src/internal/renderer/modes/index.d.ts.map +1 -0
- package/dist/src/internal/renderer/modes/index.js +4 -0
- package/dist/src/internal/renderer/modes/index.js.map +1 -0
- package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.d.ts +13 -0
- package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.d.ts.map +1 -0
- package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.js +75 -0
- package/dist/src/internal/renderer/terminal/KeyboardCapabilityProbe.js.map +1 -0
- package/dist/src/internal/renderer/terminal/TerminalSetup.d.ts +29 -0
- package/dist/src/internal/renderer/terminal/TerminalSetup.d.ts.map +1 -0
- package/dist/src/internal/renderer/terminal/TerminalSetup.js +82 -0
- package/dist/src/internal/renderer/terminal/TerminalSetup.js.map +1 -0
- package/dist/src/internal/renderer/terminal/index.d.ts +3 -0
- package/dist/src/internal/renderer/terminal/index.d.ts.map +1 -0
- package/dist/src/internal/renderer/terminal/index.js +3 -0
- package/dist/src/internal/renderer/terminal/index.js.map +1 -0
- package/dist/src/internal/renderer/types.d.ts +122 -0
- package/dist/src/internal/renderer/types.d.ts.map +1 -0
- package/dist/src/internal/renderer/types.js +2 -0
- package/dist/src/internal/renderer/types.js.map +1 -0
- package/dist/src/motion/hooks.d.ts +1 -1
- package/dist/src/motion/hooks.js +1 -1
- package/dist/src/reconciler/host-config.js +2 -2
- package/dist/src/reconciler/host-config.js.map +1 -1
- package/dist/src/reconciler/types.d.ts +5 -1
- package/dist/src/reconciler/types.d.ts.map +1 -1
- package/dist/src/renderer-context.d.ts +1 -8
- package/dist/src/renderer-context.d.ts.map +1 -1
- package/dist/src/renderer-context.js +1 -21
- package/dist/src/renderer-context.js.map +1 -1
- package/dist/src/renderer-types.d.ts +1 -115
- package/dist/src/renderer-types.d.ts.map +1 -1
- package/dist/src/renderer.d.ts +1 -31
- package/dist/src/renderer.d.ts.map +1 -1
- package/dist/src/renderer.js +1 -495
- package/dist/src/renderer.js.map +1 -1
- package/dist/src/test/render-tui.d.ts +3 -3
- package/dist/src/test/render-tui.d.ts.map +1 -1
- package/dist/src/test/render-tui.js +16 -9
- package/dist/src/test/render-tui.js.map +1 -1
- package/dist/src/utils/alignment.d.ts +1 -1
- package/dist/src/utils/alignment.d.ts.map +1 -1
- package/dist/src/utils/alignment.js +0 -2
- package/dist/src/utils/alignment.js.map +1 -1
- package/dist/src/utils/border.d.ts +1 -1
- package/dist/src/utils/border.d.ts.map +1 -1
- package/dist/src/utils/border.js +2 -0
- package/dist/src/utils/border.js.map +1 -1
- package/dist/src/utils/console-helpers.d.ts +19 -0
- package/dist/src/utils/console-helpers.d.ts.map +1 -0
- package/dist/src/utils/console-helpers.js +61 -0
- package/dist/src/utils/console-helpers.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 +8 -1
- package/dist/src/utils/styles.d.ts.map +1 -1
- package/dist/src/utils/styles.js +10 -8
- package/dist/src/utils/styles.js.map +1 -1
- package/dist/src/utils/text-layout.d.ts +22 -0
- package/dist/src/utils/text-layout.d.ts.map +1 -0
- package/dist/src/utils/text-layout.js +37 -0
- package/dist/src/utils/text-layout.js.map +1 -0
- package/dist/src/utils/text-wrap.d.ts +31 -1
- package/dist/src/utils/text-wrap.d.ts.map +1 -1
- package/dist/src/utils/text-wrap.js +205 -48
- package/dist/src/utils/text-wrap.js.map +1 -1
- package/dist/src/visualize/index.js +1 -1
- package/dist/src/visualize/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/codeblock.tsx +2 -2
- package/src/components/ListView.tsx +21 -23
- package/src/components/Markdown.tsx +3 -3
- package/src/components/MultilineTextInput.tsx +138 -344
- package/src/components/TextInput.tsx +54 -99
- package/src/components/text-editing.ts +180 -0
- package/src/console/ConsolePopover.tsx +124 -107
- package/src/debug/DebugOverlay.ts +15 -74
- package/src/debug/DiagnosticsPanel.tsx +1 -1
- package/src/dev.tsx +5 -458
- package/src/hooks/use-scroll.ts +85 -145
- package/src/hosts/base.ts +86 -3
- package/src/hosts/box.ts +37 -2
- package/src/hosts/canvas.ts +128 -42
- package/src/hosts/codeblock.ts +48 -35
- package/src/hosts/flex-container.ts +25 -6
- package/src/hosts/index.ts +11 -2
- package/src/hosts/layout-helpers.ts +20 -0
- package/src/hosts/leaf.ts +36 -0
- package/src/hosts/overlay-item.ts +8 -2
- package/src/hosts/overlay.ts +13 -11
- package/src/hosts/scroll.ts +228 -106
- package/src/hosts/single-child.ts +2 -0
- package/src/hosts/spacer.ts +8 -3
- package/src/hosts/text.ts +126 -132
- package/src/hosts/vstack.ts +1 -1
- package/src/hosts/zstack.ts +14 -9
- package/src/index.ts +2 -2
- package/src/internal/dev/hmr.ts +101 -0
- package/src/internal/dev/runtime.ts +170 -0
- package/src/internal/dev/ui.tsx +87 -0
- package/src/internal/renderer/context.ts +27 -0
- package/src/{renderer → internal/renderer}/core/FrameBuilder.ts +2 -2
- package/src/internal/renderer/index.ts +689 -0
- package/src/{renderer → internal/renderer}/input/InputProcessor.ts +10 -1
- package/src/{renderer → internal/renderer}/lifecycle/EventBus.ts +9 -1
- package/src/internal/renderer/lifecycle/ProcessLifecycle.ts +125 -0
- package/src/internal/renderer/lifecycle/index.ts +3 -0
- package/src/{renderer → internal/renderer}/modes/InlineRenderer.ts +5 -2
- package/src/{renderer → internal/renderer}/modes/RendererMode.ts +1 -1
- package/src/{renderer → internal/renderer}/modes/StaticContentRenderer.ts +5 -2
- package/src/internal/renderer/terminal/KeyboardCapabilityProbe.ts +91 -0
- package/src/{renderer/lifecycle → internal/renderer/terminal}/TerminalSetup.ts +4 -22
- package/src/internal/renderer/terminal/index.ts +2 -0
- package/src/internal/renderer/types.ts +129 -0
- package/src/motion/hooks.ts +1 -1
- package/src/reconciler/host-config.ts +2 -2
- package/src/reconciler/types.ts +7 -1
- package/src/renderer-context.ts +1 -27
- package/src/renderer-types.ts +10 -123
- package/src/renderer.ts +1 -619
- package/src/test/render-tui.ts +16 -10
- package/src/utils/alignment.ts +1 -3
- package/src/utils/border.ts +11 -1
- package/src/utils/console-helpers.ts +86 -0
- package/src/utils/index.ts +15 -1
- package/src/utils/styles.ts +16 -4
- package/src/utils/text-layout.ts +65 -0
- package/src/utils/text-wrap.ts +261 -48
- package/src/visualize/index.tsx +1 -1
- package/src/renderer/lifecycle/ResizeManager.ts +0 -65
- package/src/renderer/lifecycle/index.ts +0 -4
- /package/src/{renderer → internal/renderer}/core/RendererState.ts +0 -0
- /package/src/{renderer → internal/renderer}/core/index.ts +0 -0
- /package/src/{renderer → internal/renderer}/input/index.ts +0 -0
- /package/src/{renderer → internal/renderer}/lifecycle/RenderCache.ts +0 -0
- /package/src/{renderer → internal/renderer}/modes/FullscreenRenderer.ts +0 -0
- /package/src/{renderer → internal/renderer}/modes/index.ts +0 -0
package/src/hosts/text.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { type CellBuffer, type Color, displayWidth, type Palette } from "@effect-tui/core"
|
|
2
2
|
import type { ColorMotionValue } from "../motion/color-motion-value.js"
|
|
3
3
|
import type { CommonProps, HostContext, HostInstance, Rect, Size } from "../reconciler/types.js"
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
resolveInheritedBgStyle,
|
|
6
|
+
splitSpansByNewline,
|
|
7
|
+
spansDisplayWidth,
|
|
8
|
+
styleIdFromProps,
|
|
9
|
+
wrapSpansByLine,
|
|
10
|
+
wrapText,
|
|
11
|
+
} from "../utils/index.js"
|
|
5
12
|
import { BaseHost, getInheritedBg } from "./base.js"
|
|
13
|
+
import { LeafHost } from "./leaf.js"
|
|
6
14
|
|
|
7
15
|
/** Color prop that can be a static Color or a spring-animated ColorMotionValue */
|
|
8
16
|
export type ColorProp = Color | ColorMotionValue
|
|
@@ -49,6 +57,7 @@ export class TextHost extends BaseHost {
|
|
|
49
57
|
// Cache wrapped lines between measure() and render()
|
|
50
58
|
private cachedLines: string[] | null = null
|
|
51
59
|
private cachedWidth = 0
|
|
60
|
+
private cachedStyledWrap = false
|
|
52
61
|
// Cache content to avoid rescanning children each frame
|
|
53
62
|
private cachedContent: string | null = null
|
|
54
63
|
// Cache for styled mode
|
|
@@ -56,7 +65,6 @@ export class TextHost extends BaseHost {
|
|
|
56
65
|
private cachedSpans: StyledSpan[] | null = null
|
|
57
66
|
private hasSpans = false
|
|
58
67
|
private explicitSpans: StyledSpan[] | null = null
|
|
59
|
-
private prepared = false
|
|
60
68
|
|
|
61
69
|
constructor(props: TextProps, ctx: HostContext) {
|
|
62
70
|
super("text", props, ctx)
|
|
@@ -94,15 +102,6 @@ export class TextHost extends BaseHost {
|
|
|
94
102
|
if (child.content) {
|
|
95
103
|
spans.push({
|
|
96
104
|
text: child.content,
|
|
97
|
-
// Inherit TextHost's styles
|
|
98
|
-
fg: this.fg,
|
|
99
|
-
bg: this.bg,
|
|
100
|
-
bold: this.bold,
|
|
101
|
-
dimmed: this.dimmed,
|
|
102
|
-
italic: this.italic,
|
|
103
|
-
underline: this.underline,
|
|
104
|
-
strikethrough: this.strikethrough,
|
|
105
|
-
inverse: this.inverse,
|
|
106
105
|
})
|
|
107
106
|
}
|
|
108
107
|
} else if (child instanceof SpanHost) {
|
|
@@ -110,15 +109,15 @@ export class TextHost extends BaseHost {
|
|
|
110
109
|
if (content) {
|
|
111
110
|
spans.push({
|
|
112
111
|
text: content,
|
|
113
|
-
// Span's styles
|
|
114
|
-
fg: child.fg
|
|
115
|
-
bg: child.bg
|
|
116
|
-
bold: child.bold
|
|
117
|
-
dimmed: child.dimmed
|
|
118
|
-
italic: child.italic
|
|
119
|
-
underline: child.underline
|
|
120
|
-
strikethrough: child.strikethrough
|
|
121
|
-
inverse: child.inverse
|
|
112
|
+
// Span's styles (TextHost applies fallbacks at render time)
|
|
113
|
+
fg: child.fg,
|
|
114
|
+
bg: child.bg,
|
|
115
|
+
bold: child.bold,
|
|
116
|
+
dimmed: child.dimmed,
|
|
117
|
+
italic: child.italic,
|
|
118
|
+
underline: child.underline,
|
|
119
|
+
strikethrough: child.strikethrough,
|
|
120
|
+
inverse: child.inverse,
|
|
122
121
|
})
|
|
123
122
|
}
|
|
124
123
|
}
|
|
@@ -128,54 +127,51 @@ export class TextHost extends BaseHost {
|
|
|
128
127
|
}
|
|
129
128
|
|
|
130
129
|
private prepareContent(): void {
|
|
131
|
-
this.invalidateContent()
|
|
132
130
|
const useExplicitSpans = this.explicitSpans !== null
|
|
133
131
|
if (useExplicitSpans) {
|
|
134
132
|
this.hasSpans = false
|
|
135
133
|
this.cachedSpans = null
|
|
136
|
-
this.prepared = true
|
|
137
134
|
return
|
|
138
135
|
}
|
|
139
136
|
|
|
140
137
|
this.hasSpans = this.checkForSpans()
|
|
141
138
|
this.cachedSpans = this.hasSpans ? this.collectSpans() : null
|
|
142
|
-
this.prepared = true
|
|
143
139
|
}
|
|
144
140
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
this.prepareContent()
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** Invalidate content cache when children change */
|
|
151
|
-
private invalidateContent(): void {
|
|
141
|
+
/** Reset content-related caches (text/spans/wrap). */
|
|
142
|
+
private resetContentCaches(): void {
|
|
152
143
|
this.cachedContent = null
|
|
153
144
|
this.cachedLines = null
|
|
154
145
|
this.cachedStyledLines = null
|
|
146
|
+
this.cachedStyledWrap = false
|
|
155
147
|
this.cachedSpans = null
|
|
156
|
-
this.prepared = false
|
|
157
148
|
}
|
|
158
149
|
|
|
159
|
-
protected override prepareSelf(): void {
|
|
150
|
+
protected override prepareSelf(_layoutDirty: boolean, _renderDirty: boolean): void {
|
|
160
151
|
this.prepareContent()
|
|
161
152
|
}
|
|
162
153
|
|
|
154
|
+
override invalidateLayout(): void {
|
|
155
|
+
this.resetContentCaches()
|
|
156
|
+
super.invalidateLayout()
|
|
157
|
+
}
|
|
158
|
+
|
|
163
159
|
override appendChild(child: HostInstance): void {
|
|
160
|
+
this.resetContentCaches()
|
|
164
161
|
super.appendChild(child)
|
|
165
|
-
this.invalidateContent()
|
|
166
162
|
}
|
|
167
163
|
|
|
168
164
|
override removeChild(child: HostInstance): void {
|
|
165
|
+
this.resetContentCaches()
|
|
169
166
|
super.removeChild(child)
|
|
170
|
-
this.invalidateContent()
|
|
171
167
|
}
|
|
172
168
|
|
|
173
169
|
override insertBefore(child: HostInstance, before: HostInstance): void {
|
|
170
|
+
this.resetContentCaches()
|
|
174
171
|
super.insertBefore(child, before)
|
|
175
|
-
this.invalidateContent()
|
|
176
172
|
}
|
|
177
173
|
|
|
178
|
-
|
|
174
|
+
protected measureSelf(maxW: number, maxH: number): Size {
|
|
179
175
|
const constrained = this.constrainProposal(maxW, maxH)
|
|
180
176
|
this.ensurePrepared()
|
|
181
177
|
const useExplicitSpans = this.explicitSpans !== null
|
|
@@ -184,18 +180,21 @@ export class TextHost extends BaseHost {
|
|
|
184
180
|
if (useExplicitSpans || this.hasSpans) {
|
|
185
181
|
const spans = useExplicitSpans ? this.explicitSpans! : (this.cachedSpans ?? this.collectSpans())
|
|
186
182
|
if (this.wrap) {
|
|
187
|
-
this.cachedStyledLines =
|
|
183
|
+
this.cachedStyledLines = wrapSpansByLine(spans, constrained.w)
|
|
188
184
|
this.cachedWidth = constrained.w
|
|
185
|
+
this.cachedStyledWrap = true
|
|
189
186
|
const h = Math.min(this.cachedStyledLines.length, constrained.h)
|
|
190
|
-
const w = this.cachedStyledLines.reduce(
|
|
191
|
-
(max, line) => Math.max(max, line.reduce((sum, span) => sum + displayWidth(span.text), 0)),
|
|
192
|
-
0,
|
|
193
|
-
)
|
|
187
|
+
const w = this.cachedStyledLines.reduce((max, line) => Math.max(max, spansDisplayWidth(line)), 0)
|
|
194
188
|
return this.constrainResult({ w, h })
|
|
195
189
|
}
|
|
196
|
-
// Non-wrap styled mode
|
|
197
|
-
const
|
|
198
|
-
|
|
190
|
+
// Non-wrap styled mode (preserve explicit newlines)
|
|
191
|
+
const lines = splitSpansByNewline(spans)
|
|
192
|
+
this.cachedStyledLines = lines
|
|
193
|
+
this.cachedWidth = constrained.w
|
|
194
|
+
this.cachedStyledWrap = false
|
|
195
|
+
const maxLineWidth = lines.reduce((max, line) => Math.max(max, spansDisplayWidth(line)), 0)
|
|
196
|
+
const h = Math.min(lines.length, constrained.h)
|
|
197
|
+
return this.constrainResult({ w: Math.min(maxLineWidth, constrained.w), h })
|
|
199
198
|
}
|
|
200
199
|
|
|
201
200
|
// Simple mode: single style for all content
|
|
@@ -205,7 +204,7 @@ export class TextHost extends BaseHost {
|
|
|
205
204
|
if (this.wrap) {
|
|
206
205
|
// Wrap mode: may span multiple lines. Cache result for render()
|
|
207
206
|
this.cachedLines = rawLines.flatMap((line, idx) =>
|
|
208
|
-
idx < rawLines.length - 1 ? [...
|
|
207
|
+
idx < rawLines.length - 1 ? [...wrapText(line, constrained.w), ""] : wrapText(line, constrained.w),
|
|
209
208
|
)
|
|
210
209
|
this.cachedWidth = constrained.w
|
|
211
210
|
const w = this.cachedLines.reduce((max, line) => Math.max(max, displayWidth(line)), 0)
|
|
@@ -220,61 +219,7 @@ export class TextHost extends BaseHost {
|
|
|
220
219
|
return this.constrainResult({ w, h })
|
|
221
220
|
}
|
|
222
221
|
|
|
223
|
-
|
|
224
|
-
private wrapText(text: string, maxWidth: number): string[] {
|
|
225
|
-
const result: string[] = []
|
|
226
|
-
for (const rawLine of text.split("\n")) {
|
|
227
|
-
if (rawLine === "") {
|
|
228
|
-
result.push("")
|
|
229
|
-
continue
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Split into words (keeping whitespace as separate tokens)
|
|
233
|
-
const tokens = splitWords(rawLine)
|
|
234
|
-
let line = ""
|
|
235
|
-
let lineW = 0
|
|
236
|
-
|
|
237
|
-
for (const token of tokens) {
|
|
238
|
-
const tokenW = displayWidth(token)
|
|
239
|
-
const isWs = isWhitespace(token)
|
|
240
|
-
|
|
241
|
-
if (lineW + tokenW <= maxWidth) {
|
|
242
|
-
// Token fits on current line
|
|
243
|
-
line += token
|
|
244
|
-
lineW += tokenW
|
|
245
|
-
} else if (isWs) {
|
|
246
|
-
// Whitespace doesn't fit - just skip it (don't start new line with space)
|
|
247
|
-
continue
|
|
248
|
-
} else if (tokenW <= maxWidth) {
|
|
249
|
-
// Word doesn't fit but is smaller than maxWidth - start new line
|
|
250
|
-
if (line.trimEnd()) result.push(line.trimEnd())
|
|
251
|
-
line = token
|
|
252
|
-
lineW = tokenW
|
|
253
|
-
} else {
|
|
254
|
-
// Word is longer than maxWidth - break it character by character
|
|
255
|
-
if (line.trimEnd()) result.push(line.trimEnd())
|
|
256
|
-
line = ""
|
|
257
|
-
lineW = 0
|
|
258
|
-
for (const ch of token) {
|
|
259
|
-
const chW = displayWidth(ch)
|
|
260
|
-
if (lineW + chW > maxWidth && line.length > 0) {
|
|
261
|
-
result.push(line)
|
|
262
|
-
line = ch
|
|
263
|
-
lineW = chW
|
|
264
|
-
} else {
|
|
265
|
-
line += ch
|
|
266
|
-
lineW += chW
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
// Preserve trailing space for inline flow - only trimEnd when breaking mid-line (done above)
|
|
272
|
-
if (line) result.push(line)
|
|
273
|
-
}
|
|
274
|
-
return result.length > 0 ? result : [""]
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
override layout(rect: Rect): void {
|
|
222
|
+
protected override layoutSelf(rect: Rect): void {
|
|
278
223
|
const layoutRect = this.layoutWithConstraints(rect)
|
|
279
224
|
// Layout children (RawTextHost nodes) at same position
|
|
280
225
|
for (const child of this.children) {
|
|
@@ -283,10 +228,7 @@ export class TextHost extends BaseHost {
|
|
|
283
228
|
}
|
|
284
229
|
|
|
285
230
|
render(buffer: CellBuffer, palette: Palette): void {
|
|
286
|
-
if (!this.rect)
|
|
287
|
-
this.prepared = false
|
|
288
|
-
return
|
|
289
|
-
}
|
|
231
|
+
if (!this.rect) return
|
|
290
232
|
this.ensurePrepared()
|
|
291
233
|
|
|
292
234
|
// If text has no bg, inherit from parent box for proper highlight rendering
|
|
@@ -297,11 +239,11 @@ export class TextHost extends BaseHost {
|
|
|
297
239
|
// Styled mode: render with per-span styles
|
|
298
240
|
if (useExplicitSpans || this.hasSpans) {
|
|
299
241
|
const lines =
|
|
300
|
-
this.
|
|
242
|
+
this.cachedStyledLines && this.cachedWidth === this.rect.w && this.cachedStyledWrap === this.wrap
|
|
301
243
|
? this.cachedStyledLines
|
|
302
244
|
: (() => {
|
|
303
245
|
const spans = useExplicitSpans ? this.explicitSpans! : (this.cachedSpans ?? this.collectSpans())
|
|
304
|
-
return this.wrap ?
|
|
246
|
+
return this.wrap ? wrapSpansByLine(spans, this.rect.w) : splitSpansByNewline(spans)
|
|
305
247
|
})()
|
|
306
248
|
|
|
307
249
|
for (let y = 0; y < Math.min(lines.length, this.rect.h); y++) {
|
|
@@ -310,12 +252,12 @@ export class TextHost extends BaseHost {
|
|
|
310
252
|
const spanStyleId = styleIdFromProps(palette, {
|
|
311
253
|
fg: span.fg ?? this.fg,
|
|
312
254
|
bg: span.bg ?? inheritedBg,
|
|
313
|
-
bold: span.bold,
|
|
314
|
-
dimmed: span.dimmed,
|
|
315
|
-
italic: span.italic,
|
|
316
|
-
underline: span.underline,
|
|
317
|
-
strikethrough: span.strikethrough,
|
|
318
|
-
inverse: span.inverse,
|
|
255
|
+
bold: span.bold ?? this.bold,
|
|
256
|
+
dimmed: span.dimmed ?? this.dimmed,
|
|
257
|
+
italic: span.italic ?? this.italic,
|
|
258
|
+
underline: span.underline ?? this.underline,
|
|
259
|
+
strikethrough: span.strikethrough ?? this.strikethrough,
|
|
260
|
+
inverse: span.inverse ?? this.inverse,
|
|
319
261
|
})
|
|
320
262
|
const availableWidth = this.rect.w - (x - this.rect.x)
|
|
321
263
|
if (availableWidth <= 0) break
|
|
@@ -324,7 +266,6 @@ export class TextHost extends BaseHost {
|
|
|
324
266
|
x += displayWidth(span.text)
|
|
325
267
|
}
|
|
326
268
|
}
|
|
327
|
-
this.prepared = false
|
|
328
269
|
return
|
|
329
270
|
}
|
|
330
271
|
|
|
@@ -349,7 +290,7 @@ export class TextHost extends BaseHost {
|
|
|
349
290
|
this.cachedLines && this.cachedWidth === rectW
|
|
350
291
|
? this.cachedLines
|
|
351
292
|
: rawLines.flatMap((line, idx) =>
|
|
352
|
-
idx < rawLines.length - 1 ? [...
|
|
293
|
+
idx < rawLines.length - 1 ? [...wrapText(line, rectW), ""] : wrapText(line, rectW),
|
|
353
294
|
)
|
|
354
295
|
const visibleLines = Math.min(lines.length, this.rect.h)
|
|
355
296
|
// Explicitly paint background under text lines to clear stale styles (e.g., selection highlights).
|
|
@@ -361,7 +302,6 @@ export class TextHost extends BaseHost {
|
|
|
361
302
|
const lineWidth = Math.min(displayWidth(lines[i]), this.rect.w)
|
|
362
303
|
buffer.drawText(this.rect.x, this.rect.y + i, lines[i], styleId, lineWidth)
|
|
363
304
|
}
|
|
364
|
-
this.prepared = false
|
|
365
305
|
return
|
|
366
306
|
}
|
|
367
307
|
|
|
@@ -375,11 +315,21 @@ export class TextHost extends BaseHost {
|
|
|
375
315
|
const lineWidth = Math.min(displayWidth(rawLines[i]), this.rect.w)
|
|
376
316
|
buffer.drawText(this.rect.x, this.rect.y + i, rawLines[i], styleId, lineWidth)
|
|
377
317
|
}
|
|
378
|
-
this.prepared = false
|
|
379
318
|
}
|
|
380
319
|
|
|
381
320
|
override updateProps(props: Record<string, unknown>): void {
|
|
382
321
|
super.updateProps(props)
|
|
322
|
+
const prevFg = this.fg
|
|
323
|
+
const prevBg = this.bg
|
|
324
|
+
const prevBold = this.bold
|
|
325
|
+
const prevDimmed = this.dimmed
|
|
326
|
+
const prevItalic = this.italic
|
|
327
|
+
const prevUnderline = this.underline
|
|
328
|
+
const prevStrikethrough = this.strikethrough
|
|
329
|
+
const prevInverse = this.inverse
|
|
330
|
+
const prevWrap = this.wrap
|
|
331
|
+
const prevExplicitSpans = this.explicitSpans
|
|
332
|
+
|
|
383
333
|
// Color props support MotionValue/ColorMotionValue - auto-subscribe and animate
|
|
384
334
|
this.fg = this.resolveSpringProp("fg", props.fg, (v) => {
|
|
385
335
|
this.fg = v as Color
|
|
@@ -395,11 +345,31 @@ export class TextHost extends BaseHost {
|
|
|
395
345
|
this.inverse = Boolean(props.inverse)
|
|
396
346
|
this.wrap = Boolean(props.wrap)
|
|
397
347
|
this.explicitSpans = "spans" in props ? ((props.spans as StyledSpan[] | undefined) ?? []) : null
|
|
348
|
+
|
|
349
|
+
const layoutChanged = prevWrap !== this.wrap || prevExplicitSpans !== this.explicitSpans
|
|
350
|
+
if (layoutChanged) {
|
|
351
|
+
this.invalidateLayout()
|
|
352
|
+
return
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const renderChanged =
|
|
356
|
+
prevFg !== this.fg ||
|
|
357
|
+
prevBg !== this.bg ||
|
|
358
|
+
prevBold !== this.bold ||
|
|
359
|
+
prevDimmed !== this.dimmed ||
|
|
360
|
+
prevItalic !== this.italic ||
|
|
361
|
+
prevUnderline !== this.underline ||
|
|
362
|
+
prevStrikethrough !== this.strikethrough ||
|
|
363
|
+
prevInverse !== this.inverse
|
|
364
|
+
|
|
365
|
+
if (renderChanged) {
|
|
366
|
+
this.invalidateRender()
|
|
367
|
+
}
|
|
398
368
|
}
|
|
399
369
|
}
|
|
400
370
|
|
|
401
371
|
/** Special host for raw text nodes (React text children) */
|
|
402
|
-
export class RawTextHost extends
|
|
372
|
+
export class RawTextHost extends LeafHost {
|
|
403
373
|
content = ""
|
|
404
374
|
|
|
405
375
|
constructor(text: string, ctx: HostContext) {
|
|
@@ -407,7 +377,7 @@ export class RawTextHost extends BaseHost {
|
|
|
407
377
|
this.content = text
|
|
408
378
|
}
|
|
409
379
|
|
|
410
|
-
|
|
380
|
+
protected measureSelf(maxW: number, _maxH: number): Size {
|
|
411
381
|
const w = Math.min(displayWidth(this.content), maxW)
|
|
412
382
|
return { w, h: 1 }
|
|
413
383
|
}
|
|
@@ -424,6 +394,7 @@ export class RawTextHost extends BaseHost {
|
|
|
424
394
|
|
|
425
395
|
updateText(text: string): void {
|
|
426
396
|
this.content = text
|
|
397
|
+
this.parent?.invalidateLayout?.()
|
|
427
398
|
}
|
|
428
399
|
|
|
429
400
|
override updateProps(_props: Record<string, unknown>): void {
|
|
@@ -469,12 +440,12 @@ export interface SpanProps extends CommonProps {
|
|
|
469
440
|
export class SpanHost extends BaseHost {
|
|
470
441
|
fg?: Color
|
|
471
442
|
bg?: Color
|
|
472
|
-
bold
|
|
473
|
-
dimmed
|
|
474
|
-
italic
|
|
475
|
-
underline
|
|
476
|
-
strikethrough
|
|
477
|
-
inverse
|
|
443
|
+
bold?: boolean
|
|
444
|
+
dimmed?: boolean
|
|
445
|
+
italic?: boolean
|
|
446
|
+
underline?: boolean
|
|
447
|
+
strikethrough?: boolean
|
|
448
|
+
inverse?: boolean
|
|
478
449
|
|
|
479
450
|
constructor(props: SpanProps, ctx: HostContext) {
|
|
480
451
|
super("span", props, ctx)
|
|
@@ -489,7 +460,22 @@ export class SpanHost extends BaseHost {
|
|
|
489
460
|
.join("")
|
|
490
461
|
}
|
|
491
462
|
|
|
492
|
-
|
|
463
|
+
override appendChild(child: HostInstance): void {
|
|
464
|
+
super.appendChild(child)
|
|
465
|
+
this.parent?.invalidateLayout?.()
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
override removeChild(child: HostInstance): void {
|
|
469
|
+
super.removeChild(child)
|
|
470
|
+
this.parent?.invalidateLayout?.()
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
override insertBefore(child: HostInstance, before: HostInstance): void {
|
|
474
|
+
super.insertBefore(child, before)
|
|
475
|
+
this.parent?.invalidateLayout?.()
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
protected measureSelf(_maxW: number, _maxH: number): Size {
|
|
493
479
|
// Span doesn't measure independently - parent TextHost handles layout
|
|
494
480
|
return { w: 0, h: 0 }
|
|
495
481
|
}
|
|
@@ -505,12 +491,20 @@ export class SpanHost extends BaseHost {
|
|
|
505
491
|
// Individual props override textStyle object
|
|
506
492
|
this.fg = props.fg !== undefined ? (props.fg as Color) : textStyle?.fg
|
|
507
493
|
this.bg = props.bg !== undefined ? (props.bg as Color) : textStyle?.bg
|
|
508
|
-
this.bold = props.bold !== undefined ? Boolean(props.bold) :
|
|
509
|
-
this.dimmed = props.dimmed !== undefined ? Boolean(props.dimmed) :
|
|
510
|
-
this.italic = props.italic !== undefined ? Boolean(props.italic) :
|
|
511
|
-
this.underline = props.underline !== undefined ? Boolean(props.underline) :
|
|
512
|
-
this.strikethrough =
|
|
513
|
-
|
|
514
|
-
this.
|
|
494
|
+
this.bold = props.bold !== undefined ? Boolean(props.bold) : textStyle?.bold
|
|
495
|
+
this.dimmed = props.dimmed !== undefined ? Boolean(props.dimmed) : textStyle?.dimmed
|
|
496
|
+
this.italic = props.italic !== undefined ? Boolean(props.italic) : textStyle?.italic
|
|
497
|
+
this.underline = props.underline !== undefined ? Boolean(props.underline) : textStyle?.underline
|
|
498
|
+
this.strikethrough = props.strikethrough !== undefined ? Boolean(props.strikethrough) : textStyle?.strikethrough
|
|
499
|
+
this.inverse = props.inverse !== undefined ? Boolean(props.inverse) : textStyle?.inverse
|
|
500
|
+
this.invalidateLayout()
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
override invalidateLayout(): void {
|
|
504
|
+
this.parent?.invalidateLayout?.()
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
override invalidateRender(): void {
|
|
508
|
+
this.parent?.invalidateRender?.()
|
|
515
509
|
}
|
|
516
510
|
}
|
package/src/hosts/vstack.ts
CHANGED
|
@@ -8,6 +8,6 @@ export interface VStackProps extends FlexContainerProps<"vertical"> {}
|
|
|
8
8
|
*/
|
|
9
9
|
export class VStackHost extends FlexContainerHost<"vertical"> {
|
|
10
10
|
constructor(props: VStackProps, ctx: HostContext) {
|
|
11
|
-
super("vertical", "vstack", props as FlexContainerProps<"vertical">, ctx, "
|
|
11
|
+
super("vertical", "vstack", props as FlexContainerProps<"vertical">, ctx, "left")
|
|
12
12
|
}
|
|
13
13
|
}
|
package/src/hosts/zstack.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { CellBuffer, Palette } from "@effect-tui/core"
|
|
2
2
|
import type { CommonProps, HostContext, Rect, Size } from "../reconciler/types.js"
|
|
3
|
-
import {
|
|
3
|
+
import { type HAlign, type VAlign } from "../utils/index.js"
|
|
4
4
|
import { BaseHost } from "./base.js"
|
|
5
|
+
import { layoutAlignedChildren } from "./layout-helpers.js"
|
|
5
6
|
|
|
6
7
|
export interface ZStackProps extends CommonProps {
|
|
7
|
-
alignment?: { h?: "
|
|
8
|
+
alignment?: { h?: "left" | "center" | "right"; v?: "top" | "center" | "bottom" }
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
// Overlay children in the same rect, honoring alignment for each child.
|
|
@@ -18,7 +19,7 @@ export class ZStackHost extends BaseHost {
|
|
|
18
19
|
this.updateProps(props as unknown as Record<string, unknown>)
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
protected measureSelf(maxW: number, maxH: number): Size {
|
|
22
23
|
// Apply frame constraints to what we propose to children
|
|
23
24
|
const constrained = this.constrainProposal(maxW, maxH)
|
|
24
25
|
|
|
@@ -42,14 +43,13 @@ export class ZStackHost extends BaseHost {
|
|
|
42
43
|
return this.constrainResult(naturalSize)
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
override
|
|
46
|
+
protected override layoutSelf(rect: Rect): void {
|
|
46
47
|
const layoutRect = this.layoutWithConstraints(rect)
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
49
|
+
layoutAlignedChildren(layoutRect, this.children, this.cachedSizes, () => ({
|
|
50
|
+
h: this.alignmentH,
|
|
51
|
+
v: this.alignmentV,
|
|
52
|
+
}))
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
render(buffer: CellBuffer, palette: Palette): void {
|
|
@@ -60,10 +60,15 @@ export class ZStackHost extends BaseHost {
|
|
|
60
60
|
|
|
61
61
|
override updateProps(props: Record<string, unknown>): void {
|
|
62
62
|
super.updateProps(props)
|
|
63
|
+
const prevH = this.alignmentH
|
|
64
|
+
const prevV = this.alignmentV
|
|
63
65
|
if (props.alignment !== undefined) {
|
|
64
66
|
const a = props.alignment as ZStackProps["alignment"]
|
|
65
67
|
if (a?.h) this.alignmentH = a.h
|
|
66
68
|
if (a?.v) this.alignmentV = a.v
|
|
67
69
|
}
|
|
70
|
+
if (prevH !== this.alignmentH || prevV !== this.alignmentV) {
|
|
71
|
+
this.invalidateLayout()
|
|
72
|
+
}
|
|
68
73
|
}
|
|
69
74
|
}
|
package/src/index.ts
CHANGED
|
@@ -65,9 +65,9 @@ export { useKeyboard, useMouse, usePaste, useQuit, useScroll, useShortcut, useTi
|
|
|
65
65
|
export { isKey } from "./shortcuts.js"
|
|
66
66
|
export { useFrameStats } from "./hooks/useFrameStats.js"
|
|
67
67
|
export type { BorderKind, BoxProps } from "./hosts/box.js"
|
|
68
|
-
export type { CanvasProps, DrawContext } from "./hosts/canvas.js"
|
|
68
|
+
export type { CanvasCell, CanvasProps, DrawContext } from "./hosts/canvas.js"
|
|
69
69
|
export type { HStackProps } from "./hosts/hstack.js"
|
|
70
|
-
export type { ScrollProps } from "./hosts/scroll.js"
|
|
70
|
+
export type { ScrollAlign, ScrollAlignX, ScrollAlignY, ScrollAxis, ScrollLayoutChange, ScrollProps } from "./hosts/scroll.js"
|
|
71
71
|
export type { SpacerProps } from "./hosts/spacer.js"
|
|
72
72
|
export type { SpanProps, SpanStyle, TextProps } from "./hosts/text.js"
|
|
73
73
|
export type { VStackProps } from "./hosts/vstack.js"
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
import { globalValue } from "effect/GlobalValue"
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pipeable combinator that makes an atom persist across hot reloads.
|
|
6
|
+
*
|
|
7
|
+
* This is the cleanest way to define HMR-persistent atoms - just pipe it!
|
|
8
|
+
* Combines globalValue (for persistence) + keepAlive (prevents registry cleanup).
|
|
9
|
+
*/
|
|
10
|
+
export function hmr(key: string): <A,>(self: A) => A {
|
|
11
|
+
return <A,>(self: A): A => {
|
|
12
|
+
return globalValue(Symbol.for(`hmr/${key}`), () => {
|
|
13
|
+
// Apply keepAlive if it's an atom (just sets keepAlive: true)
|
|
14
|
+
if (typeof self === "object" && self !== null && "keepAlive" in self) {
|
|
15
|
+
return Object.assign(Object.create(Object.getPrototypeOf(self)), {
|
|
16
|
+
...self,
|
|
17
|
+
keepAlive: true,
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
return self
|
|
21
|
+
}) as A
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wrap any value creation in HMR persistence.
|
|
27
|
+
*/
|
|
28
|
+
export function hmrState<T>(key: string, create: () => T): T {
|
|
29
|
+
return globalValue(Symbol.for(`hmr/${key}`), create)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Cache for runtime key extraction
|
|
33
|
+
const keyCache = new Map<string, string>()
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Parse stack trace to get file path and line number.
|
|
37
|
+
* Returns null if parsing fails.
|
|
38
|
+
*/
|
|
39
|
+
function parseStack(stack: string): { file: string; line: number; col: number } | null {
|
|
40
|
+
// Bun stack format: " at functionName (file:line:col)" or " at file:line:col"
|
|
41
|
+
const lines = stack.split("\n")
|
|
42
|
+
// Skip first line (Error message) and find caller (skip autoHmr itself)
|
|
43
|
+
for (let i = 2; i < lines.length; i++) {
|
|
44
|
+
const match = lines[i].match(/\((.+?):(\d+):(\d+)\)/) || lines[i].match(/at\s+(.+?):(\d+):(\d+)/)
|
|
45
|
+
if (match) {
|
|
46
|
+
return { file: match[1], line: parseInt(match[2], 10), col: parseInt(match[3], 10) }
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Extract variable name from source line using regex.
|
|
54
|
+
* Returns null if extraction fails.
|
|
55
|
+
*/
|
|
56
|
+
function extractVarName(source: string, line: number): string | null {
|
|
57
|
+
const lines = source.split("\n")
|
|
58
|
+
if (line < 1 || line > lines.length) return null
|
|
59
|
+
|
|
60
|
+
const sourceLine = lines[line - 1]
|
|
61
|
+
const match = sourceLine.match(/(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/)
|
|
62
|
+
return match?.[1] ?? null
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Auto-keyed HMR persistence for atoms.
|
|
67
|
+
*
|
|
68
|
+
* When used with the HMR plugin (recommended), keys are injected at load time
|
|
69
|
+
* based on the variable name. Without the plugin, falls back to runtime
|
|
70
|
+
* stack trace parsing.
|
|
71
|
+
*/
|
|
72
|
+
export function autoHmr<A>(self: A): A {
|
|
73
|
+
// Get stack trace for caller location
|
|
74
|
+
const stack = new Error().stack ?? ""
|
|
75
|
+
const location = parseStack(stack)
|
|
76
|
+
|
|
77
|
+
if (!location) {
|
|
78
|
+
// Fallback: use a hash of the stack trace
|
|
79
|
+
const fallbackKey = `unknown:${stack.slice(0, 100)}`
|
|
80
|
+
return hmr(fallbackKey)(self)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const cacheKey = `${location.file}:${location.line}:${location.col}`
|
|
84
|
+
|
|
85
|
+
// Check cache first
|
|
86
|
+
let key = keyCache.get(cacheKey)
|
|
87
|
+
if (!key) {
|
|
88
|
+
try {
|
|
89
|
+
// Read source file and extract variable name
|
|
90
|
+
const source = readFileSync(location.file, "utf-8")
|
|
91
|
+
const varName = extractVarName(source, location.line)
|
|
92
|
+
key = `${location.file}:${varName ?? `line${location.line}`}`
|
|
93
|
+
} catch {
|
|
94
|
+
// File read failed, use line-based key
|
|
95
|
+
key = `${location.file}:line${location.line}`
|
|
96
|
+
}
|
|
97
|
+
keyCache.set(cacheKey, key)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return hmr(key)(self)
|
|
101
|
+
}
|