@effect-tui/core 0.1.0 → 0.1.4

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 (271) hide show
  1. package/README.md +31 -11
  2. package/dist/ansi.d.ts +127 -32
  3. package/dist/ansi.d.ts.map +1 -1
  4. package/dist/ansi.js +159 -37
  5. package/dist/ansi.js.map +1 -1
  6. package/dist/colors.d.ts +139 -0
  7. package/dist/colors.d.ts.map +1 -0
  8. package/dist/colors.js +339 -0
  9. package/dist/colors.js.map +1 -0
  10. package/dist/index.d.ts +6 -10
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +13 -11
  13. package/dist/index.js.map +1 -1
  14. package/dist/keys.d.ts +21 -0
  15. package/dist/keys.d.ts.map +1 -1
  16. package/dist/keys.js +199 -58
  17. package/dist/keys.js.map +1 -1
  18. package/dist/layout/axis-helpers.d.ts +19 -0
  19. package/dist/layout/axis-helpers.d.ts.map +1 -0
  20. package/dist/layout/axis-helpers.js +19 -0
  21. package/dist/layout/axis-helpers.js.map +1 -0
  22. package/dist/output.d.ts +59 -0
  23. package/dist/output.d.ts.map +1 -0
  24. package/dist/output.js +142 -0
  25. package/dist/output.js.map +1 -0
  26. package/dist/render/buffer.d.ts.map +1 -1
  27. package/dist/render/buffer.js +6 -25
  28. package/dist/render/buffer.js.map +1 -1
  29. package/dist/render/graphemes.d.ts +15 -0
  30. package/dist/render/graphemes.d.ts.map +1 -0
  31. package/dist/render/graphemes.js +28 -0
  32. package/dist/render/graphemes.js.map +1 -0
  33. package/dist/render/measure.d.ts +1 -0
  34. package/dist/render/measure.d.ts.map +1 -1
  35. package/dist/render/measure.js +14 -36
  36. package/dist/render/measure.js.map +1 -1
  37. package/dist/render/palette.d.ts.map +1 -1
  38. package/dist/render/palette.js +26 -1
  39. package/dist/render/palette.js.map +1 -1
  40. package/dist/render/segmenter.d.ts +8 -0
  41. package/dist/render/segmenter.d.ts.map +1 -0
  42. package/dist/render/segmenter.js +23 -0
  43. package/dist/render/segmenter.js.map +1 -0
  44. package/dist/render/surface.d.ts +6 -32
  45. package/dist/render/surface.d.ts.map +1 -1
  46. package/dist/render/surface.js +11 -80
  47. package/dist/render/surface.js.map +1 -1
  48. package/dist/runtime/backend_node.d.ts.map +1 -1
  49. package/dist/runtime/backend_node.js.map +1 -1
  50. package/dist/tailwind-colors.d.ts +291 -0
  51. package/dist/tailwind-colors.d.ts.map +1 -0
  52. package/dist/tailwind-colors.js +291 -0
  53. package/dist/tailwind-colors.js.map +1 -0
  54. package/dist/types.d.ts +15 -0
  55. package/dist/types.d.ts.map +1 -0
  56. package/dist/types.js +3 -0
  57. package/dist/types.js.map +1 -0
  58. package/package.json +55 -55
  59. package/src/ansi.ts +201 -73
  60. package/src/colors.ts +468 -0
  61. package/src/index.ts +28 -14
  62. package/src/keys.ts +467 -287
  63. package/src/layout/axis-helpers.ts +33 -0
  64. package/src/output.ts +175 -0
  65. package/src/render/buffer.ts +161 -184
  66. package/src/render/graphemes.ts +34 -0
  67. package/src/render/measure.ts +15 -38
  68. package/src/render/palette.ts +98 -77
  69. package/src/render/segmenter.ts +27 -0
  70. package/src/render/surface.ts +139 -225
  71. package/src/runtime/backend_node.ts +71 -71
  72. package/src/tailwind-colors.ts +295 -0
  73. package/src/types.ts +18 -0
  74. package/dist/anim.d.ts +0 -4
  75. package/dist/anim.d.ts.map +0 -1
  76. package/dist/anim.js +0 -5
  77. package/dist/anim.js.map +0 -1
  78. package/dist/layout/linearStack.d.ts +0 -17
  79. package/dist/layout/linearStack.d.ts.map +0 -1
  80. package/dist/layout/linearStack.js +0 -86
  81. package/dist/layout/linearStack.js.map +0 -1
  82. package/dist/motion-value.d.ts +0 -58
  83. package/dist/motion-value.d.ts.map +0 -1
  84. package/dist/motion-value.js +0 -250
  85. package/dist/motion-value.js.map +0 -1
  86. package/dist/present/display.d.ts +0 -58
  87. package/dist/present/display.d.ts.map +0 -1
  88. package/dist/present/display.js +0 -168
  89. package/dist/present/display.js.map +0 -1
  90. package/dist/present/writers/fullscreen.d.ts +0 -19
  91. package/dist/present/writers/fullscreen.d.ts.map +0 -1
  92. package/dist/present/writers/fullscreen.js +0 -55
  93. package/dist/present/writers/fullscreen.js.map +0 -1
  94. package/dist/present/writers/inline.d.ts +0 -20
  95. package/dist/present/writers/inline.d.ts.map +0 -1
  96. package/dist/present/writers/inline.js +0 -92
  97. package/dist/present/writers/inline.js.map +0 -1
  98. package/dist/render/color-utils.d.ts +0 -18
  99. package/dist/render/color-utils.d.ts.map +0 -1
  100. package/dist/render/color-utils.js +0 -58
  101. package/dist/render/color-utils.js.map +0 -1
  102. package/dist/render/diff.d.ts +0 -30
  103. package/dist/render/diff.d.ts.map +0 -1
  104. package/dist/render/diff.js +0 -83
  105. package/dist/render/diff.js.map +0 -1
  106. package/dist/spring-physics.d.ts +0 -36
  107. package/dist/spring-physics.d.ts.map +0 -1
  108. package/dist/spring-physics.js +0 -113
  109. package/dist/spring-physics.js.map +0 -1
  110. package/dist/spring.d.ts +0 -73
  111. package/dist/spring.d.ts.map +0 -1
  112. package/dist/spring.js +0 -136
  113. package/dist/spring.js.map +0 -1
  114. package/dist/ui/containers/canvas.d.ts +0 -13
  115. package/dist/ui/containers/canvas.d.ts.map +0 -1
  116. package/dist/ui/containers/canvas.js +0 -16
  117. package/dist/ui/containers/canvas.js.map +0 -1
  118. package/dist/ui/containers/geometry-reader.d.ts +0 -17
  119. package/dist/ui/containers/geometry-reader.d.ts.map +0 -1
  120. package/dist/ui/containers/geometry-reader.js +0 -24
  121. package/dist/ui/containers/geometry-reader.js.map +0 -1
  122. package/dist/ui/containers/hstack.d.ts +0 -12
  123. package/dist/ui/containers/hstack.d.ts.map +0 -1
  124. package/dist/ui/containers/hstack.js +0 -28
  125. package/dist/ui/containers/hstack.js.map +0 -1
  126. package/dist/ui/containers/scroll.d.ts +0 -28
  127. package/dist/ui/containers/scroll.d.ts.map +0 -1
  128. package/dist/ui/containers/scroll.js +0 -97
  129. package/dist/ui/containers/scroll.js.map +0 -1
  130. package/dist/ui/containers/shared.d.ts +0 -12
  131. package/dist/ui/containers/shared.d.ts.map +0 -1
  132. package/dist/ui/containers/shared.js +0 -19
  133. package/dist/ui/containers/shared.js.map +0 -1
  134. package/dist/ui/containers/vstack.d.ts +0 -12
  135. package/dist/ui/containers/vstack.d.ts.map +0 -1
  136. package/dist/ui/containers/vstack.js +0 -28
  137. package/dist/ui/containers/vstack.js.map +0 -1
  138. package/dist/ui/containers/zstack.d.ts +0 -14
  139. package/dist/ui/containers/zstack.d.ts.map +0 -1
  140. package/dist/ui/containers/zstack.js +0 -36
  141. package/dist/ui/containers/zstack.js.map +0 -1
  142. package/dist/ui/core/geometry-store.d.ts +0 -22
  143. package/dist/ui/core/geometry-store.d.ts.map +0 -1
  144. package/dist/ui/core/geometry-store.js +0 -29
  145. package/dist/ui/core/geometry-store.js.map +0 -1
  146. package/dist/ui/core/geometry.d.ts +0 -34
  147. package/dist/ui/core/geometry.d.ts.map +0 -1
  148. package/dist/ui/core/geometry.js +0 -14
  149. package/dist/ui/core/geometry.js.map +0 -1
  150. package/dist/ui/core/view.d.ts +0 -25
  151. package/dist/ui/core/view.d.ts.map +0 -1
  152. package/dist/ui/core/view.js +0 -34
  153. package/dist/ui/core/view.js.map +0 -1
  154. package/dist/ui/index.d.ts +0 -44
  155. package/dist/ui/index.d.ts.map +0 -1
  156. package/dist/ui/index.js +0 -39
  157. package/dist/ui/index.js.map +0 -1
  158. package/dist/ui/inlinetext.d.ts +0 -24
  159. package/dist/ui/inlinetext.d.ts.map +0 -1
  160. package/dist/ui/inlinetext.js +0 -131
  161. package/dist/ui/inlinetext.js.map +0 -1
  162. package/dist/ui/install.d.ts +0 -22
  163. package/dist/ui/install.d.ts.map +0 -1
  164. package/dist/ui/install.js +0 -66
  165. package/dist/ui/install.js.map +0 -1
  166. package/dist/ui/markdown.d.ts +0 -40
  167. package/dist/ui/markdown.d.ts.map +0 -1
  168. package/dist/ui/markdown.js +0 -351
  169. package/dist/ui/markdown.js.map +0 -1
  170. package/dist/ui/modifiers/border.d.ts +0 -33
  171. package/dist/ui/modifiers/border.d.ts.map +0 -1
  172. package/dist/ui/modifiers/border.js +0 -82
  173. package/dist/ui/modifiers/border.js.map +0 -1
  174. package/dist/ui/modifiers/fill.d.ts +0 -14
  175. package/dist/ui/modifiers/fill.d.ts.map +0 -1
  176. package/dist/ui/modifiers/fill.js +0 -25
  177. package/dist/ui/modifiers/fill.js.map +0 -1
  178. package/dist/ui/modifiers/frame.d.ts +0 -23
  179. package/dist/ui/modifiers/frame.d.ts.map +0 -1
  180. package/dist/ui/modifiers/frame.js +0 -54
  181. package/dist/ui/modifiers/frame.js.map +0 -1
  182. package/dist/ui/modifiers/offset.d.ts +0 -15
  183. package/dist/ui/modifiers/offset.d.ts.map +0 -1
  184. package/dist/ui/modifiers/offset.js +0 -21
  185. package/dist/ui/modifiers/offset.js.map +0 -1
  186. package/dist/ui/modifiers/opacity.d.ts +0 -15
  187. package/dist/ui/modifiers/opacity.d.ts.map +0 -1
  188. package/dist/ui/modifiers/opacity.js +0 -95
  189. package/dist/ui/modifiers/opacity.js.map +0 -1
  190. package/dist/ui/modifiers/padding.d.ts +0 -20
  191. package/dist/ui/modifiers/padding.d.ts.map +0 -1
  192. package/dist/ui/modifiers/padding.js +0 -36
  193. package/dist/ui/modifiers/padding.js.map +0 -1
  194. package/dist/ui/modifiers/styled.d.ts +0 -14
  195. package/dist/ui/modifiers/styled.d.ts.map +0 -1
  196. package/dist/ui/modifiers/styled.js +0 -26
  197. package/dist/ui/modifiers/styled.js.map +0 -1
  198. package/dist/ui/primitives/rectangle.d.ts +0 -15
  199. package/dist/ui/primitives/rectangle.d.ts.map +0 -1
  200. package/dist/ui/primitives/rectangle.js +0 -23
  201. package/dist/ui/primitives/rectangle.js.map +0 -1
  202. package/dist/ui/primitives/spacer.d.ts +0 -13
  203. package/dist/ui/primitives/spacer.d.ts.map +0 -1
  204. package/dist/ui/primitives/spacer.js +0 -16
  205. package/dist/ui/primitives/spacer.js.map +0 -1
  206. package/dist/ui/primitives/text.d.ts +0 -15
  207. package/dist/ui/primitives/text.d.ts.map +0 -1
  208. package/dist/ui/primitives/text.js +0 -79
  209. package/dist/ui/primitives/text.js.map +0 -1
  210. package/dist/ui/primitives/wrapped-text.d.ts +0 -30
  211. package/dist/ui/primitives/wrapped-text.d.ts.map +0 -1
  212. package/dist/ui/primitives/wrapped-text.js +0 -117
  213. package/dist/ui/primitives/wrapped-text.js.map +0 -1
  214. package/dist/ui/shinytext.d.ts +0 -66
  215. package/dist/ui/shinytext.d.ts.map +0 -1
  216. package/dist/ui/shinytext.js +0 -99
  217. package/dist/ui/shinytext.js.map +0 -1
  218. package/dist/ui/text/layout.d.ts +0 -35
  219. package/dist/ui/text/layout.d.ts.map +0 -1
  220. package/dist/ui/text/layout.js +0 -102
  221. package/dist/ui/text/layout.js.map +0 -1
  222. package/dist/ui/textinput.d.ts +0 -140
  223. package/dist/ui/textinput.d.ts.map +0 -1
  224. package/dist/ui/textinput.js +0 -402
  225. package/dist/ui/textinput.js.map +0 -1
  226. package/dist/ui/view-constructors.d.ts +0 -72
  227. package/dist/ui/view-constructors.d.ts.map +0 -1
  228. package/dist/ui/view-constructors.js +0 -74
  229. package/dist/ui/view-constructors.js.map +0 -1
  230. package/src/anim.ts +0 -5
  231. package/src/layout/linearStack.ts +0 -115
  232. package/src/motion-value.ts +0 -335
  233. package/src/present/display.ts +0 -206
  234. package/src/present/writers/fullscreen.ts +0 -58
  235. package/src/present/writers/inline.ts +0 -101
  236. package/src/render/color-utils.ts +0 -60
  237. package/src/render/diff.ts +0 -95
  238. package/src/spring-physics.ts +0 -151
  239. package/src/spring.ts +0 -234
  240. package/src/ui/__snapshots__/wrappedtext.test.ts.snap +0 -57
  241. package/src/ui/containers/canvas.ts +0 -18
  242. package/src/ui/containers/geometry-reader.ts +0 -32
  243. package/src/ui/containers/hstack.ts +0 -33
  244. package/src/ui/containers/scroll.ts +0 -106
  245. package/src/ui/containers/shared.ts +0 -27
  246. package/src/ui/containers/vstack.ts +0 -34
  247. package/src/ui/containers/zstack.ts +0 -37
  248. package/src/ui/core/geometry-store.ts +0 -42
  249. package/src/ui/core/geometry.ts +0 -30
  250. package/src/ui/core/view.ts +0 -49
  251. package/src/ui/index.ts +0 -84
  252. package/src/ui/inlinetext.ts +0 -135
  253. package/src/ui/install.ts +0 -110
  254. package/src/ui/markdown.test.ts +0 -74
  255. package/src/ui/markdown.ts +0 -388
  256. package/src/ui/modifiers/border.ts +0 -100
  257. package/src/ui/modifiers/fill.ts +0 -28
  258. package/src/ui/modifiers/frame.ts +0 -74
  259. package/src/ui/modifiers/offset.ts +0 -23
  260. package/src/ui/modifiers/opacity.ts +0 -93
  261. package/src/ui/modifiers/padding.ts +0 -53
  262. package/src/ui/modifiers/styled.ts +0 -31
  263. package/src/ui/primitives/rectangle.ts +0 -25
  264. package/src/ui/primitives/spacer.ts +0 -18
  265. package/src/ui/primitives/text.ts +0 -85
  266. package/src/ui/primitives/wrapped-text.ts +0 -131
  267. package/src/ui/shinytext.ts +0 -159
  268. package/src/ui/text/layout.ts +0 -119
  269. package/src/ui/textinput.ts +0 -496
  270. package/src/ui/view-constructors.ts +0 -96
  271. package/src/ui/wrappedtext.test.ts +0 -138
@@ -1,74 +0,0 @@
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
- export interface FrameSpec {
6
- width?: number
7
- height?: number
8
- minWidth?: number
9
- maxWidth?: number
10
- minHeight?: number
11
- maxHeight?: number
12
- alignment?: Align2D
13
- }
14
-
15
- export class Frame extends View {
16
- constructor(
17
- readonly child: View,
18
- readonly opts: FrameSpec,
19
- ) {
20
- super()
21
- }
22
-
23
- protected measureContent(maxW: number, maxH: number) {
24
- const childMeasure = this.child.measure(maxW, maxH)
25
-
26
- const fs = this.opts
27
- const capW = Math.min(maxW, fs.maxWidth ?? maxW)
28
- const capH = Math.min(maxH, fs.maxHeight ?? maxH)
29
-
30
- let targetW = fs.width === Infinity ? capW : (fs.width ?? childMeasure.w)
31
- let targetH = fs.height === Infinity ? capH : (fs.height ?? childMeasure.h)
32
-
33
- targetW = Math.max(fs.minWidth ?? 0, Math.min(capW, targetW))
34
- targetH = Math.max(fs.minHeight ?? 0, Math.min(capH, targetH))
35
-
36
- return {
37
- w: Math.min(maxW, targetW),
38
- h: Math.min(maxH, targetH),
39
- }
40
- }
41
-
42
- protected renderContent(s: Surface, pal: Palette, rect: Rect) {
43
- // Measure child within our rect bounds
44
- const childMeasure = this.child.measure(rect.w, rect.h)
45
- const childW = Math.min(rect.w, childMeasure.w)
46
- const childH = Math.min(rect.h, childMeasure.h)
47
-
48
- // Apply alignment
49
- const alignment = this.opts.alignment ?? { h: "leading", v: "top" }
50
- let offsetX = 0
51
- let offsetY = 0
52
-
53
- if (alignment.h === "center") {
54
- offsetX = Math.floor((rect.w - childW) / 2)
55
- } else if (alignment.h === "trailing") {
56
- offsetX = Math.max(0, rect.w - childW)
57
- }
58
-
59
- if (alignment.v === "center") {
60
- offsetY = Math.floor((rect.h - childH) / 2)
61
- } else if (alignment.v === "bottom") {
62
- offsetY = Math.max(0, rect.h - childH)
63
- }
64
-
65
- const childRect: Rect = {
66
- x: rect.x + offsetX,
67
- y: rect.y + offsetY,
68
- w: childW,
69
- h: childH,
70
- }
71
-
72
- this.child.render(s, pal, childRect)
73
- }
74
- }
@@ -1,23 +0,0 @@
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
- // Offset wrapper: translate child's render position by (dx, dy) without affecting measurement
6
- export class Offset extends View {
7
- constructor(
8
- readonly child: View,
9
- readonly dx = 0,
10
- readonly dy = 0,
11
- ) {
12
- super()
13
- }
14
-
15
- protected measureContent(maxW: number, maxH: number) {
16
- return this.child.measure(maxW, maxH)
17
- }
18
-
19
- protected renderContent(s: Surface, pal: Palette, rect: Rect) {
20
- const r = { x: rect.x + (this.dx | 0), y: rect.y + (this.dy | 0), w: rect.w, h: rect.h }
21
- this.child.render(s, pal, r)
22
- }
23
- }
@@ -1,93 +0,0 @@
1
- import type { Surface, StyleSpec, ColorValue } from "../../render/surface.js"
2
- import { mergeStyle, Palette } from "../../render/surface.js"
3
- import { clamp255, idxToRGB } from "../../render/color-utils.js"
4
- import { View } from "../core/view.js"
5
- import type { Rect } from "../core/geometry.js"
6
-
7
- // Minimal palette wrapper that applies a style transform before delegating to the base palette
8
- class FilterPalette extends Palette {
9
- constructor(
10
- private readonly base: Palette,
11
- private readonly transform: (spec?: StyleSpec) => StyleSpec | undefined,
12
- ) {
13
- super()
14
- }
15
-
16
- override id(spec?: StyleSpec): number {
17
- return this.base.id(this.transform(spec))
18
- }
19
- override sgr(id: number): string {
20
- return this.base.sgr(id)
21
- }
22
- }
23
-
24
- function toRGB(c?: ColorValue): { r: number; g: number; b: number } | undefined {
25
- if (c == null) return undefined
26
- if (typeof c === "number") return idxToRGB(c)
27
- return { r: clamp255(c.r), g: clamp255(c.g), b: clamp255(c.b) }
28
- }
29
-
30
- function applyOpacity(spec: StyleSpec | undefined, alpha: number): StyleSpec | undefined {
31
- if (!spec) return spec
32
- const a = Math.max(0, Math.min(1, alpha))
33
- if (a === 1) return spec
34
- if (a <= 0) return {} // render as default style; caller may skip drawing entirely
35
- const next: StyleSpec = { ...spec }
36
- const fgc = toRGB(spec.fg)
37
- const bgc = toRGB(spec.bg)
38
- if (fgc) next.fg = { r: clamp255(fgc.r * a), g: clamp255(fgc.g * a), b: clamp255(fgc.b * a) }
39
- if (bgc) next.bg = { r: clamp255(bgc.r * a), g: clamp255(bgc.g * a), b: clamp255(bgc.b * a) }
40
- // Optionally taper bold to avoid harshness at low alpha
41
- if (spec.bold && a < 0.5) next.bold = false
42
- return next
43
- }
44
-
45
- export class Opacity extends View {
46
- constructor(
47
- readonly child: View,
48
- readonly alpha = 1,
49
- ) {
50
- super()
51
- }
52
-
53
- protected measureContent(maxW: number, maxH: number) {
54
- return this.child.measure(maxW, maxH)
55
- }
56
-
57
- protected renderContent(s: Surface, pal: Palette, rect: Rect) {
58
- const a = Math.max(0, Math.min(1, this.alpha))
59
- if (a <= 0.001) return // invisible; keep layout but skip drawing
60
- const filtered = new FilterPalette(pal, (spec) => applyOpacity(mergeStyle(undefined, spec), a))
61
- this.child.render(s, filtered, rect)
62
- }
63
- }
64
-
65
- /*
66
- * opacity.ts — composable opacity modifier for terminal views
67
- *
68
- * How it works (no real alpha in terminals):
69
- * - Terminals don’t support true alpha blending, and we can’t read the
70
- * current screen contents to composite. To approximate “opacity”, we
71
- * dim colors toward black by scaling their RGB channels by `alpha`.
72
- * At alpha=1 the colors are unchanged; at alpha=0 they become black.
73
- *
74
- * Implementation:
75
- * - We wrap the palette with a tiny FilterPalette that transforms any
76
- * StyleSpec handed to `palette.id()` before it is converted to SGR.
77
- * - The transform scales fg/bg colors by `alpha` and optionally disables
78
- * bold at low alpha to avoid harsh flashes.
79
- * - Rendering is skipped entirely when alpha ≤ ~0.001 (keeps layout but
80
- * avoids drawing noise while effectively invisible).
81
- *
82
- * Composition:
83
- * - This is a real modifier (wrapper node). You can chain it on any view:
84
- * View.text("Loading").opacity(0.5)
85
- * ShinyText.view(model).opacity(alpha).padding(0,1,0,1)
86
- * - Multiple opacities compound (roughly multiplicative dimming).
87
- * - Use it for fade in/out by driving `alpha` with a spring/motion value.
88
- *
89
- * Trade-offs:
90
- * - Fades toward black, not toward the terminal background color. For most
91
- * themes this reads as a natural fade. If you prefer a different look,
92
- * we could add a color-tint/brightness modifier using the same pattern.
93
- */
@@ -1,53 +0,0 @@
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 interface PaddingSpec {
6
- t: number
7
- r: number
8
- b: number
9
- l: number
10
- }
11
-
12
- function resolvePadding(t: number, r?: number, b?: number, l?: number): PaddingSpec {
13
- const top = Math.max(0, t | 0)
14
- const right = r == null ? top : Math.max(0, r | 0)
15
- const bottom = b == null ? top : Math.max(0, b | 0)
16
- const left = l == null ? right : Math.max(0, l | 0)
17
- return { t: top, r: right, b: bottom, l: left }
18
- }
19
-
20
- export class Padding extends View {
21
- private readonly pad: PaddingSpec
22
-
23
- constructor(
24
- readonly child: View,
25
- t: number,
26
- r?: number,
27
- b?: number,
28
- l?: number,
29
- ) {
30
- super()
31
- this.pad = resolvePadding(t, r, b, l)
32
- }
33
-
34
- protected measureContent(maxW: number, maxH: number) {
35
- const innerW = Math.max(0, maxW - this.pad.l - this.pad.r)
36
- const innerH = Math.max(0, maxH - this.pad.t - this.pad.b)
37
- const m = this.child.measure(innerW, innerH)
38
- return {
39
- w: Math.min(maxW, m.w + this.pad.l + this.pad.r),
40
- h: Math.min(maxH, m.h + this.pad.t + this.pad.b),
41
- }
42
- }
43
-
44
- protected renderContent(s: Surface, pal: Palette, rect: Rect) {
45
- const inner: Rect = {
46
- x: rect.x + this.pad.l,
47
- y: rect.y + this.pad.t,
48
- w: Math.max(0, rect.w - this.pad.l - this.pad.r),
49
- h: Math.max(0, rect.h - this.pad.t - this.pad.b),
50
- }
51
- this.child.render(s, pal, inner)
52
- }
53
- }
@@ -1,31 +0,0 @@
1
- import type { Palette, Surface, StyleSpec } from "../../render/surface.js"
2
- import { derivePalette } from "../../render/surface.js"
3
- import { View } from "../core/view.js"
4
- import type { Rect } from "../core/geometry.js"
5
-
6
- export class Styled extends View {
7
- constructor(
8
- readonly child: View,
9
- readonly styleSpec: StyleSpec,
10
- ) {
11
- super()
12
- }
13
-
14
- protected measureContent(maxW: number, maxH: number) {
15
- return this.child.measure(maxW, maxH)
16
- }
17
-
18
- protected renderContent(s: Surface, pal: Palette, rect: Rect) {
19
- // Create derived palette with our style applied
20
- const derivedPal = derivePalette(pal, this.styleSpec)
21
-
22
- // If we have a background color, fill the entire rect
23
- if (this.styleSpec.bg != null) {
24
- const id = derivedPal.id(this.styleSpec)
25
- s.fillRect(rect.x, rect.y, rect.w, rect.h, 32, id) // 32 = space character
26
- }
27
-
28
- // Render child with the styled palette
29
- this.child.render(s, derivedPal, rect)
30
- }
31
- }
@@ -1,25 +0,0 @@
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
- // Rectangle primitive (fills area with a codepoint, default space). Useful as a block element.
6
- export class Rectangle extends View {
7
- constructor(
8
- readonly width: number,
9
- readonly height: number,
10
- readonly fillCP = 32, // space character
11
- ) {
12
- super()
13
- }
14
-
15
- protected measureContent(maxW: number, maxH: number) {
16
- const w = Math.min(maxW, Math.max(0, this.width | 0))
17
- const h = Math.min(maxH, Math.max(0, this.height | 0))
18
- return { w, h }
19
- }
20
-
21
- protected renderContent(s: Surface, pal: Palette, rect: Rect) {
22
- const id = pal.id()
23
- s.fillRect(rect.x, rect.y, rect.w, rect.h, this.fillCP, id)
24
- }
25
- }
@@ -1,18 +0,0 @@
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
- // Spacer (flexible in stacks)
6
- export class Spacer extends View {
7
- constructor(readonly minLength = 0) {
8
- super()
9
- }
10
-
11
- protected measureContent(_maxW: number, _maxH: number) {
12
- return { w: 0, h: 0 }
13
- }
14
-
15
- protected renderContent(_s: Surface, _pal: Palette, _rect: Rect) {
16
- // Spacers render nothing
17
- }
18
- }
@@ -1,85 +0,0 @@
1
- import type { Palette, Surface } from "../../render/surface.js"
2
- import { displayWidth } from "../../render/measure.js"
3
- import { View } from "../core/view.js"
4
- import type { Rect } from "../core/geometry.js"
5
-
6
- export class Text extends View {
7
- constructor(
8
- readonly text: string,
9
- readonly wrap?: boolean,
10
- ) {
11
- super()
12
- }
13
-
14
- protected measureContent(maxW: number, maxH: number) {
15
- if (!this.wrap) {
16
- const w = Math.min(maxW, displayWidth(this.text))
17
- return { w, h: 1 }
18
- }
19
-
20
- // Wrapped text measurement
21
- const lines = this.wrapText(maxW)
22
- const height = Math.min(maxH, lines.length)
23
- let width = 0
24
- for (let i = 0; i < height; i++) {
25
- width = Math.max(width, Math.min(maxW, displayWidth(lines[i])))
26
- }
27
- return { w: width, h: height }
28
- }
29
-
30
- protected renderContent(s: Surface, pal: Palette, rect: Rect) {
31
- const id = pal.id()
32
- if (this.wrap) {
33
- // Use wrapping logic for rendering
34
- const lines = this.wrapText(rect.w)
35
- for (let i = 0; i < Math.min(lines.length, rect.h); i++) {
36
- const line = lines[i]
37
- if (line.length > 0) {
38
- s.drawText(rect.x, rect.y + i, line, id, rect.w)
39
- }
40
- }
41
- } else {
42
- s.drawText(rect.x, rect.y, this.text, id, rect.w)
43
- }
44
- }
45
-
46
- private wrapText(maxWidth: number): string[] {
47
- if (maxWidth <= 0) return [""]
48
- if (!this.text) return [""]
49
-
50
- const lines = this.text.split("\n")
51
- const result: string[] = []
52
-
53
- for (const line of lines) {
54
- if (line.length === 0) {
55
- result.push("")
56
- continue
57
- }
58
-
59
- const words = line.split(" ")
60
- let currentLine = ""
61
-
62
- for (const word of words) {
63
- const testLine = currentLine + (currentLine ? " " : "") + word
64
- if (displayWidth(testLine) <= maxWidth) {
65
- currentLine = testLine
66
- } else {
67
- if (currentLine) {
68
- result.push(currentLine)
69
- currentLine = word
70
- } else {
71
- // Single word longer than maxWidth
72
- result.push(word.slice(0, maxWidth))
73
- currentLine = word.slice(maxWidth)
74
- }
75
- }
76
- }
77
-
78
- if (currentLine) {
79
- result.push(currentLine)
80
- }
81
- }
82
-
83
- return result.length > 0 ? result : [""]
84
- }
85
- }
@@ -1,131 +0,0 @@
1
- import type { Palette, Surface } from "../../render/surface.js"
2
- import type { Rect } from "../core/geometry.js"
3
- import { View } from "../core/view.js"
4
- import { displayWidth, sliceByWidth } from "../../render/measure.js"
5
-
6
- export type WrappingOptions = {
7
- wordWrap?: boolean // default true - wrap at word boundaries
8
- breakWords?: boolean // default false - break long words that exceed width
9
- }
10
-
11
- export class WrappedText extends View {
12
- constructor(
13
- readonly text: string,
14
- readonly options: WrappingOptions = {},
15
- ) {
16
- super()
17
- }
18
-
19
- /**
20
- * Split text into lines, respecting wrapping rules
21
- */
22
- private wrapText(maxWidth: number): string[] {
23
- if (maxWidth <= 0) return [""]
24
- if (!this.text) return []
25
-
26
- const { wordWrap = true, breakWords = false } = this.options
27
-
28
- // First split on explicit line breaks
29
- const inputLines = this.text.split("\n")
30
- const wrappedLines: string[] = []
31
-
32
- for (const line of inputLines) {
33
- if (line.length === 0) {
34
- wrappedLines.push("")
35
- continue
36
- }
37
-
38
- if (line.length <= maxWidth) {
39
- wrappedLines.push(line)
40
- continue
41
- }
42
-
43
- // Line needs wrapping
44
- if (wordWrap) {
45
- this.wrapLineByWords(line, maxWidth, breakWords, wrappedLines)
46
- } else {
47
- this.wrapLineByCharacters(line, maxWidth, wrappedLines)
48
- }
49
- }
50
-
51
- return wrappedLines
52
- }
53
-
54
- /**
55
- * Wrap a line by word boundaries
56
- */
57
- private wrapLineByWords(line: string, maxWidth: number, breakWords: boolean, output: string[]): void {
58
- const words = line.split(/(\s+)/) // Keep whitespace in results
59
- let currentLine = ""
60
-
61
- for (const word of words) {
62
- // Skip empty strings
63
- if (word.length === 0) continue
64
-
65
- // If this is whitespace and we're at the start of a line, skip it
66
- if (/^\s+$/.test(word) && currentLine.length === 0) continue
67
-
68
- // If adding this word would exceed width
69
- if (displayWidth(currentLine + word) > maxWidth) {
70
- // If we have content on current line, save it and start new line
71
- if (currentLine.length > 0) {
72
- output.push(currentLine.trimEnd())
73
- currentLine = ""
74
- }
75
-
76
- // Handle long words
77
- if (displayWidth(word.trim()) > maxWidth) {
78
- if (breakWords) {
79
- this.wrapLineByCharacters(word.trim(), maxWidth, output)
80
- } else {
81
- // Don't break the word, just put it on its own line (may overflow)
82
- output.push(word.trim())
83
- }
84
- } else if (word.trim().length > 0) {
85
- // Start new line with this word (skip leading whitespace)
86
- currentLine = word.trim()
87
- }
88
- } else {
89
- currentLine += word
90
- }
91
- }
92
-
93
- // Add remaining content
94
- if (currentLine.length > 0) {
95
- output.push(currentLine.trimEnd())
96
- }
97
- }
98
-
99
- /**
100
- * Wrap a line by character boundaries
101
- */
102
- private wrapLineByCharacters(line: string, maxWidth: number, output: string[]): void {
103
- let rest = line
104
- while (rest.length > 0) {
105
- const { text } = sliceByWidth(rest, maxWidth)
106
- if (text.length === 0) break
107
- output.push(text)
108
- rest = rest.slice(text.length)
109
- }
110
- }
111
-
112
- protected measureContent(maxW: number, maxH: number): { w: number; h: number } {
113
- const lines = this.wrapText(maxW)
114
- const h = Math.min(maxH, Math.max(1, lines.length))
115
- // WrappedText is greedy horizontally - takes full available width when content exists
116
- const w = maxW
117
- return { w, h }
118
- }
119
-
120
- protected renderContent(s: Surface, pal: Palette, rect: Rect): void {
121
- const lines = this.wrapText(rect.w)
122
- const styleId = pal.id()
123
-
124
- for (let i = 0; i < Math.min(lines.length, rect.h); i++) {
125
- const line = lines[i]
126
- if (line.length > 0) {
127
- s.drawText(rect.x, rect.y + i, line, styleId, rect.w)
128
- }
129
- }
130
- }
131
- }
@@ -1,159 +0,0 @@
1
- /* shinytext.ts — reusable shining text component */
2
-
3
- import type { Palette, Surface } from "../render/surface.js"
4
- import { Colors } from "../render/surface.js"
5
- import type { Rect } from "./core/geometry.js"
6
- import { Step, type Stepper } from "../anim.js"
7
- import { View } from "./core/view.js"
8
-
9
- export type ShineTextOptions = {
10
- /**
11
- * Center position of the shine in character coordinates (can be fractional).
12
- * 0 = before first char, length-1 = last char. Values outside are fine for overscan.
13
- */
14
- center?: number
15
- /**
16
- * Alternative to center: normalized progress 0..1 across the text.
17
- * If provided, takes precedence and maps to center internally.
18
- */
19
- progress?: number
20
- /** Gaussian falloff spread; larger = wider shine. Default 2.25 */
21
- spread?: number
22
- /** Base gray level (0..23). Default 12 */
23
- baseGray?: number
24
- /** Max additional gray levels added at peak (0..23). Default 8 */
25
- boost?: number
26
- /** Bold threshold on intensity (0..1). Default 0.75 */
27
- boldThreshold?: number
28
- }
29
-
30
- export class ShinyText extends View {
31
- constructor(
32
- readonly text: string,
33
- readonly opts?: ShineTextOptions,
34
- ) {
35
- super()
36
- }
37
-
38
- private getChars(): string[] {
39
- return [...this.text]
40
- }
41
-
42
- protected measureContent(maxW: number, _maxH: number) {
43
- const w = Math.min(maxW, this.getChars().length)
44
- return { w, h: 1 }
45
- }
46
-
47
- protected renderContent(s: Surface, pal: Palette, rect: Rect) {
48
- const chars = this.getChars()
49
- const n = Math.min(rect.w, chars.length)
50
- if (rect.h <= 0 || n <= 0) return
51
-
52
- const spread = this.opts?.spread ?? 2.25
53
- const base = Math.max(0, Math.min(23, (this.opts?.baseGray ?? 12) | 0))
54
- const boost = Math.max(0, Math.min(23, (this.opts?.boost ?? 8) | 0))
55
- const boldTh = this.opts?.boldThreshold ?? 0.75
56
-
57
- const progress = this.opts?.progress
58
- const center = progress != null ? progress * (chars.length - 1) : (this.opts?.center ?? 0)
59
-
60
- const styleFor = (i: number) => {
61
- const d = (i - center) / Math.max(1e-3, spread)
62
- const intensity = Math.exp(-d * d)
63
- const level = Math.max(0, Math.min(23, base + Math.round(intensity * boost)))
64
- const bold = intensity > boldTh
65
- return pal.id({ fg: Colors.gray(level), bold })
66
- }
67
-
68
- // Emit minimal runs per style id
69
- const y = rect.y
70
- let x = rect.x
71
- let runStyle = -1
72
- let runStart = x
73
- let runText = ""
74
- const flush = () => {
75
- if (runText.length > 0) {
76
- const maxW = Math.max(0, rect.x + rect.w - runStart)
77
- if (maxW > 0) s.drawText(runStart, y, runText, runStyle, maxW)
78
- runText = ""
79
- }
80
- }
81
- for (let i = 0; i < n; i++) {
82
- const ch = chars[i]
83
- const id = styleFor(i)
84
- if (id !== runStyle) {
85
- flush()
86
- runStyle = id
87
- runStart = x
88
- }
89
- runText += ch
90
- x++
91
- }
92
- flush()
93
- }
94
- }
95
-
96
- // Optional builder contribution for the View object
97
- export type ViewShinyTextExt = {
98
- shineText(text: string, opts?: ShineTextOptions): View
99
- }
100
- export const viewShinyText: ViewShinyTextExt = {
101
- shineText(text: string, opts?: ShineTextOptions): View {
102
- return new ShinyText(text, opts)
103
- },
104
- }
105
-
106
- // ----------------------------------------------------------------------------
107
- // Stateful convenience API (init/tick/setSpeed + node factory)
108
- // ----------------------------------------------------------------------------
109
-
110
- export type ShinyTextModel = {
111
- text: string
112
- step: Stepper
113
- speedMs: number
114
- overscan: number
115
- shine?: Omit<ShineTextOptions, "center" | "progress">
116
- }
117
-
118
- export type InitShinyOptions = {
119
- speedMs?: number
120
- overscan?: number
121
- shine?: Omit<ShineTextOptions, "center" | "progress">
122
- }
123
-
124
- export function initShinyText(text: string, opts?: InitShinyOptions): ShinyTextModel {
125
- const speedMs = Math.max(1, (opts?.speedMs ?? 40) | 0)
126
- const overscan = Math.max(0, (opts?.overscan ?? 6) | 0)
127
- const width = [...text].length
128
- const step = Step.init(width + overscan * 2, speedMs, 0)
129
- return { text, step, speedMs, overscan, shine: opts?.shine }
130
- }
131
-
132
- export function tickShinyText(state: ShinyTextModel, nowMs: number): ShinyTextModel {
133
- const step = Step.tick(state.step, nowMs)
134
- return { ...state, step }
135
- }
136
-
137
- export function setShinySpeed(state: ShinyTextModel, speedMs: number): ShinyTextModel {
138
- const ms = Math.max(1, speedMs | 0)
139
- const step = { ...state.step, intervalMs: ms }
140
- return { ...state, speedMs: ms, step }
141
- }
142
-
143
- /** Create a ShinyText node from state. Use with chainers like .frame/.padding. */
144
- export function shinyText(state: ShinyTextModel): View {
145
- const center = state.step.index - state.overscan
146
- return new ShinyText(state.text, { ...state.shine, center })
147
- }
148
-
149
- // Merged namespace to expose a cohesive API: ShinyText.make(), ShinyText.tick(), ShinyText.setSpeed(), ShinyText.view()
150
- export namespace ShinyText {
151
- export type Model = ShinyTextModel
152
- export type Options = ShineTextOptions
153
- export type MakeOptions = InitShinyOptions
154
-
155
- export const make = initShinyText
156
- export const tick = tickShinyText
157
- export const setSpeed = setShinySpeed
158
- export const view = shinyText
159
- }