@effect-tui/core 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +93 -0
- package/dist/anim.d.ts +4 -0
- package/dist/anim.d.ts.map +1 -0
- package/dist/anim.js +5 -0
- package/dist/anim.js.map +1 -0
- package/dist/ansi.d.ts +69 -0
- package/dist/ansi.d.ts.map +1 -0
- package/dist/ansi.js +72 -0
- package/dist/ansi.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +18 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +247 -0
- package/dist/keys.js.map +1 -0
- package/dist/layout/linearStack.d.ts +17 -0
- package/dist/layout/linearStack.d.ts.map +1 -0
- package/dist/layout/linearStack.js +86 -0
- package/dist/layout/linearStack.js.map +1 -0
- package/dist/motion-value.d.ts +58 -0
- package/dist/motion-value.d.ts.map +1 -0
- package/dist/motion-value.js +250 -0
- package/dist/motion-value.js.map +1 -0
- package/dist/present/display.d.ts +58 -0
- package/dist/present/display.d.ts.map +1 -0
- package/dist/present/display.js +168 -0
- package/dist/present/display.js.map +1 -0
- package/dist/present/writers/fullscreen.d.ts +19 -0
- package/dist/present/writers/fullscreen.d.ts.map +1 -0
- package/dist/present/writers/fullscreen.js +55 -0
- package/dist/present/writers/fullscreen.js.map +1 -0
- package/dist/present/writers/inline.d.ts +20 -0
- package/dist/present/writers/inline.d.ts.map +1 -0
- package/dist/present/writers/inline.js +92 -0
- package/dist/present/writers/inline.js.map +1 -0
- package/dist/render/buffer.d.ts +31 -0
- package/dist/render/buffer.d.ts.map +1 -0
- package/dist/render/buffer.js +183 -0
- package/dist/render/buffer.js.map +1 -0
- package/dist/render/color-utils.d.ts +18 -0
- package/dist/render/color-utils.d.ts.map +1 -0
- package/dist/render/color-utils.js +58 -0
- package/dist/render/color-utils.js.map +1 -0
- package/dist/render/diff.d.ts +30 -0
- package/dist/render/diff.d.ts.map +1 -0
- package/dist/render/diff.js +83 -0
- package/dist/render/diff.js.map +1 -0
- package/dist/render/measure.d.ts +15 -0
- package/dist/render/measure.d.ts.map +1 -0
- package/dist/render/measure.js +65 -0
- package/dist/render/measure.js.map +1 -0
- package/dist/render/palette.d.ts +46 -0
- package/dist/render/palette.d.ts.map +1 -0
- package/dist/render/palette.js +108 -0
- package/dist/render/palette.js.map +1 -0
- package/dist/render/surface.d.ts +77 -0
- package/dist/render/surface.d.ts.map +1 -0
- package/dist/render/surface.js +198 -0
- package/dist/render/surface.js.map +1 -0
- package/dist/runtime/backend_node.d.ts +36 -0
- package/dist/runtime/backend_node.d.ts.map +1 -0
- package/dist/runtime/backend_node.js +66 -0
- package/dist/runtime/backend_node.js.map +1 -0
- package/dist/spring-physics.d.ts +36 -0
- package/dist/spring-physics.d.ts.map +1 -0
- package/dist/spring-physics.js +113 -0
- package/dist/spring-physics.js.map +1 -0
- package/dist/spring.d.ts +73 -0
- package/dist/spring.d.ts.map +1 -0
- package/dist/spring.js +136 -0
- package/dist/spring.js.map +1 -0
- package/dist/ui/containers/canvas.d.ts +13 -0
- package/dist/ui/containers/canvas.d.ts.map +1 -0
- package/dist/ui/containers/canvas.js +16 -0
- package/dist/ui/containers/canvas.js.map +1 -0
- package/dist/ui/containers/geometry-reader.d.ts +17 -0
- package/dist/ui/containers/geometry-reader.d.ts.map +1 -0
- package/dist/ui/containers/geometry-reader.js +24 -0
- package/dist/ui/containers/geometry-reader.js.map +1 -0
- package/dist/ui/containers/hstack.d.ts +12 -0
- package/dist/ui/containers/hstack.d.ts.map +1 -0
- package/dist/ui/containers/hstack.js +28 -0
- package/dist/ui/containers/hstack.js.map +1 -0
- package/dist/ui/containers/scroll.d.ts +28 -0
- package/dist/ui/containers/scroll.d.ts.map +1 -0
- package/dist/ui/containers/scroll.js +97 -0
- package/dist/ui/containers/scroll.js.map +1 -0
- package/dist/ui/containers/shared.d.ts +12 -0
- package/dist/ui/containers/shared.d.ts.map +1 -0
- package/dist/ui/containers/shared.js +19 -0
- package/dist/ui/containers/shared.js.map +1 -0
- package/dist/ui/containers/vstack.d.ts +12 -0
- package/dist/ui/containers/vstack.d.ts.map +1 -0
- package/dist/ui/containers/vstack.js +28 -0
- package/dist/ui/containers/vstack.js.map +1 -0
- package/dist/ui/containers/zstack.d.ts +14 -0
- package/dist/ui/containers/zstack.d.ts.map +1 -0
- package/dist/ui/containers/zstack.js +36 -0
- package/dist/ui/containers/zstack.js.map +1 -0
- package/dist/ui/core/geometry-store.d.ts +22 -0
- package/dist/ui/core/geometry-store.d.ts.map +1 -0
- package/dist/ui/core/geometry-store.js +29 -0
- package/dist/ui/core/geometry-store.js.map +1 -0
- package/dist/ui/core/geometry.d.ts +34 -0
- package/dist/ui/core/geometry.d.ts.map +1 -0
- package/dist/ui/core/geometry.js +14 -0
- package/dist/ui/core/geometry.js.map +1 -0
- package/dist/ui/core/view.d.ts +25 -0
- package/dist/ui/core/view.d.ts.map +1 -0
- package/dist/ui/core/view.js +34 -0
- package/dist/ui/core/view.js.map +1 -0
- package/dist/ui/index.d.ts +44 -0
- package/dist/ui/index.d.ts.map +1 -0
- package/dist/ui/index.js +39 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/inlinetext.d.ts +24 -0
- package/dist/ui/inlinetext.d.ts.map +1 -0
- package/dist/ui/inlinetext.js +131 -0
- package/dist/ui/inlinetext.js.map +1 -0
- package/dist/ui/install.d.ts +22 -0
- package/dist/ui/install.d.ts.map +1 -0
- package/dist/ui/install.js +66 -0
- package/dist/ui/install.js.map +1 -0
- package/dist/ui/markdown.d.ts +40 -0
- package/dist/ui/markdown.d.ts.map +1 -0
- package/dist/ui/markdown.js +351 -0
- package/dist/ui/markdown.js.map +1 -0
- package/dist/ui/modifiers/border.d.ts +33 -0
- package/dist/ui/modifiers/border.d.ts.map +1 -0
- package/dist/ui/modifiers/border.js +82 -0
- package/dist/ui/modifiers/border.js.map +1 -0
- package/dist/ui/modifiers/fill.d.ts +14 -0
- package/dist/ui/modifiers/fill.d.ts.map +1 -0
- package/dist/ui/modifiers/fill.js +25 -0
- package/dist/ui/modifiers/fill.js.map +1 -0
- package/dist/ui/modifiers/frame.d.ts +23 -0
- package/dist/ui/modifiers/frame.d.ts.map +1 -0
- package/dist/ui/modifiers/frame.js +54 -0
- package/dist/ui/modifiers/frame.js.map +1 -0
- package/dist/ui/modifiers/offset.d.ts +15 -0
- package/dist/ui/modifiers/offset.d.ts.map +1 -0
- package/dist/ui/modifiers/offset.js +21 -0
- package/dist/ui/modifiers/offset.js.map +1 -0
- package/dist/ui/modifiers/opacity.d.ts +15 -0
- package/dist/ui/modifiers/opacity.d.ts.map +1 -0
- package/dist/ui/modifiers/opacity.js +95 -0
- package/dist/ui/modifiers/opacity.js.map +1 -0
- package/dist/ui/modifiers/padding.d.ts +20 -0
- package/dist/ui/modifiers/padding.d.ts.map +1 -0
- package/dist/ui/modifiers/padding.js +36 -0
- package/dist/ui/modifiers/padding.js.map +1 -0
- package/dist/ui/modifiers/styled.d.ts +14 -0
- package/dist/ui/modifiers/styled.d.ts.map +1 -0
- package/dist/ui/modifiers/styled.js +26 -0
- package/dist/ui/modifiers/styled.js.map +1 -0
- package/dist/ui/primitives/rectangle.d.ts +15 -0
- package/dist/ui/primitives/rectangle.d.ts.map +1 -0
- package/dist/ui/primitives/rectangle.js +23 -0
- package/dist/ui/primitives/rectangle.js.map +1 -0
- package/dist/ui/primitives/spacer.d.ts +13 -0
- package/dist/ui/primitives/spacer.d.ts.map +1 -0
- package/dist/ui/primitives/spacer.js +16 -0
- package/dist/ui/primitives/spacer.js.map +1 -0
- package/dist/ui/primitives/text.d.ts +15 -0
- package/dist/ui/primitives/text.d.ts.map +1 -0
- package/dist/ui/primitives/text.js +79 -0
- package/dist/ui/primitives/text.js.map +1 -0
- package/dist/ui/primitives/wrapped-text.d.ts +30 -0
- package/dist/ui/primitives/wrapped-text.d.ts.map +1 -0
- package/dist/ui/primitives/wrapped-text.js +117 -0
- package/dist/ui/primitives/wrapped-text.js.map +1 -0
- package/dist/ui/shinytext.d.ts +66 -0
- package/dist/ui/shinytext.d.ts.map +1 -0
- package/dist/ui/shinytext.js +99 -0
- package/dist/ui/shinytext.js.map +1 -0
- package/dist/ui/text/layout.d.ts +35 -0
- package/dist/ui/text/layout.d.ts.map +1 -0
- package/dist/ui/text/layout.js +102 -0
- package/dist/ui/text/layout.js.map +1 -0
- package/dist/ui/textinput.d.ts +140 -0
- package/dist/ui/textinput.d.ts.map +1 -0
- package/dist/ui/textinput.js +402 -0
- package/dist/ui/textinput.js.map +1 -0
- package/dist/ui/view-constructors.d.ts +72 -0
- package/dist/ui/view-constructors.d.ts.map +1 -0
- package/dist/ui/view-constructors.js +74 -0
- package/dist/ui/view-constructors.js.map +1 -0
- package/package.json +57 -0
- package/src/anim.ts +5 -0
- package/src/ansi.ts +83 -0
- package/src/index.ts +21 -0
- package/src/keys.ts +302 -0
- package/src/layout/linearStack.ts +115 -0
- package/src/motion-value.ts +335 -0
- package/src/present/display.ts +206 -0
- package/src/present/writers/fullscreen.ts +58 -0
- package/src/present/writers/inline.ts +101 -0
- package/src/render/buffer.ts +200 -0
- package/src/render/color-utils.ts +60 -0
- package/src/render/diff.ts +95 -0
- package/src/render/measure.ts +74 -0
- package/src/render/palette.ts +113 -0
- package/src/render/surface.ts +238 -0
- package/src/runtime/backend_node.ts +80 -0
- package/src/spring-physics.ts +151 -0
- package/src/spring.ts +234 -0
- package/src/ui/__snapshots__/wrappedtext.test.ts.snap +57 -0
- package/src/ui/containers/canvas.ts +18 -0
- package/src/ui/containers/geometry-reader.ts +32 -0
- package/src/ui/containers/hstack.ts +33 -0
- package/src/ui/containers/scroll.ts +106 -0
- package/src/ui/containers/shared.ts +27 -0
- package/src/ui/containers/vstack.ts +34 -0
- package/src/ui/containers/zstack.ts +37 -0
- package/src/ui/core/geometry-store.ts +42 -0
- package/src/ui/core/geometry.ts +30 -0
- package/src/ui/core/view.ts +49 -0
- package/src/ui/index.ts +84 -0
- package/src/ui/inlinetext.ts +135 -0
- package/src/ui/install.ts +110 -0
- package/src/ui/markdown.test.ts +74 -0
- package/src/ui/markdown.ts +388 -0
- package/src/ui/modifiers/border.ts +100 -0
- package/src/ui/modifiers/fill.ts +28 -0
- package/src/ui/modifiers/frame.ts +74 -0
- package/src/ui/modifiers/offset.ts +23 -0
- package/src/ui/modifiers/opacity.ts +93 -0
- package/src/ui/modifiers/padding.ts +53 -0
- package/src/ui/modifiers/styled.ts +31 -0
- package/src/ui/primitives/rectangle.ts +25 -0
- package/src/ui/primitives/spacer.ts +18 -0
- package/src/ui/primitives/text.ts +85 -0
- package/src/ui/primitives/wrapped-text.ts +131 -0
- package/src/ui/shinytext.ts +159 -0
- package/src/ui/text/layout.ts +119 -0
- package/src/ui/textinput.ts +496 -0
- package/src/ui/view-constructors.ts +96 -0
- package/src/ui/wrappedtext.test.ts +138 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`WrappedText > basic word wrapping > should handle single words longer than width 1`] = `
|
|
4
|
+
"verylo
|
|
5
|
+
ngword
|
|
6
|
+
"
|
|
7
|
+
`;
|
|
8
|
+
|
|
9
|
+
exports[`WrappedText > basic word wrapping > should preserve explicit line breaks 1`] = `
|
|
10
|
+
"line1
|
|
11
|
+
line2
|
|
12
|
+
line3
|
|
13
|
+
|
|
14
|
+
"
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
exports[`WrappedText > basic word wrapping > should wrap text at word boundaries 1`] = `
|
|
18
|
+
"hello
|
|
19
|
+
world
|
|
20
|
+
test"
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
exports[`WrappedText > wrapping strategies > should break words when breakWords is enabled 1`] = `
|
|
24
|
+
"supercalif
|
|
25
|
+
ragilistic
|
|
26
|
+
expialidoc
|
|
27
|
+
ious
|
|
28
|
+
"
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
exports[`WrappedText basic word wrapping should wrap text at word boundaries 1`] = `
|
|
32
|
+
"hello
|
|
33
|
+
world
|
|
34
|
+
test"
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
exports[`WrappedText basic word wrapping should handle single words longer than width 1`] = `
|
|
38
|
+
"verylo
|
|
39
|
+
ngword
|
|
40
|
+
"
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
exports[`WrappedText basic word wrapping should preserve explicit line breaks 1`] = `
|
|
44
|
+
"line1
|
|
45
|
+
line2
|
|
46
|
+
line3
|
|
47
|
+
|
|
48
|
+
"
|
|
49
|
+
`;
|
|
50
|
+
|
|
51
|
+
exports[`WrappedText wrapping strategies should break words when breakWords is enabled 1`] = `
|
|
52
|
+
"supercalif
|
|
53
|
+
ragilistic
|
|
54
|
+
expialidoc
|
|
55
|
+
ious
|
|
56
|
+
"
|
|
57
|
+
`;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Palette, Surface } from "../../render/surface.js"
|
|
2
|
+
import { View } from "../core/view.js"
|
|
3
|
+
import type { Rect } from "../core/geometry.js"
|
|
4
|
+
|
|
5
|
+
export class Canvas extends View {
|
|
6
|
+
constructor(readonly paint: (s: Surface, pal: Palette, rect: Rect) => void) {
|
|
7
|
+
super()
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
protected measureContent(maxW: number, maxH: number) {
|
|
11
|
+
// Greedy: occupy the full available area (caller can constrain via frame())
|
|
12
|
+
return { w: Math.max(0, maxW), h: Math.max(0, maxH) }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
protected renderContent(s: Surface, pal: Palette, rect: Rect) {
|
|
16
|
+
this.paint(s, pal, rect)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Palette, Surface } from "../../render/surface.js"
|
|
2
|
+
import { View } from "../core/view.js"
|
|
3
|
+
import type { Rect } from "../core/geometry.js"
|
|
4
|
+
|
|
5
|
+
// GeometryReader - provides size information to child builder
|
|
6
|
+
export type GeometryProxy = {
|
|
7
|
+
width: number
|
|
8
|
+
height: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class GeometryReader extends View {
|
|
12
|
+
constructor(readonly reader: (proxy: GeometryProxy) => View) {
|
|
13
|
+
super()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
protected measureContent(maxW: number, maxH: number) {
|
|
17
|
+
// GeometryReader is greedy - takes all available space
|
|
18
|
+
return { w: maxW, h: maxH }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
protected renderContent(s: Surface, pal: Palette, rect: Rect) {
|
|
22
|
+
// Create the child with the actual geometry
|
|
23
|
+
const proxy: GeometryProxy = {
|
|
24
|
+
width: rect.w,
|
|
25
|
+
height: rect.h,
|
|
26
|
+
}
|
|
27
|
+
const child = this.reader(proxy)
|
|
28
|
+
// Measure the child first, then render
|
|
29
|
+
child.measure(rect.w, rect.h)
|
|
30
|
+
child.render(s, pal, rect)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Palette, Surface } from "../../render/surface.js"
|
|
2
|
+
import { View } from "../core/view.js"
|
|
3
|
+
import type { Rect, VAlign } from "../core/geometry.js"
|
|
4
|
+
import { measureLinear, layoutLinear } from "../../layout/linearStack.js"
|
|
5
|
+
import { Spacer } from "../primitives/spacer.js"
|
|
6
|
+
import { flexBaseForAxis } from "./shared.js"
|
|
7
|
+
|
|
8
|
+
export class HStack extends View {
|
|
9
|
+
constructor(
|
|
10
|
+
readonly children: View[],
|
|
11
|
+
readonly spacing = 1,
|
|
12
|
+
readonly align: VAlign = "center",
|
|
13
|
+
) {
|
|
14
|
+
super()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
protected measureContent(maxW: number, maxH: number) {
|
|
18
|
+
const sizes = this.children.map((c) => c.measure(maxW, maxH))
|
|
19
|
+
const flexBase = this.children.map((c, i) =>
|
|
20
|
+
c instanceof Spacer ? c.minLength | 0 : flexBaseForAxis(c, "horizontal", sizes[i]),
|
|
21
|
+
)
|
|
22
|
+
return measureLinear("horizontal", maxW, maxH, this.spacing, sizes, flexBase)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected renderContent(s: Surface, pal: Palette, rect: Rect) {
|
|
26
|
+
const sizes = this.children.map((c) => c.measure(rect.w, rect.h))
|
|
27
|
+
const flexBase = this.children.map((c, i) =>
|
|
28
|
+
c instanceof Spacer ? c.minLength | 0 : flexBaseForAxis(c, "horizontal", sizes[i]),
|
|
29
|
+
)
|
|
30
|
+
const rects = layoutLinear("horizontal", rect, this.spacing, this.align, sizes, flexBase)
|
|
31
|
+
for (let i = 0; i < this.children.length; i++) this.children[i].render(s, pal, rects[i])
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { Palette, Surface } from "../../render/surface.js"
|
|
2
|
+
import { View } from "../core/view.js"
|
|
3
|
+
import type { Rect, VAlign, HAlign } from "../core/geometry.js"
|
|
4
|
+
|
|
5
|
+
// Scroll wrapper: viewport with clean SwiftUI-like semantics
|
|
6
|
+
export class Scroll extends View {
|
|
7
|
+
/** Duck-typed brand so helpers can detect Scroll without imports/cycles */
|
|
8
|
+
public readonly __isScroll = true
|
|
9
|
+
|
|
10
|
+
private _fullW = 0
|
|
11
|
+
private _fullH = 0
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
readonly child: View,
|
|
15
|
+
readonly opts: {
|
|
16
|
+
axis?: "vertical" | "horizontal"
|
|
17
|
+
offset?: number
|
|
18
|
+
align?: VAlign | HAlign
|
|
19
|
+
} = {},
|
|
20
|
+
) {
|
|
21
|
+
super()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Get the maximum valid scroll offset for this content */
|
|
25
|
+
getMaxOffset(viewportW: number, viewportH: number): number {
|
|
26
|
+
const axis = this.opts.axis ?? "vertical"
|
|
27
|
+
if (axis === "vertical") {
|
|
28
|
+
const full = this.child.measure(viewportW, Number.MAX_SAFE_INTEGER)
|
|
29
|
+
return Math.max(0, full.h - viewportH)
|
|
30
|
+
} else {
|
|
31
|
+
const full = this.child.measure(Number.MAX_SAFE_INTEGER, viewportH)
|
|
32
|
+
return Math.max(0, full.w - viewportW)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
protected measureContent(maxW: number, maxH: number) {
|
|
37
|
+
const axis = this.opts.axis ?? "vertical"
|
|
38
|
+
if (axis === "vertical") {
|
|
39
|
+
const full = this.child.measure(maxW, Number.MAX_SAFE_INTEGER)
|
|
40
|
+
this._fullW = full.w
|
|
41
|
+
this._fullH = full.h
|
|
42
|
+
// Greedy: take all available space (viewport semantics)
|
|
43
|
+
return { w: Math.min(maxW, full.w), h: maxH }
|
|
44
|
+
} else {
|
|
45
|
+
const full = this.child.measure(Number.MAX_SAFE_INTEGER, maxH)
|
|
46
|
+
this._fullW = full.w
|
|
47
|
+
this._fullH = full.h
|
|
48
|
+
return { w: maxW, h: Math.min(maxH, full.h) }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
protected renderContent(s: Surface, pal: Palette, rect: Rect) {
|
|
53
|
+
const axis = this.opts.axis ?? "vertical"
|
|
54
|
+
const align = this.opts.align ?? "top"
|
|
55
|
+
const rawOffset = this.opts.offset ?? 0
|
|
56
|
+
|
|
57
|
+
// Ensure we have measured the child content
|
|
58
|
+
if (this._fullH === 0 && this._fullW === 0) {
|
|
59
|
+
this.measureContent(rect.w, rect.h)
|
|
60
|
+
// measureContent sets _fullH and _fullW as side effects
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (axis === "vertical") {
|
|
64
|
+
const overflow = Math.max(0, this._fullH - rect.h)
|
|
65
|
+
// Clamp offset to valid range [0, overflow]
|
|
66
|
+
const offset = Math.max(0, Math.min(overflow, rawOffset))
|
|
67
|
+
let start: number
|
|
68
|
+
|
|
69
|
+
if (align === "bottom") {
|
|
70
|
+
// Bottom-pinned: offset 0 shows last lines, positive offset scrolls up from bottom
|
|
71
|
+
if (overflow === 0) {
|
|
72
|
+
// Content fits: bottom-align by pushing content down
|
|
73
|
+
start = -(rect.h - this._fullH)
|
|
74
|
+
} else {
|
|
75
|
+
// Content overflows: start from bottom minus offset
|
|
76
|
+
start = overflow - offset
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
// Top-aligned: traditional scrolling
|
|
80
|
+
start = offset
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const childRect = { x: rect.x, y: rect.y - start, w: rect.w, h: this._fullH }
|
|
84
|
+
s.withClip(rect.x, rect.y, rect.w, rect.h, () => this.child.render(s, pal, childRect))
|
|
85
|
+
} else {
|
|
86
|
+
const overflow = Math.max(0, this._fullW - rect.w)
|
|
87
|
+
// Clamp offset to valid range [0, overflow]
|
|
88
|
+
const offset = Math.max(0, Math.min(overflow, rawOffset))
|
|
89
|
+
let start: number
|
|
90
|
+
|
|
91
|
+
if (align === "trailing") {
|
|
92
|
+
// Right-pinned: similar logic for horizontal
|
|
93
|
+
if (overflow === 0) {
|
|
94
|
+
start = -(rect.w - this._fullW)
|
|
95
|
+
} else {
|
|
96
|
+
start = overflow - offset
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
start = offset
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const childRect = { x: rect.x - start, y: rect.y, w: this._fullW, h: rect.h }
|
|
103
|
+
s.withClip(rect.x, rect.y, rect.w, rect.h, () => this.child.render(s, pal, childRect))
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Shared utilities for containers
|
|
2
|
+
import type { View } from "../core/view.js"
|
|
3
|
+
|
|
4
|
+
// Check if a node is a Fill wrapper for a given axis
|
|
5
|
+
export function isFillForAxis(node: View, axis: "horizontal" | "vertical"): boolean {
|
|
6
|
+
return node.constructor.name === "Fill" && (node as any).axis === axis
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Get the main-axis size from a measured size
|
|
10
|
+
export function mainSize(axis: "horizontal" | "vertical", size: { w: number; h: number }): number {
|
|
11
|
+
return axis === "horizontal" ? size.w : size.h
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Return the flex base size for a node on an axis, or null if fixed. */
|
|
15
|
+
export function flexBaseForAxis(
|
|
16
|
+
node: View,
|
|
17
|
+
axis: "horizontal" | "vertical",
|
|
18
|
+
measured: { w: number; h: number },
|
|
19
|
+
): number | null {
|
|
20
|
+
// Scroll should be flexible but with a *zero* base so siblings get their fixed space first.
|
|
21
|
+
if ((node as any).__isScroll) return 0
|
|
22
|
+
|
|
23
|
+
// Fill is flexible with intrinsic base equal to its measured main size.
|
|
24
|
+
if (isFillForAxis(node, axis)) return mainSize(axis, measured)
|
|
25
|
+
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Palette, Surface } from "../../render/surface.js"
|
|
2
|
+
import { View } from "../core/view.js"
|
|
3
|
+
import type { Rect, HAlign } from "../core/geometry.js"
|
|
4
|
+
import { measureLinear, layoutLinear } from "../../layout/linearStack.js"
|
|
5
|
+
import { Spacer } from "../primitives/spacer.js"
|
|
6
|
+
import { flexBaseForAxis } from "./shared.js"
|
|
7
|
+
|
|
8
|
+
export class VStack extends View {
|
|
9
|
+
constructor(
|
|
10
|
+
readonly children: View[],
|
|
11
|
+
readonly spacing = 0,
|
|
12
|
+
readonly align: HAlign = "leading",
|
|
13
|
+
) {
|
|
14
|
+
super()
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
protected measureContent(maxW: number, maxH: number) {
|
|
18
|
+
const sizes = this.children.map((c) => c.measure(maxW, maxH))
|
|
19
|
+
const flexBase = this.children.map((c, i) =>
|
|
20
|
+
c instanceof Spacer ? c.minLength | 0 : flexBaseForAxis(c, "vertical", sizes[i]),
|
|
21
|
+
)
|
|
22
|
+
return measureLinear("vertical", maxW, maxH, this.spacing, sizes, flexBase)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected renderContent(s: Surface, pal: Palette, rect: Rect) {
|
|
26
|
+
const sizes = this.children.map((c) => c.measure(rect.w, rect.h))
|
|
27
|
+
const flexBase = this.children.map((c, i) =>
|
|
28
|
+
c instanceof Spacer ? c.minLength | 0 : flexBaseForAxis(c, "vertical", sizes[i]),
|
|
29
|
+
)
|
|
30
|
+
const rects = layoutLinear("vertical", rect, this.spacing, this.align, sizes, flexBase)
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < this.children.length; i++) this.children[i].render(s, pal, rects[i])
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Palette, Surface } from "../../render/surface.js"
|
|
2
|
+
import { View } from "../core/view.js"
|
|
3
|
+
import type { Rect, Align2D } from "../core/geometry.js"
|
|
4
|
+
|
|
5
|
+
// ZStack (overlay)
|
|
6
|
+
export class ZStack extends View {
|
|
7
|
+
constructor(
|
|
8
|
+
readonly children: View[],
|
|
9
|
+
readonly alignment: Align2D = { h: "center", v: "center" },
|
|
10
|
+
) {
|
|
11
|
+
super()
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
protected measureContent(maxW: number, maxH: number) {
|
|
15
|
+
let w = 0,
|
|
16
|
+
h = 0
|
|
17
|
+
for (const c of this.children) {
|
|
18
|
+
const m = c.measure(maxW, maxH)
|
|
19
|
+
w = Math.max(w, m.w)
|
|
20
|
+
h = Math.max(h, m.h)
|
|
21
|
+
}
|
|
22
|
+
return { w: Math.min(maxW, w), h: Math.min(maxH, h) }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
protected renderContent(s: Surface, pal: Palette, rect: Rect) {
|
|
26
|
+
for (const c of this.children) {
|
|
27
|
+
const m = c.measure(rect.w, rect.h)
|
|
28
|
+
let x = rect.x,
|
|
29
|
+
y = rect.y
|
|
30
|
+
if (this.alignment.h === "center") x += Math.floor((rect.w - m.w) / 2)
|
|
31
|
+
else if (this.alignment.h === "trailing") x += Math.max(0, rect.w - m.w)
|
|
32
|
+
if (this.alignment.v === "center") y += Math.floor((rect.h - m.h) / 2)
|
|
33
|
+
else if (this.alignment.v === "bottom") y += Math.max(0, rect.h - m.h)
|
|
34
|
+
c.render(s, pal, { x, y, w: Math.min(rect.w, m.w), h: Math.min(rect.h, m.h) })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/* geometry-store.ts — last-known layout/geometry store keyed by ViewId */
|
|
2
|
+
|
|
3
|
+
import type { Rect } from "./geometry.js"
|
|
4
|
+
|
|
5
|
+
export type ViewId = string
|
|
6
|
+
export type { Rect }
|
|
7
|
+
export type InputGeom = { firstW: number; wrapW: number; xFirst: number; xOther: number }
|
|
8
|
+
|
|
9
|
+
class GeometryStoreImpl {
|
|
10
|
+
private frameNo = 0
|
|
11
|
+
private rects = new Map<ViewId, Rect>()
|
|
12
|
+
private inputs = new Map<ViewId, InputGeom>()
|
|
13
|
+
|
|
14
|
+
beginFrame(frameNo?: number) {
|
|
15
|
+
// Clear for the new render pass; consumers will read after endFrame
|
|
16
|
+
this.frameNo = frameNo ?? this.frameNo + 1
|
|
17
|
+
this.rects.clear()
|
|
18
|
+
this.inputs.clear()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
endFrame() {
|
|
22
|
+
// no-op for now; snapshot already in maps
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setRect(id: ViewId, rect: Rect) {
|
|
26
|
+
this.rects.set(id, rect)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
setInputGeom(id: ViewId, g: InputGeom) {
|
|
30
|
+
this.inputs.set(id, g)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
getRect(id: ViewId): Rect | undefined {
|
|
34
|
+
return this.rects.get(id)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getInputGeom(id: ViewId): InputGeom | undefined {
|
|
38
|
+
return this.inputs.get(id)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const geometryStore = new GeometryStoreImpl()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Basic geometry types and alignments
|
|
2
|
+
|
|
3
|
+
export type Rect = { x: number; y: number; w: number; h: number }
|
|
4
|
+
|
|
5
|
+
export type HAlign = "leading" | "center" | "trailing"
|
|
6
|
+
export type VAlign = "top" | "center" | "bottom"
|
|
7
|
+
export type Align2D = { h: HAlign; v: VAlign }
|
|
8
|
+
|
|
9
|
+
export const Align = {
|
|
10
|
+
leading: "leading" as HAlign,
|
|
11
|
+
center: "center" as HAlign,
|
|
12
|
+
trailing: "trailing" as HAlign,
|
|
13
|
+
top: "top" as VAlign,
|
|
14
|
+
bottom: "bottom" as VAlign,
|
|
15
|
+
topLeading: { h: "leading", v: "top" } as Align2D,
|
|
16
|
+
topTrailing: { h: "trailing", v: "top" } as Align2D,
|
|
17
|
+
bottomLeading: { h: "leading", v: "bottom" } as Align2D,
|
|
18
|
+
bottomTrailing: { h: "trailing", v: "bottom" } as Align2D,
|
|
19
|
+
center2D: { h: "center", v: "center" } as Align2D,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type FrameSpec = {
|
|
23
|
+
width?: number // exact width; use Infinity to fill
|
|
24
|
+
height?: number
|
|
25
|
+
minWidth?: number
|
|
26
|
+
maxWidth?: number
|
|
27
|
+
minHeight?: number
|
|
28
|
+
maxHeight?: number
|
|
29
|
+
alignment?: Align2D // child placement within surplus
|
|
30
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* view.ts — Core View class (renamed from CoreViewBase)
|
|
2
|
+
*
|
|
3
|
+
* This module defines the fundamental UI View class with minimal base functionality.
|
|
4
|
+
* It has NO imports of concrete modifiers to avoid circular dependencies.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Palette, Surface } from "../../render/surface.js"
|
|
8
|
+
import type { Rect } from "./geometry.js"
|
|
9
|
+
import { geometryStore } from "./geometry-store.js"
|
|
10
|
+
|
|
11
|
+
export interface View {
|
|
12
|
+
measure(maxW: number, maxH: number): { w: number; h: number }
|
|
13
|
+
render(s: Surface, pal: Palette, rect: Rect): void
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Abstract base class providing a minimal View foundation.
|
|
18
|
+
* Subclasses implement measureContent/renderContent for their specific behavior.
|
|
19
|
+
* All styling, padding, and frame logic has been moved to modifier classes.
|
|
20
|
+
*/
|
|
21
|
+
export abstract class View implements View {
|
|
22
|
+
protected _id?: string
|
|
23
|
+
|
|
24
|
+
/** Attach a stable id to this view for geometry tracking. */
|
|
25
|
+
id(id: string): View {
|
|
26
|
+
const c = Object.create(Object.getPrototypeOf(this)) as this
|
|
27
|
+
// copy own fields
|
|
28
|
+
for (const k of Object.keys(this) as Array<keyof this>) (c as any)[k] = (this as any)[k]
|
|
29
|
+
c._id = id
|
|
30
|
+
return c
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
measure(maxW: number, maxH: number) {
|
|
34
|
+
return this.measureContent(maxW, maxH)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
render(s: Surface, pal: Palette, rect: Rect) {
|
|
38
|
+
// Record geometry if id is present
|
|
39
|
+
if (this._id) {
|
|
40
|
+
geometryStore.setRect(this._id, rect)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
this.renderContent(s, pal, rect)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ---- Hooks for subclasses ----
|
|
47
|
+
protected abstract measureContent(maxW: number, maxH: number): { w: number; h: number }
|
|
48
|
+
protected abstract renderContent(s: Surface, pal: Palette, rect: Rect): void
|
|
49
|
+
}
|
package/src/ui/index.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Single barrel export for the View-centric UI system
|
|
2
|
+
|
|
3
|
+
// Install chainable modifiers (side-effect import)
|
|
4
|
+
import "./install.js"
|
|
5
|
+
|
|
6
|
+
// Core geometry (types + helpers)
|
|
7
|
+
export * from "./core/geometry.js"
|
|
8
|
+
|
|
9
|
+
// View class will be exported at bottom after enhancement
|
|
10
|
+
|
|
11
|
+
// Primitives
|
|
12
|
+
export * from "./primitives/text.js"
|
|
13
|
+
export * from "./primitives/rectangle.js"
|
|
14
|
+
export * from "./primitives/spacer.js"
|
|
15
|
+
export * from "./primitives/wrapped-text.js"
|
|
16
|
+
|
|
17
|
+
// Containers
|
|
18
|
+
export * from "./containers/hstack.js"
|
|
19
|
+
export * from "./containers/vstack.js"
|
|
20
|
+
export * from "./containers/zstack.js"
|
|
21
|
+
export * from "./containers/scroll.js"
|
|
22
|
+
export * from "./containers/canvas.js"
|
|
23
|
+
export * from "./containers/geometry-reader.js"
|
|
24
|
+
|
|
25
|
+
// Modifiers (classes exported, chainers are installed via side effects above)
|
|
26
|
+
export * from "./modifiers/border.js"
|
|
27
|
+
export * from "./modifiers/offset.js"
|
|
28
|
+
export * from "./modifiers/fill.js"
|
|
29
|
+
export * from "./modifiers/padding.js"
|
|
30
|
+
export { Frame, type FrameSpec as ModifierFrameSpec } from "./modifiers/frame.js"
|
|
31
|
+
export * from "./modifiers/styled.js"
|
|
32
|
+
|
|
33
|
+
// Components & utilities
|
|
34
|
+
export { ShinyText } from "./shinytext.js"
|
|
35
|
+
export {
|
|
36
|
+
TextInput,
|
|
37
|
+
TextInputState,
|
|
38
|
+
editTextInput,
|
|
39
|
+
type TextInputStateLike,
|
|
40
|
+
type TextInputStatePlain,
|
|
41
|
+
type TextInputGeom,
|
|
42
|
+
type TextInputOptions,
|
|
43
|
+
} from "./textinput.js"
|
|
44
|
+
export { Markdown, type MarkdownOptions } from "./markdown.js"
|
|
45
|
+
|
|
46
|
+
// Import View class and constructors
|
|
47
|
+
import { View as ViewClass } from "./core/view.js"
|
|
48
|
+
import { ViewConstructors } from "./view-constructors.js"
|
|
49
|
+
|
|
50
|
+
// Use namespace merging to add static methods to View class
|
|
51
|
+
declare module "./core/view.js" {
|
|
52
|
+
namespace View {
|
|
53
|
+
// Primitives
|
|
54
|
+
const text: typeof ViewConstructors.text
|
|
55
|
+
const wrappedText: typeof ViewConstructors.wrappedText
|
|
56
|
+
const rect: typeof ViewConstructors.rect
|
|
57
|
+
const spacer: typeof ViewConstructors.spacer
|
|
58
|
+
|
|
59
|
+
// Containers
|
|
60
|
+
const hstack: typeof ViewConstructors.hstack
|
|
61
|
+
const vstack: typeof ViewConstructors.vstack
|
|
62
|
+
const zstack: typeof ViewConstructors.zstack
|
|
63
|
+
const scroll: typeof ViewConstructors.scroll
|
|
64
|
+
const canvas: typeof ViewConstructors.canvas
|
|
65
|
+
const geometryReader: typeof ViewConstructors.geometryReader
|
|
66
|
+
const overlay: typeof ViewConstructors.overlay
|
|
67
|
+
|
|
68
|
+
// Components
|
|
69
|
+
const textInput: typeof ViewConstructors.textInput
|
|
70
|
+
const markdown: typeof ViewConstructors.markdown
|
|
71
|
+
|
|
72
|
+
// Utilities
|
|
73
|
+
const Colors: typeof ViewConstructors.Colors
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Install all the constructor methods on the View class
|
|
78
|
+
const View = ViewClass as typeof ViewClass & typeof ViewConstructors
|
|
79
|
+
|
|
80
|
+
// Install all constructors
|
|
81
|
+
Object.assign(View, ViewConstructors)
|
|
82
|
+
|
|
83
|
+
// Export the enhanced View
|
|
84
|
+
export { View }
|