@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.
Files changed (241) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +93 -0
  3. package/dist/anim.d.ts +4 -0
  4. package/dist/anim.d.ts.map +1 -0
  5. package/dist/anim.js +5 -0
  6. package/dist/anim.js.map +1 -0
  7. package/dist/ansi.d.ts +69 -0
  8. package/dist/ansi.d.ts.map +1 -0
  9. package/dist/ansi.js +72 -0
  10. package/dist/ansi.js.map +1 -0
  11. package/dist/index.d.ts +16 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +16 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/keys.d.ts +18 -0
  16. package/dist/keys.d.ts.map +1 -0
  17. package/dist/keys.js +247 -0
  18. package/dist/keys.js.map +1 -0
  19. package/dist/layout/linearStack.d.ts +17 -0
  20. package/dist/layout/linearStack.d.ts.map +1 -0
  21. package/dist/layout/linearStack.js +86 -0
  22. package/dist/layout/linearStack.js.map +1 -0
  23. package/dist/motion-value.d.ts +58 -0
  24. package/dist/motion-value.d.ts.map +1 -0
  25. package/dist/motion-value.js +250 -0
  26. package/dist/motion-value.js.map +1 -0
  27. package/dist/present/display.d.ts +58 -0
  28. package/dist/present/display.d.ts.map +1 -0
  29. package/dist/present/display.js +168 -0
  30. package/dist/present/display.js.map +1 -0
  31. package/dist/present/writers/fullscreen.d.ts +19 -0
  32. package/dist/present/writers/fullscreen.d.ts.map +1 -0
  33. package/dist/present/writers/fullscreen.js +55 -0
  34. package/dist/present/writers/fullscreen.js.map +1 -0
  35. package/dist/present/writers/inline.d.ts +20 -0
  36. package/dist/present/writers/inline.d.ts.map +1 -0
  37. package/dist/present/writers/inline.js +92 -0
  38. package/dist/present/writers/inline.js.map +1 -0
  39. package/dist/render/buffer.d.ts +31 -0
  40. package/dist/render/buffer.d.ts.map +1 -0
  41. package/dist/render/buffer.js +183 -0
  42. package/dist/render/buffer.js.map +1 -0
  43. package/dist/render/color-utils.d.ts +18 -0
  44. package/dist/render/color-utils.d.ts.map +1 -0
  45. package/dist/render/color-utils.js +58 -0
  46. package/dist/render/color-utils.js.map +1 -0
  47. package/dist/render/diff.d.ts +30 -0
  48. package/dist/render/diff.d.ts.map +1 -0
  49. package/dist/render/diff.js +83 -0
  50. package/dist/render/diff.js.map +1 -0
  51. package/dist/render/measure.d.ts +15 -0
  52. package/dist/render/measure.d.ts.map +1 -0
  53. package/dist/render/measure.js +65 -0
  54. package/dist/render/measure.js.map +1 -0
  55. package/dist/render/palette.d.ts +46 -0
  56. package/dist/render/palette.d.ts.map +1 -0
  57. package/dist/render/palette.js +108 -0
  58. package/dist/render/palette.js.map +1 -0
  59. package/dist/render/surface.d.ts +77 -0
  60. package/dist/render/surface.d.ts.map +1 -0
  61. package/dist/render/surface.js +198 -0
  62. package/dist/render/surface.js.map +1 -0
  63. package/dist/runtime/backend_node.d.ts +36 -0
  64. package/dist/runtime/backend_node.d.ts.map +1 -0
  65. package/dist/runtime/backend_node.js +66 -0
  66. package/dist/runtime/backend_node.js.map +1 -0
  67. package/dist/spring-physics.d.ts +36 -0
  68. package/dist/spring-physics.d.ts.map +1 -0
  69. package/dist/spring-physics.js +113 -0
  70. package/dist/spring-physics.js.map +1 -0
  71. package/dist/spring.d.ts +73 -0
  72. package/dist/spring.d.ts.map +1 -0
  73. package/dist/spring.js +136 -0
  74. package/dist/spring.js.map +1 -0
  75. package/dist/ui/containers/canvas.d.ts +13 -0
  76. package/dist/ui/containers/canvas.d.ts.map +1 -0
  77. package/dist/ui/containers/canvas.js +16 -0
  78. package/dist/ui/containers/canvas.js.map +1 -0
  79. package/dist/ui/containers/geometry-reader.d.ts +17 -0
  80. package/dist/ui/containers/geometry-reader.d.ts.map +1 -0
  81. package/dist/ui/containers/geometry-reader.js +24 -0
  82. package/dist/ui/containers/geometry-reader.js.map +1 -0
  83. package/dist/ui/containers/hstack.d.ts +12 -0
  84. package/dist/ui/containers/hstack.d.ts.map +1 -0
  85. package/dist/ui/containers/hstack.js +28 -0
  86. package/dist/ui/containers/hstack.js.map +1 -0
  87. package/dist/ui/containers/scroll.d.ts +28 -0
  88. package/dist/ui/containers/scroll.d.ts.map +1 -0
  89. package/dist/ui/containers/scroll.js +97 -0
  90. package/dist/ui/containers/scroll.js.map +1 -0
  91. package/dist/ui/containers/shared.d.ts +12 -0
  92. package/dist/ui/containers/shared.d.ts.map +1 -0
  93. package/dist/ui/containers/shared.js +19 -0
  94. package/dist/ui/containers/shared.js.map +1 -0
  95. package/dist/ui/containers/vstack.d.ts +12 -0
  96. package/dist/ui/containers/vstack.d.ts.map +1 -0
  97. package/dist/ui/containers/vstack.js +28 -0
  98. package/dist/ui/containers/vstack.js.map +1 -0
  99. package/dist/ui/containers/zstack.d.ts +14 -0
  100. package/dist/ui/containers/zstack.d.ts.map +1 -0
  101. package/dist/ui/containers/zstack.js +36 -0
  102. package/dist/ui/containers/zstack.js.map +1 -0
  103. package/dist/ui/core/geometry-store.d.ts +22 -0
  104. package/dist/ui/core/geometry-store.d.ts.map +1 -0
  105. package/dist/ui/core/geometry-store.js +29 -0
  106. package/dist/ui/core/geometry-store.js.map +1 -0
  107. package/dist/ui/core/geometry.d.ts +34 -0
  108. package/dist/ui/core/geometry.d.ts.map +1 -0
  109. package/dist/ui/core/geometry.js +14 -0
  110. package/dist/ui/core/geometry.js.map +1 -0
  111. package/dist/ui/core/view.d.ts +25 -0
  112. package/dist/ui/core/view.d.ts.map +1 -0
  113. package/dist/ui/core/view.js +34 -0
  114. package/dist/ui/core/view.js.map +1 -0
  115. package/dist/ui/index.d.ts +44 -0
  116. package/dist/ui/index.d.ts.map +1 -0
  117. package/dist/ui/index.js +39 -0
  118. package/dist/ui/index.js.map +1 -0
  119. package/dist/ui/inlinetext.d.ts +24 -0
  120. package/dist/ui/inlinetext.d.ts.map +1 -0
  121. package/dist/ui/inlinetext.js +131 -0
  122. package/dist/ui/inlinetext.js.map +1 -0
  123. package/dist/ui/install.d.ts +22 -0
  124. package/dist/ui/install.d.ts.map +1 -0
  125. package/dist/ui/install.js +66 -0
  126. package/dist/ui/install.js.map +1 -0
  127. package/dist/ui/markdown.d.ts +40 -0
  128. package/dist/ui/markdown.d.ts.map +1 -0
  129. package/dist/ui/markdown.js +351 -0
  130. package/dist/ui/markdown.js.map +1 -0
  131. package/dist/ui/modifiers/border.d.ts +33 -0
  132. package/dist/ui/modifiers/border.d.ts.map +1 -0
  133. package/dist/ui/modifiers/border.js +82 -0
  134. package/dist/ui/modifiers/border.js.map +1 -0
  135. package/dist/ui/modifiers/fill.d.ts +14 -0
  136. package/dist/ui/modifiers/fill.d.ts.map +1 -0
  137. package/dist/ui/modifiers/fill.js +25 -0
  138. package/dist/ui/modifiers/fill.js.map +1 -0
  139. package/dist/ui/modifiers/frame.d.ts +23 -0
  140. package/dist/ui/modifiers/frame.d.ts.map +1 -0
  141. package/dist/ui/modifiers/frame.js +54 -0
  142. package/dist/ui/modifiers/frame.js.map +1 -0
  143. package/dist/ui/modifiers/offset.d.ts +15 -0
  144. package/dist/ui/modifiers/offset.d.ts.map +1 -0
  145. package/dist/ui/modifiers/offset.js +21 -0
  146. package/dist/ui/modifiers/offset.js.map +1 -0
  147. package/dist/ui/modifiers/opacity.d.ts +15 -0
  148. package/dist/ui/modifiers/opacity.d.ts.map +1 -0
  149. package/dist/ui/modifiers/opacity.js +95 -0
  150. package/dist/ui/modifiers/opacity.js.map +1 -0
  151. package/dist/ui/modifiers/padding.d.ts +20 -0
  152. package/dist/ui/modifiers/padding.d.ts.map +1 -0
  153. package/dist/ui/modifiers/padding.js +36 -0
  154. package/dist/ui/modifiers/padding.js.map +1 -0
  155. package/dist/ui/modifiers/styled.d.ts +14 -0
  156. package/dist/ui/modifiers/styled.d.ts.map +1 -0
  157. package/dist/ui/modifiers/styled.js +26 -0
  158. package/dist/ui/modifiers/styled.js.map +1 -0
  159. package/dist/ui/primitives/rectangle.d.ts +15 -0
  160. package/dist/ui/primitives/rectangle.d.ts.map +1 -0
  161. package/dist/ui/primitives/rectangle.js +23 -0
  162. package/dist/ui/primitives/rectangle.js.map +1 -0
  163. package/dist/ui/primitives/spacer.d.ts +13 -0
  164. package/dist/ui/primitives/spacer.d.ts.map +1 -0
  165. package/dist/ui/primitives/spacer.js +16 -0
  166. package/dist/ui/primitives/spacer.js.map +1 -0
  167. package/dist/ui/primitives/text.d.ts +15 -0
  168. package/dist/ui/primitives/text.d.ts.map +1 -0
  169. package/dist/ui/primitives/text.js +79 -0
  170. package/dist/ui/primitives/text.js.map +1 -0
  171. package/dist/ui/primitives/wrapped-text.d.ts +30 -0
  172. package/dist/ui/primitives/wrapped-text.d.ts.map +1 -0
  173. package/dist/ui/primitives/wrapped-text.js +117 -0
  174. package/dist/ui/primitives/wrapped-text.js.map +1 -0
  175. package/dist/ui/shinytext.d.ts +66 -0
  176. package/dist/ui/shinytext.d.ts.map +1 -0
  177. package/dist/ui/shinytext.js +99 -0
  178. package/dist/ui/shinytext.js.map +1 -0
  179. package/dist/ui/text/layout.d.ts +35 -0
  180. package/dist/ui/text/layout.d.ts.map +1 -0
  181. package/dist/ui/text/layout.js +102 -0
  182. package/dist/ui/text/layout.js.map +1 -0
  183. package/dist/ui/textinput.d.ts +140 -0
  184. package/dist/ui/textinput.d.ts.map +1 -0
  185. package/dist/ui/textinput.js +402 -0
  186. package/dist/ui/textinput.js.map +1 -0
  187. package/dist/ui/view-constructors.d.ts +72 -0
  188. package/dist/ui/view-constructors.d.ts.map +1 -0
  189. package/dist/ui/view-constructors.js +74 -0
  190. package/dist/ui/view-constructors.js.map +1 -0
  191. package/package.json +57 -0
  192. package/src/anim.ts +5 -0
  193. package/src/ansi.ts +83 -0
  194. package/src/index.ts +21 -0
  195. package/src/keys.ts +302 -0
  196. package/src/layout/linearStack.ts +115 -0
  197. package/src/motion-value.ts +335 -0
  198. package/src/present/display.ts +206 -0
  199. package/src/present/writers/fullscreen.ts +58 -0
  200. package/src/present/writers/inline.ts +101 -0
  201. package/src/render/buffer.ts +200 -0
  202. package/src/render/color-utils.ts +60 -0
  203. package/src/render/diff.ts +95 -0
  204. package/src/render/measure.ts +74 -0
  205. package/src/render/palette.ts +113 -0
  206. package/src/render/surface.ts +238 -0
  207. package/src/runtime/backend_node.ts +80 -0
  208. package/src/spring-physics.ts +151 -0
  209. package/src/spring.ts +234 -0
  210. package/src/ui/__snapshots__/wrappedtext.test.ts.snap +57 -0
  211. package/src/ui/containers/canvas.ts +18 -0
  212. package/src/ui/containers/geometry-reader.ts +32 -0
  213. package/src/ui/containers/hstack.ts +33 -0
  214. package/src/ui/containers/scroll.ts +106 -0
  215. package/src/ui/containers/shared.ts +27 -0
  216. package/src/ui/containers/vstack.ts +34 -0
  217. package/src/ui/containers/zstack.ts +37 -0
  218. package/src/ui/core/geometry-store.ts +42 -0
  219. package/src/ui/core/geometry.ts +30 -0
  220. package/src/ui/core/view.ts +49 -0
  221. package/src/ui/index.ts +84 -0
  222. package/src/ui/inlinetext.ts +135 -0
  223. package/src/ui/install.ts +110 -0
  224. package/src/ui/markdown.test.ts +74 -0
  225. package/src/ui/markdown.ts +388 -0
  226. package/src/ui/modifiers/border.ts +100 -0
  227. package/src/ui/modifiers/fill.ts +28 -0
  228. package/src/ui/modifiers/frame.ts +74 -0
  229. package/src/ui/modifiers/offset.ts +23 -0
  230. package/src/ui/modifiers/opacity.ts +93 -0
  231. package/src/ui/modifiers/padding.ts +53 -0
  232. package/src/ui/modifiers/styled.ts +31 -0
  233. package/src/ui/primitives/rectangle.ts +25 -0
  234. package/src/ui/primitives/spacer.ts +18 -0
  235. package/src/ui/primitives/text.ts +85 -0
  236. package/src/ui/primitives/wrapped-text.ts +131 -0
  237. package/src/ui/shinytext.ts +159 -0
  238. package/src/ui/text/layout.ts +119 -0
  239. package/src/ui/textinput.ts +496 -0
  240. package/src/ui/view-constructors.ts +96 -0
  241. 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
+ }
@@ -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 }