@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/hooks/use-scroll.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import type { KeyMsg } from "@effect-tui/core"
|
|
4
4
|
import { useCallback, useLayoutEffect, useMemo, useReducer, useRef } from "react"
|
|
5
|
-
import type { ScrollProps } from "../hosts/scroll.js"
|
|
5
|
+
import type { ScrollAlign, ScrollAxis, ScrollLayoutChange, ScrollProps } from "../hosts/scroll.js"
|
|
6
6
|
import { useTerminalSize } from "../renderer.js"
|
|
7
7
|
import { useKeyboard } from "./use-keyboard.js"
|
|
8
8
|
|
|
@@ -86,21 +86,21 @@ class MacOSScrollAccel implements ScrollAcceleration {
|
|
|
86
86
|
// ============================================================================
|
|
87
87
|
|
|
88
88
|
export interface ScrollState {
|
|
89
|
-
/** Current
|
|
90
|
-
|
|
91
|
-
/** Maximum
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
|
|
95
|
-
/** Whether viewport size has been measured by the host
|
|
96
|
-
|
|
97
|
-
/** Total content size
|
|
98
|
-
|
|
99
|
-
/** Whether we're at the
|
|
100
|
-
|
|
101
|
-
/** Whether we're at the
|
|
102
|
-
|
|
103
|
-
/** Current horizontal offset (pixels from
|
|
89
|
+
/** Current vertical offset (pixels from top) */
|
|
90
|
+
offsetY: number
|
|
91
|
+
/** Maximum vertical offset */
|
|
92
|
+
maxOffsetY: number
|
|
93
|
+
/** Vertical viewport size */
|
|
94
|
+
viewportSizeY: number
|
|
95
|
+
/** Whether vertical viewport size has been measured by the host */
|
|
96
|
+
viewportMeasuredY: boolean
|
|
97
|
+
/** Total vertical content size */
|
|
98
|
+
contentSizeY: number
|
|
99
|
+
/** Whether we're at the top edge */
|
|
100
|
+
atStartY: boolean
|
|
101
|
+
/** Whether we're at the bottom edge */
|
|
102
|
+
atEndY: boolean
|
|
103
|
+
/** Current horizontal offset (pixels from left) */
|
|
104
104
|
offsetX: number
|
|
105
105
|
/** Maximum horizontal offset */
|
|
106
106
|
maxOffsetX: number
|
|
@@ -118,31 +118,25 @@ export interface ScrollState {
|
|
|
118
118
|
|
|
119
119
|
export interface UseScrollOptions {
|
|
120
120
|
/** Scroll axis: "vertical" (default), "horizontal", or "both" */
|
|
121
|
-
axis?:
|
|
122
|
-
/** Controlled content size for the primary axis (skips host measurement when provided) */
|
|
123
|
-
contentSize?: number
|
|
121
|
+
axis?: ScrollAxis
|
|
124
122
|
/** Controlled content width (skips host measurement when provided) */
|
|
125
123
|
contentWidth?: number
|
|
126
124
|
/** Controlled content height (skips host measurement when provided) */
|
|
127
125
|
contentHeight?: number
|
|
128
|
-
/** @internal Initial viewport size override (useful for tests) */
|
|
129
|
-
initialViewportSize?: number
|
|
130
126
|
/** @internal Initial viewport width override (useful for tests) */
|
|
131
127
|
initialViewportWidth?: number
|
|
132
128
|
/** @internal Initial viewport height override (useful for tests) */
|
|
133
129
|
initialViewportHeight?: number
|
|
134
|
-
/** @internal Initial content size override (useful for tests) */
|
|
135
|
-
initialContentSize?: number
|
|
136
130
|
/** @internal Initial content width override (useful for tests) */
|
|
137
131
|
initialContentWidth?: number
|
|
138
132
|
/** @internal Initial content height override (useful for tests) */
|
|
139
133
|
initialContentHeight?: number
|
|
140
|
-
/** Initial scroll offset
|
|
141
|
-
|
|
134
|
+
/** Initial vertical scroll offset */
|
|
135
|
+
initialOffsetY?: number
|
|
142
136
|
/** Initial horizontal scroll offset */
|
|
143
137
|
initialOffsetX?: number
|
|
144
138
|
/** Alignment when content is smaller than viewport */
|
|
145
|
-
align?:
|
|
139
|
+
align?: ScrollAlign
|
|
146
140
|
/** Whether to show scrollbars */
|
|
147
141
|
showScrollbar?: boolean
|
|
148
142
|
/** Enable keyboard navigation (default: true) */
|
|
@@ -157,23 +151,25 @@ export interface UseScrollOptions {
|
|
|
157
151
|
arrowSpeed?: number
|
|
158
152
|
/** Scroll speed for page up/down (fraction of viewport, default: 0.5) */
|
|
159
153
|
pageSpeed?: number
|
|
154
|
+
/** Optional layout callback (content/viewport/offset/rect). */
|
|
155
|
+
onScrollLayoutChange?: (event: ScrollLayoutChange) => void
|
|
160
156
|
}
|
|
161
157
|
|
|
162
158
|
export interface UseScrollReturn {
|
|
163
159
|
/** Current scroll state */
|
|
164
160
|
state: ScrollState
|
|
165
|
-
/** Set scroll offset directly
|
|
166
|
-
|
|
161
|
+
/** Set vertical scroll offset directly */
|
|
162
|
+
setOffsetY: (offsetY: number) => void
|
|
167
163
|
/** Set horizontal scroll offset directly */
|
|
168
164
|
setOffsetX: (offsetX: number) => void
|
|
169
|
-
/** Scroll by delta pixels
|
|
170
|
-
|
|
165
|
+
/** Scroll by delta pixels vertically */
|
|
166
|
+
scrollByY: (delta: number) => void
|
|
171
167
|
/** Scroll by delta pixels horizontally */
|
|
172
168
|
scrollByX: (delta: number) => void
|
|
173
|
-
/** Scroll to
|
|
174
|
-
|
|
175
|
-
/** Scroll to
|
|
176
|
-
|
|
169
|
+
/** Scroll to top */
|
|
170
|
+
scrollToStartY: () => void
|
|
171
|
+
/** Scroll to bottom */
|
|
172
|
+
scrollToEndY: () => void
|
|
177
173
|
/** Scroll to horizontal start */
|
|
178
174
|
scrollToStartX: () => void
|
|
179
175
|
/** Scroll to horizontal end */
|
|
@@ -187,7 +183,7 @@ export interface UseScrollReturn {
|
|
|
187
183
|
* @param totalSize - Optional known total content size (avoids stale state issues)
|
|
188
184
|
* @param axis - Axis to scroll (defaults to vertical)
|
|
189
185
|
*/
|
|
190
|
-
|
|
186
|
+
scrollIntoView: (
|
|
191
187
|
position: number,
|
|
192
188
|
itemSize?: number,
|
|
193
189
|
padding?: number,
|
|
@@ -287,18 +283,15 @@ const reduceScroll = (state: ScrollInternalState, action: ScrollAction): ScrollI
|
|
|
287
283
|
export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
288
284
|
const {
|
|
289
285
|
axis = "vertical",
|
|
290
|
-
contentSize,
|
|
291
286
|
contentWidth,
|
|
292
287
|
contentHeight,
|
|
293
|
-
initialViewportSize,
|
|
294
288
|
initialViewportWidth,
|
|
295
289
|
initialViewportHeight,
|
|
296
|
-
initialContentSize,
|
|
297
290
|
initialContentWidth,
|
|
298
291
|
initialContentHeight,
|
|
299
|
-
|
|
292
|
+
initialOffsetY = 0,
|
|
300
293
|
initialOffsetX = 0,
|
|
301
|
-
align
|
|
294
|
+
align,
|
|
302
295
|
showScrollbar = true,
|
|
303
296
|
enableKeyboard = true,
|
|
304
297
|
enableMouseWheel = true,
|
|
@@ -306,31 +299,30 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
306
299
|
sticky = false,
|
|
307
300
|
arrowSpeed = 1,
|
|
308
301
|
pageSpeed = 0.5,
|
|
302
|
+
onScrollLayoutChange,
|
|
309
303
|
} = options
|
|
310
304
|
|
|
311
305
|
const { width: termWidth, height: termHeight } = useTerminalSize()
|
|
312
306
|
const enableY = axis === "vertical" || axis === "both"
|
|
313
307
|
const enableX = axis === "horizontal" || axis === "both"
|
|
314
|
-
const
|
|
308
|
+
const useHorizontalPrimary = axis === "horizontal"
|
|
315
309
|
|
|
316
|
-
const controlledContentHeight = contentHeight
|
|
317
|
-
const controlledContentWidth = contentWidth
|
|
310
|
+
const controlledContentHeight = contentHeight
|
|
311
|
+
const controlledContentWidth = contentWidth
|
|
318
312
|
|
|
319
|
-
const baseViewportHeight =
|
|
320
|
-
|
|
321
|
-
const baseViewportWidth =
|
|
322
|
-
initialViewportWidth ?? (axis === "horizontal" ? initialViewportSize : undefined) ?? termWidth
|
|
313
|
+
const baseViewportHeight = initialViewportHeight ?? termHeight
|
|
314
|
+
const baseViewportWidth = initialViewportWidth ?? termWidth
|
|
323
315
|
|
|
324
316
|
const baseContentHeight =
|
|
325
|
-
controlledContentHeight ?? initialContentHeight ??
|
|
317
|
+
controlledContentHeight ?? initialContentHeight ?? 0
|
|
326
318
|
const baseContentWidth =
|
|
327
|
-
controlledContentWidth ?? initialContentWidth ??
|
|
319
|
+
controlledContentWidth ?? initialContentWidth ?? 0
|
|
328
320
|
|
|
329
|
-
const
|
|
330
|
-
const initialOffsetXResolved = axis === "
|
|
321
|
+
const initialOffsetYResolved = axis === "horizontal" ? 0 : initialOffsetY
|
|
322
|
+
const initialOffsetXResolved = axis === "vertical" ? 0 : initialOffsetX
|
|
331
323
|
|
|
332
324
|
const [internalY, dispatchY] = useReducer(reduceScroll, {
|
|
333
|
-
offset: clampOffset(
|
|
325
|
+
offset: clampOffset(initialOffsetYResolved, baseContentHeight, baseViewportHeight),
|
|
334
326
|
contentSize: baseContentHeight,
|
|
335
327
|
viewportSize: baseViewportHeight,
|
|
336
328
|
viewportMeasured: false,
|
|
@@ -398,17 +390,6 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
398
390
|
[dispatchX, enableX],
|
|
399
391
|
)
|
|
400
392
|
|
|
401
|
-
const scrollBy = useCallback(
|
|
402
|
-
(delta: number) => {
|
|
403
|
-
if (primaryAxis === "horizontal") {
|
|
404
|
-
scrollByX(delta)
|
|
405
|
-
} else {
|
|
406
|
-
scrollByY(delta)
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
[primaryAxis, scrollByX, scrollByY],
|
|
410
|
-
)
|
|
411
|
-
|
|
412
393
|
const scrollToStartY = useCallback(() => {
|
|
413
394
|
dispatchY({ type: "set-offset", offset: 0 })
|
|
414
395
|
accumulatorYRef.current = 0
|
|
@@ -421,14 +402,6 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
421
402
|
accelX.reset()
|
|
422
403
|
}, [dispatchX, accelX])
|
|
423
404
|
|
|
424
|
-
const scrollToStart = useCallback(() => {
|
|
425
|
-
if (primaryAxis === "horizontal") {
|
|
426
|
-
scrollToStartX()
|
|
427
|
-
} else {
|
|
428
|
-
scrollToStartY()
|
|
429
|
-
}
|
|
430
|
-
}, [primaryAxis, scrollToStartX, scrollToStartY])
|
|
431
|
-
|
|
432
405
|
const scrollToEndY = useCallback(() => {
|
|
433
406
|
const current = stateYRef.current
|
|
434
407
|
const maxOffset = Math.max(0, current.contentSize - current.viewportSize)
|
|
@@ -445,27 +418,6 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
445
418
|
accelX.reset()
|
|
446
419
|
}, [dispatchX, accelX])
|
|
447
420
|
|
|
448
|
-
const scrollToEnd = useCallback(() => {
|
|
449
|
-
if (primaryAxis === "horizontal") {
|
|
450
|
-
scrollToEndX()
|
|
451
|
-
} else {
|
|
452
|
-
scrollToEndY()
|
|
453
|
-
}
|
|
454
|
-
}, [primaryAxis, scrollToEndX, scrollToEndY])
|
|
455
|
-
|
|
456
|
-
// Handle content size changes (for sticky scroll)
|
|
457
|
-
const handleContentSize = useCallback(
|
|
458
|
-
(width: number, height: number) => {
|
|
459
|
-
if (enableY && controlledContentHeight === undefined) {
|
|
460
|
-
dispatchY({ type: "set-content", size: height })
|
|
461
|
-
}
|
|
462
|
-
if (enableX && controlledContentWidth === undefined) {
|
|
463
|
-
dispatchX({ type: "set-content", size: width })
|
|
464
|
-
}
|
|
465
|
-
},
|
|
466
|
-
[controlledContentHeight, controlledContentWidth, dispatchX, dispatchY, enableX, enableY],
|
|
467
|
-
)
|
|
468
|
-
|
|
469
421
|
useLayoutEffect(() => {
|
|
470
422
|
if (!enableY || controlledContentHeight === undefined) return
|
|
471
423
|
dispatchY({ type: "set-content", size: controlledContentHeight })
|
|
@@ -476,19 +428,35 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
476
428
|
dispatchX({ type: "set-content", size: controlledContentWidth })
|
|
477
429
|
}, [controlledContentWidth, dispatchX, enableX])
|
|
478
430
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
(width: number, height: number) => {
|
|
431
|
+
const handleScrollLayout = useCallback(
|
|
432
|
+
(event: ScrollLayoutChange) => {
|
|
482
433
|
if (enableY) {
|
|
483
|
-
viewportYRef.current = height
|
|
484
|
-
dispatchY({ type: "set-viewport", size: height })
|
|
434
|
+
viewportYRef.current = event.viewport.height
|
|
435
|
+
dispatchY({ type: "set-viewport", size: event.viewport.height })
|
|
436
|
+
if (controlledContentHeight === undefined) {
|
|
437
|
+
dispatchY({ type: "set-content", size: event.content.height })
|
|
438
|
+
}
|
|
439
|
+
dispatchY({ type: "sync-effective-offset", offset: event.offset.y })
|
|
485
440
|
}
|
|
486
441
|
if (enableX) {
|
|
487
|
-
viewportXRef.current = width
|
|
488
|
-
dispatchX({ type: "set-viewport", size: width })
|
|
442
|
+
viewportXRef.current = event.viewport.width
|
|
443
|
+
dispatchX({ type: "set-viewport", size: event.viewport.width })
|
|
444
|
+
if (controlledContentWidth === undefined) {
|
|
445
|
+
dispatchX({ type: "set-content", size: event.content.width })
|
|
446
|
+
}
|
|
447
|
+
dispatchX({ type: "sync-effective-offset", offset: event.offset.x })
|
|
489
448
|
}
|
|
449
|
+
onScrollLayoutChange?.(event)
|
|
490
450
|
},
|
|
491
|
-
[
|
|
451
|
+
[
|
|
452
|
+
controlledContentHeight,
|
|
453
|
+
controlledContentWidth,
|
|
454
|
+
dispatchX,
|
|
455
|
+
dispatchY,
|
|
456
|
+
enableX,
|
|
457
|
+
enableY,
|
|
458
|
+
onScrollLayoutChange,
|
|
459
|
+
],
|
|
492
460
|
)
|
|
493
461
|
|
|
494
462
|
// Keyboard handler
|
|
@@ -497,7 +465,7 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
497
465
|
// Mouse wheel comes as pageup/pagedown with meta=true
|
|
498
466
|
// Handle separately from keyboard since enableKeyboard shouldn't disable mouse
|
|
499
467
|
if (key.meta && enableMouseWheel && (key.name === "pageup" || key.name === "pagedown")) {
|
|
500
|
-
const multiplier =
|
|
468
|
+
const multiplier = useHorizontalPrimary && !enableY ? accelX.tick() : accelY.tick()
|
|
501
469
|
const delta = Math.ceil(arrowSpeed * multiplier)
|
|
502
470
|
if (axis === "horizontal") {
|
|
503
471
|
scrollByX(key.name === "pageup" ? -delta : delta)
|
|
@@ -569,7 +537,7 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
569
537
|
accelX,
|
|
570
538
|
accelY,
|
|
571
539
|
enableY,
|
|
572
|
-
|
|
540
|
+
useHorizontalPrimary,
|
|
573
541
|
scrollByX,
|
|
574
542
|
scrollByY,
|
|
575
543
|
scrollToEndX,
|
|
@@ -585,7 +553,7 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
585
553
|
// Uses refs to avoid stale closures, but re-creates when viewport size changes
|
|
586
554
|
// so selection effects can re-run after measurement updates.
|
|
587
555
|
// Bypasses clampOffset because it uses totalSize for accurate clamping
|
|
588
|
-
const
|
|
556
|
+
const scrollIntoView = useCallback(
|
|
589
557
|
(
|
|
590
558
|
position: number,
|
|
591
559
|
itemSize = 1,
|
|
@@ -633,17 +601,6 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
633
601
|
[dispatchX],
|
|
634
602
|
)
|
|
635
603
|
|
|
636
|
-
const setOffset = useCallback(
|
|
637
|
-
(newOffset: number) => {
|
|
638
|
-
if (primaryAxis === "horizontal") {
|
|
639
|
-
setOffsetX(newOffset)
|
|
640
|
-
} else {
|
|
641
|
-
setOffsetY(newOffset)
|
|
642
|
-
}
|
|
643
|
-
},
|
|
644
|
-
[primaryAxis, setOffsetX, setOffsetY],
|
|
645
|
-
)
|
|
646
|
-
|
|
647
604
|
// Calculate derived state
|
|
648
605
|
const maxOffsetY = Math.max(0, internalY.contentSize - internalY.viewportSize)
|
|
649
606
|
const maxOffsetX = Math.max(0, internalX.contentSize - internalX.viewportSize)
|
|
@@ -652,19 +609,14 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
652
609
|
const atStartX = internalX.offset <= 0
|
|
653
610
|
const atEndX = internalX.offset >= maxOffsetX
|
|
654
611
|
|
|
655
|
-
const primaryState = primaryAxis === "horizontal" ? internalX : internalY
|
|
656
|
-
const primaryMaxOffset = primaryAxis === "horizontal" ? maxOffsetX : maxOffsetY
|
|
657
|
-
const primaryAtStart = primaryAxis === "horizontal" ? atStartX : atStartY
|
|
658
|
-
const primaryAtEnd = primaryAxis === "horizontal" ? atEndX : atEndY
|
|
659
|
-
|
|
660
612
|
const state: ScrollState = {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
613
|
+
offsetY: internalY.offset,
|
|
614
|
+
maxOffsetY,
|
|
615
|
+
viewportSizeY: internalY.viewportSize,
|
|
616
|
+
viewportMeasuredY: internalY.viewportMeasured,
|
|
617
|
+
contentSizeY: internalY.contentSize,
|
|
618
|
+
atStartY,
|
|
619
|
+
atEndY,
|
|
668
620
|
offsetX: internalX.offset,
|
|
669
621
|
maxOffsetX,
|
|
670
622
|
viewportSizeX: internalX.viewportSize,
|
|
@@ -674,39 +626,27 @@ export function useScroll(options: UseScrollOptions = {}): UseScrollReturn {
|
|
|
674
626
|
atEndX,
|
|
675
627
|
}
|
|
676
628
|
|
|
677
|
-
// Handle effective offset sync from host (when sticky adjusts the offset)
|
|
678
|
-
const handleEffectiveOffset = useCallback((effectiveOffset: number) => {
|
|
679
|
-
dispatchY({ type: "sync-effective-offset", offset: effectiveOffset })
|
|
680
|
-
}, [dispatchY])
|
|
681
|
-
|
|
682
|
-
const handleEffectiveOffsetX = useCallback((effectiveOffsetX: number) => {
|
|
683
|
-
dispatchX({ type: "sync-effective-offset", offset: effectiveOffsetX })
|
|
684
|
-
}, [dispatchX])
|
|
685
|
-
|
|
686
629
|
const scrollProps: ScrollProps = {
|
|
687
|
-
|
|
630
|
+
offsetY: enableY ? internalY.offset : 0,
|
|
688
631
|
offsetX: enableX ? internalX.offset : 0,
|
|
689
632
|
axis,
|
|
690
633
|
align,
|
|
691
634
|
sticky,
|
|
692
635
|
showScrollbar,
|
|
693
|
-
|
|
694
|
-
onViewportSize: handleViewportSize,
|
|
695
|
-
onEffectiveOffset: enableY ? handleEffectiveOffset : undefined,
|
|
696
|
-
onEffectiveOffsetX: enableX ? handleEffectiveOffsetX : undefined,
|
|
636
|
+
onScrollLayoutChange: handleScrollLayout,
|
|
697
637
|
}
|
|
698
638
|
|
|
699
639
|
return {
|
|
700
640
|
state,
|
|
701
|
-
|
|
641
|
+
setOffsetY,
|
|
702
642
|
setOffsetX,
|
|
703
|
-
|
|
643
|
+
scrollByY,
|
|
704
644
|
scrollByX,
|
|
705
|
-
|
|
706
|
-
|
|
645
|
+
scrollToStartY,
|
|
646
|
+
scrollToEndY,
|
|
707
647
|
scrollToStartX,
|
|
708
648
|
scrollToEndX,
|
|
709
|
-
|
|
649
|
+
scrollIntoView,
|
|
710
650
|
scrollProps,
|
|
711
651
|
}
|
|
712
652
|
}
|
package/src/hosts/base.ts
CHANGED
|
@@ -67,6 +67,12 @@ export abstract class BaseHost implements HostInstance {
|
|
|
67
67
|
parent: HostInstance | null = null
|
|
68
68
|
children: HostInstance[] = []
|
|
69
69
|
rect: Rect | null = null
|
|
70
|
+
private _layoutDirty = true
|
|
71
|
+
private _renderDirty = true
|
|
72
|
+
private _lastMeasureW = -1
|
|
73
|
+
private _lastMeasureH = -1
|
|
74
|
+
private _lastMeasuredSize: Size | null = null
|
|
75
|
+
private _lastLayoutRect: Rect | null = null
|
|
70
76
|
|
|
71
77
|
// Greedy layout - expands to fill remaining space
|
|
72
78
|
// undefined = not greedy (hug content)
|
|
@@ -117,17 +123,38 @@ export abstract class BaseHost implements HostInstance {
|
|
|
117
123
|
* Optional pre-frame hook. BaseHost will call prepareSelf() and then recurse into children.
|
|
118
124
|
*/
|
|
119
125
|
prepareFrame(): void {
|
|
120
|
-
this.
|
|
126
|
+
this.ensurePrepared()
|
|
121
127
|
for (const child of this.children) {
|
|
122
128
|
child.prepareFrame?.()
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
|
|
126
132
|
/** Override in subclasses to precompute caches once per frame. */
|
|
127
|
-
protected prepareSelf(): void {
|
|
133
|
+
protected prepareSelf(_layoutDirty: boolean, _renderDirty: boolean): void {
|
|
128
134
|
// Default no-op
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
protected ensurePrepared(): void {
|
|
138
|
+
if (!this._layoutDirty && !this._renderDirty) return
|
|
139
|
+
const layoutDirty = this._layoutDirty
|
|
140
|
+
const renderDirty = this._renderDirty
|
|
141
|
+
this._renderDirty = false
|
|
142
|
+
this.prepareSelf(layoutDirty, renderDirty)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
invalidateLayout(): void {
|
|
146
|
+
this._layoutDirty = true
|
|
147
|
+
this._renderDirty = true
|
|
148
|
+
this._lastMeasuredSize = null
|
|
149
|
+
this.ctx.requestRender()
|
|
150
|
+
this.parent?.invalidateLayout?.()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
invalidateRender(): void {
|
|
154
|
+
this._renderDirty = true
|
|
155
|
+
this.ctx.requestRender()
|
|
156
|
+
}
|
|
157
|
+
|
|
131
158
|
/**
|
|
132
159
|
* Resolve a prop that may be a MotionValue/ColorMotionValue.
|
|
133
160
|
* If it's a spring, subscribes to changes and returns current value.
|
|
@@ -204,10 +231,39 @@ export abstract class BaseHost implements HostInstance {
|
|
|
204
231
|
this._springSubscriptions.clear()
|
|
205
232
|
}
|
|
206
233
|
|
|
207
|
-
|
|
234
|
+
measure(maxW: number, maxH: number): Size {
|
|
235
|
+
const constraintsUnchanged = this._lastMeasureW === maxW && this._lastMeasureH === maxH
|
|
236
|
+
if (!this._layoutDirty && constraintsUnchanged && this._lastMeasuredSize) {
|
|
237
|
+
return this._lastMeasuredSize
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!constraintsUnchanged) {
|
|
241
|
+
this._layoutDirty = true
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const size = this.measureSelf(maxW, maxH)
|
|
245
|
+
this._lastMeasureW = maxW
|
|
246
|
+
this._lastMeasureH = maxH
|
|
247
|
+
this._lastMeasuredSize = size
|
|
248
|
+
return size
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
protected abstract measureSelf(maxW: number, maxH: number): Size
|
|
208
252
|
abstract render(buffer: CellBuffer, palette: Palette): void
|
|
209
253
|
|
|
210
254
|
layout(rect: Rect): void {
|
|
255
|
+
const prev = this._lastLayoutRect
|
|
256
|
+
const rectUnchanged =
|
|
257
|
+
prev?.x === rect.x && prev?.y === rect.y && prev?.w === rect.w && prev?.h === rect.h
|
|
258
|
+
|
|
259
|
+
if (!this._layoutDirty && rectUnchanged) return
|
|
260
|
+
|
|
261
|
+
this.layoutSelf(rect)
|
|
262
|
+
this._layoutDirty = false
|
|
263
|
+
this._lastLayoutRect = this.rect ?? rect
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
protected layoutSelf(rect: Rect): void {
|
|
211
267
|
this.layoutWithConstraints(rect)
|
|
212
268
|
}
|
|
213
269
|
|
|
@@ -319,6 +375,7 @@ export abstract class BaseHost implements HostInstance {
|
|
|
319
375
|
updateProps(props: Record<string, unknown>): void {
|
|
320
376
|
// Greedy layout - reset to undefined unless explicitly set
|
|
321
377
|
// Subclasses like Spacer/Scroll set their own default before calling super
|
|
378
|
+
const prevGreedy = this.greedy
|
|
322
379
|
if ("greedy" in props) {
|
|
323
380
|
const greedy = props.greedy
|
|
324
381
|
if (greedy === true) {
|
|
@@ -336,6 +393,12 @@ export abstract class BaseHost implements HostInstance {
|
|
|
336
393
|
}
|
|
337
394
|
|
|
338
395
|
// Frame constraints - only accept valid numbers (ignore strings like "100%")
|
|
396
|
+
const prevFrameWidth = this.frameWidth
|
|
397
|
+
const prevFrameHeight = this.frameHeight
|
|
398
|
+
const prevFrameMinWidth = this.frameMinWidth
|
|
399
|
+
const prevFrameMaxWidth = this.frameMaxWidth
|
|
400
|
+
const prevFrameMinHeight = this.frameMinHeight
|
|
401
|
+
const prevFrameMaxHeight = this.frameMaxHeight
|
|
339
402
|
this.frameWidth = typeof props.width === "number" ? props.width : undefined
|
|
340
403
|
this.frameHeight = typeof props.height === "number" ? props.height : undefined
|
|
341
404
|
this.frameMinWidth = typeof props.minWidth === "number" ? props.minWidth : undefined
|
|
@@ -345,6 +408,19 @@ export abstract class BaseHost implements HostInstance {
|
|
|
345
408
|
|
|
346
409
|
// onLayout callback
|
|
347
410
|
this.onLayout = typeof props.onLayout === "function" ? (props.onLayout as typeof this.onLayout) : undefined
|
|
411
|
+
|
|
412
|
+
const layoutChanged =
|
|
413
|
+
prevGreedy !== this.greedy ||
|
|
414
|
+
prevFrameWidth !== this.frameWidth ||
|
|
415
|
+
prevFrameHeight !== this.frameHeight ||
|
|
416
|
+
prevFrameMinWidth !== this.frameMinWidth ||
|
|
417
|
+
prevFrameMaxWidth !== this.frameMaxWidth ||
|
|
418
|
+
prevFrameMinHeight !== this.frameMinHeight ||
|
|
419
|
+
prevFrameMaxHeight !== this.frameMaxHeight
|
|
420
|
+
|
|
421
|
+
if (layoutChanged) {
|
|
422
|
+
this.invalidateLayout()
|
|
423
|
+
}
|
|
348
424
|
}
|
|
349
425
|
|
|
350
426
|
/**
|
|
@@ -353,7 +429,11 @@ export abstract class BaseHost implements HostInstance {
|
|
|
353
429
|
*/
|
|
354
430
|
protected applyGreedyDefault(props: Record<string, unknown>, fallback: number): void {
|
|
355
431
|
if (!("greedy" in props)) {
|
|
432
|
+
const prevGreedy = this.greedy
|
|
356
433
|
this.greedy = fallback
|
|
434
|
+
if (prevGreedy !== this.greedy) {
|
|
435
|
+
this.invalidateLayout()
|
|
436
|
+
}
|
|
357
437
|
}
|
|
358
438
|
}
|
|
359
439
|
|
|
@@ -366,6 +446,7 @@ export abstract class BaseHost implements HostInstance {
|
|
|
366
446
|
appendChild(child: HostInstance): void {
|
|
367
447
|
this.children.push(child)
|
|
368
448
|
child.parent = this
|
|
449
|
+
this.invalidateLayout()
|
|
369
450
|
}
|
|
370
451
|
|
|
371
452
|
removeChild(child: HostInstance): void {
|
|
@@ -374,6 +455,7 @@ export abstract class BaseHost implements HostInstance {
|
|
|
374
455
|
this.children.splice(idx, 1)
|
|
375
456
|
child.parent = null
|
|
376
457
|
}
|
|
458
|
+
this.invalidateLayout()
|
|
377
459
|
}
|
|
378
460
|
|
|
379
461
|
insertBefore(child: HostInstance, before: HostInstance): void {
|
|
@@ -388,6 +470,7 @@ export abstract class BaseHost implements HostInstance {
|
|
|
388
470
|
this.children.push(child)
|
|
389
471
|
}
|
|
390
472
|
child.parent = this
|
|
473
|
+
this.invalidateLayout()
|
|
391
474
|
}
|
|
392
475
|
|
|
393
476
|
/**
|
package/src/hosts/box.ts
CHANGED
|
@@ -65,7 +65,7 @@ export class BoxHost extends SingleChildHost {
|
|
|
65
65
|
return this.borderThickness + titleHeight + this.padding.top + this.padding.bottom + this.borderThickness
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
protected measureSelf(maxW: number, maxH: number): Size {
|
|
69
69
|
// Apply frame constraints first
|
|
70
70
|
const constrained = this.constrainProposal(maxW, maxH)
|
|
71
71
|
|
|
@@ -91,7 +91,7 @@ export class BoxHost extends SingleChildHost {
|
|
|
91
91
|
return this.constrainResult(naturalSize)
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
override
|
|
94
|
+
protected override layoutSelf(rect: Rect): void {
|
|
95
95
|
const layoutRect = this.layoutWithConstraints(rect)
|
|
96
96
|
|
|
97
97
|
const t = this.borderThickness
|
|
@@ -173,6 +173,15 @@ export class BoxHost extends SingleChildHost {
|
|
|
173
173
|
|
|
174
174
|
override updateProps(props: Record<string, unknown>): void {
|
|
175
175
|
super.updateProps(props)
|
|
176
|
+
const prevPadding = this.padding
|
|
177
|
+
const prevBorder = this.border
|
|
178
|
+
const prevBorderColor = this.borderColor
|
|
179
|
+
const prevBg = this.bg
|
|
180
|
+
const prevTitle = this.title
|
|
181
|
+
const prevTitleColor = this.titleColor
|
|
182
|
+
const prevTitleBold = this.titleBold
|
|
183
|
+
const prevTitleDivider = this.titleDivider
|
|
184
|
+
|
|
176
185
|
this.padding = resolvePadding(props.padding as BoxProps["padding"])
|
|
177
186
|
this.border = (props.border as BorderKind | undefined) ?? "none"
|
|
178
187
|
// Color props support MotionValue/ColorMotionValue - auto-subscribe and animate
|
|
@@ -188,5 +197,31 @@ export class BoxHost extends SingleChildHost {
|
|
|
188
197
|
}) as Color | undefined
|
|
189
198
|
this.titleBold = Boolean(props.titleBold)
|
|
190
199
|
this.titleDivider = Boolean(props.titleDivider)
|
|
200
|
+
|
|
201
|
+
const paddingChanged =
|
|
202
|
+
prevPadding.top !== this.padding.top ||
|
|
203
|
+
prevPadding.right !== this.padding.right ||
|
|
204
|
+
prevPadding.bottom !== this.padding.bottom ||
|
|
205
|
+
prevPadding.left !== this.padding.left
|
|
206
|
+
const layoutChanged =
|
|
207
|
+
paddingChanged ||
|
|
208
|
+
prevBorder !== this.border ||
|
|
209
|
+
prevTitle !== this.title ||
|
|
210
|
+
prevTitleDivider !== this.titleDivider
|
|
211
|
+
|
|
212
|
+
if (layoutChanged) {
|
|
213
|
+
this.invalidateLayout()
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const renderChanged =
|
|
218
|
+
prevBorderColor !== this.borderColor ||
|
|
219
|
+
prevBg !== this.bg ||
|
|
220
|
+
prevTitleColor !== this.titleColor ||
|
|
221
|
+
prevTitleBold !== this.titleBold
|
|
222
|
+
|
|
223
|
+
if (renderChanged) {
|
|
224
|
+
this.invalidateRender()
|
|
225
|
+
}
|
|
191
226
|
}
|
|
192
227
|
}
|