@effect-tui/core 0.1.1 → 0.1.5

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,101 +0,0 @@
1
- import type { RunWriter } from "../../render/diff.js"
2
- import type { Palette } from "../../render/surface.js"
3
- import { ANSI } from "../../ansi.js"
4
-
5
- type Seg = { col: number; style: number; text: string }
6
-
7
- export class LineDiffWriter implements RunWriter {
8
- private shadow = new Map<number, Seg[]>()
9
- private printed = new Map<number, Seg[]>() // canonical content per row
10
- private rows = 0
11
- private baseRow = 0
12
-
13
- constructor(private palette: Palette) {}
14
-
15
- begin(vp: { cols: number; rows: number }) {
16
- this.rows = vp.rows
17
- this.shadow.clear()
18
- }
19
-
20
- setBaseRow(n: number) {
21
- this.baseRow = Math.max(0, n | 0)
22
- }
23
-
24
- run(row: number, col: number, style: number, text: string) {
25
- if (!text) return
26
- const list = this.shadow.get(row) ?? []
27
- list.push({ col, style, text })
28
- this.shadow.set(row, list)
29
- }
30
-
31
- end() {
32
- /* no-op */
33
- }
34
-
35
- private sameRow(a?: Seg[], b?: Seg[]): boolean {
36
- if (!a && !b) return true
37
- if (!a || !b || a.length !== b.length) return false
38
- for (let i = 0; i < a.length; i++) {
39
- const A = a[i],
40
- B = b[i]
41
- if (A.col !== B.col || A.style !== B.style || A.text !== B.text) return false
42
- }
43
- return true
44
- }
45
-
46
- flush(): string {
47
- // Only process rows that the diff reported as changed.
48
- // Unchanged rows are left intact to avoid accidental clearing.
49
- const rowsToProcess = Array.from(this.shadow.keys()).sort((a, b) => a - b)
50
-
51
- let out = ""
52
-
53
- for (const row of rowsToProcess) {
54
- const segs = (this.shadow.get(row) ?? []).sort((a, b) => a.col - b.col)
55
- const prev = this.printed.get(row)
56
- if (this.sameRow(prev, segs)) continue // nothing changed effectively
57
-
58
- // coalesce adjacent segments with same style (keep this optimization)
59
- const merged: Seg[] = []
60
- for (const s of segs) {
61
- const last = merged[merged.length - 1]
62
- if (last && last.style === s.style && last.col + last.text.length === s.col) {
63
- last.text += s.text
64
- } else {
65
- merged.push({ ...s })
66
- }
67
- }
68
-
69
- // Move to absolute row (1-based) and place segments in-place.
70
- // Avoid clearing the entire line; we only overwrite changed cells.
71
- const termRow1 = this.baseRow + row + 1
72
- out += ANSI.cursor.to(termRow1, 1)
73
-
74
- let curStyle = -1
75
- for (const s of merged) {
76
- // Move to column (1-based) within the current row
77
- out += ANSI.cursor.toCol(s.col + 1)
78
- if (curStyle !== s.style) {
79
- out += this.palette.sgr(s.style)
80
- curStyle = s.style
81
- }
82
- out += s.text
83
- }
84
- if (curStyle !== -1) out += ANSI.reset
85
- this.printed.set(row, merged)
86
- }
87
-
88
- // Determine printed height (last row index with any segments + 1)
89
- let lastRow = -1
90
- for (const [r, segs] of this.printed) {
91
- if (segs.length > 0 && r > lastRow) lastRow = r
92
- }
93
- const printedHeight = lastRow + 1
94
-
95
- // Park cursor at bottom-left of our region (clamped to viewport)
96
- const parkRow = Math.min(this.rows, Math.max(1, this.baseRow + (printedHeight > 0 ? printedHeight : 1)))
97
- out += ANSI.cursor.to(parkRow, 1)
98
-
99
- return out
100
- }
101
- }
@@ -1,60 +0,0 @@
1
- // color-utils.ts - Shared color conversion utilities
2
-
3
- /**
4
- * Clamp a number to the 0-255 range and convert to integer.
5
- */
6
- export function clamp255(n: number): number {
7
- return n < 0 ? 0 : n > 255 ? 255 : n | 0
8
- }
9
-
10
- /**
11
- * Convert a 256-color index to RGB values (approximate xterm palette).
12
- *
13
- * The 256-color palette is divided into:
14
- * - 0-15: Standard ANSI colors (system colors)
15
- * - 16-231: 6x6x6 RGB cube
16
- * - 232-255: Grayscale ramp (24 shades)
17
- */
18
- export function idxToRGB(idx: number): { r: number; g: number; b: number } {
19
- if (idx < 0) idx = 0
20
- if (idx > 255) idx = 255
21
-
22
- // Standard ANSI colors (0-15)
23
- if (idx < 16) {
24
- const base: Array<[number, number, number]> = [
25
- [0x00, 0x00, 0x00], // 0: black
26
- [0x80, 0x00, 0x00], // 1: red
27
- [0x00, 0x80, 0x00], // 2: green
28
- [0x80, 0x80, 0x00], // 3: yellow
29
- [0x00, 0x00, 0x80], // 4: blue
30
- [0x80, 0x00, 0x80], // 5: magenta
31
- [0x00, 0x80, 0x80], // 6: cyan
32
- [0xc0, 0xc0, 0xc0], // 7: white
33
- [0x80, 0x80, 0x80], // 8: bright black (gray)
34
- [0xff, 0x00, 0x00], // 9: bright red
35
- [0x00, 0xff, 0x00], // 10: bright green
36
- [0xff, 0xff, 0x00], // 11: bright yellow
37
- [0x00, 0x00, 0xff], // 12: bright blue
38
- [0xff, 0x00, 0xff], // 13: bright magenta
39
- [0x00, 0xff, 0xff], // 14: bright cyan
40
- [0xff, 0xff, 0xff], // 15: bright white
41
- ]
42
- const [r, g, b] = base[idx] ?? [0, 0, 0]
43
- return { r, g, b }
44
- }
45
-
46
- // Grayscale ramp (232-255)
47
- if (idx >= 232) {
48
- const n = idx - 232 // 0..23
49
- const v = 8 + n * 10
50
- return { r: v, g: v, b: v }
51
- }
52
-
53
- // 6x6x6 RGB cube (16-231)
54
- const n = idx - 16 // 0..215
55
- const r = Math.floor(n / 36) % 6
56
- const g = Math.floor(n / 6) % 6
57
- const b = n % 6
58
- const steps = [0, 95, 135, 175, 215, 255]
59
- return { r: steps[r] ?? 0, g: steps[g] ?? 0, b: steps[b] ?? 0 }
60
- }
@@ -1,95 +0,0 @@
1
- import type { Surface } from "./surface.js"
2
-
3
- /**
4
- * Writer that consumes style runs produced by diffing two frames.
5
- * - Coordinates are 0-based: `row` in [0..rows), `col` in [0..cols).
6
- * - `begin` is called once with the viewport size, then `run` zero or more times,
7
- * then `end`. Implementations may buffer and expose the result via `flush`.
8
- */
9
- export interface RunWriter {
10
- begin(viewport: { cols: number; rows: number }): void
11
- run(row: number, col: number, styleId: number, text: string): void
12
- end(): void
13
- flush(): string
14
- }
15
-
16
- /**
17
- * Stream a minimal set of style-runs for changes from prev → next.
18
- *
19
- * Algorithm (row-wise):
20
- * - For each row, compute a tight change window [left, right]. Skip entirely if equal.
21
- * - Within the window, emit contiguous runs grouped by style. A new run begins when:
22
- * - A cell changes, and
23
- * - Its style differs from the current run (or the run just started).
24
- * - Wide glyph continuation cells (cw=0) are skipped so they are not reprinted.
25
- *
26
- * Assumes both Surfaces are same size and compares their back buffers (B).
27
- */
28
- export function diffFrames(prev: Surface, next: Surface, w: RunWriter): void {
29
- const width = next.w
30
- const height = next.h
31
- // Access back buffers (like Surface.flush does). B is private on Surface, so use `any`.
32
- const gA = (prev as any).B.g as Uint32Array
33
- const sA = (prev as any).B.s as Uint32Array
34
- const gB = (next as any).B.g as Uint32Array
35
- const sB = (next as any).B.s as Uint32Array
36
- const cwB = (next as any).B.cw as Uint8Array
37
-
38
- w.begin({ cols: width, rows: height })
39
-
40
- for (let y = 0; y < height; y++) {
41
- const row = y * width
42
-
43
- // Fast skip window: [left..right] bounds that changed
44
- let left = 0,
45
- right = width - 1
46
- while (left <= right) {
47
- const i = row + left
48
- if (gA[i] === gB[i] && sA[i] === sB[i]) left++
49
- else break
50
- }
51
- if (left > right) continue
52
-
53
- while (right >= left) {
54
- const i = row + right
55
- if (gA[i] === gB[i] && sA[i] === sB[i]) right--
56
- else break
57
- }
58
-
59
- // Emit contiguous style runs within [left..right]
60
- let x = left
61
- while (x <= right) {
62
- // Skip unchanged within window
63
- while (x <= right) {
64
- const i = row + x
65
- if (gA[i] !== gB[i] || sA[i] !== sB[i]) break
66
- x++
67
- }
68
- if (x > right) break
69
-
70
- const style = sB[row + x]
71
- const runX = x
72
- let text = ""
73
-
74
- while (x <= right) {
75
- const i = row + x
76
- if (sB[i] !== style) break
77
- const cp = gB[i]
78
- const ww = cwB[i]
79
- if (ww !== 0) text += cp === 32 ? " " : String.fromCodePoint(cp)
80
- x++
81
- // skip continuation cells of wide glyphs from the next buffer
82
- while (x <= right && cwB[row + x] === 0) x++
83
-
84
- // Stop run if next cell is unchanged; diff window will reposition
85
- if (x <= right) {
86
- const j = row + x
87
- if (gA[j] === gB[j] && sA[j] === sB[j]) break
88
- }
89
- }
90
-
91
- if (text.length > 0) w.run(y, runX, style, text)
92
- }
93
- }
94
- w.end()
95
- }
@@ -1,151 +0,0 @@
1
- // spring-physics.ts - Core spring physics primitives
2
-
3
- export type SpringState = {
4
- // current normalized position (e.g., 0..1)
5
- x: number
6
- // current velocity in units per second
7
- v: number
8
- // target normalized position
9
- target: number
10
- }
11
-
12
- export type SpringConfig = {
13
- // natural frequency in Hz (cycles per second). e.g., 3 = fairly snappy
14
- frequency?: number
15
- // damping ratio (1 = critical damping, < 1 underdamped, > 1 overdamped)
16
- dampingRatio?: number
17
- // when |x-target| and |v| fall below epsilon, consider settled
18
- epsilon?: number
19
- // Optional UX-style controls akin to Motion libraries:
20
- // approximate settle time in seconds (to ~2% band)
21
- duration?: number
22
- // bounce amount (0..1), maps to overshoot ratio; default 0 = no overshoot
23
- bounce?: number
24
- }
25
-
26
- export const DEFAULTS: Required<Omit<SpringConfig, "duration">> & { duration?: number } = {
27
- frequency: 3,
28
- dampingRatio: 1,
29
- epsilon: 0.0005,
30
- duration: undefined,
31
- bounce: 0,
32
- }
33
-
34
- export function fromDurationBounce(durationSec: number, bounce = 0): { frequency: number; dampingRatio: number } {
35
- const d = Math.max(0.05, durationSec)
36
- let zeta: number
37
- const b = Math.max(0, Math.min(0.999, bounce))
38
- if (b <= 0) {
39
- zeta = 1 // critically damped, no overshoot
40
- } else {
41
- // Map overshoot ratio M_p=b to damping ratio via standard 2nd-order step response
42
- const lnMp = Math.log(Math.max(1e-6, b))
43
- zeta = Math.sqrt((lnMp * lnMp) / (Math.PI * Math.PI + lnMp * lnMp))
44
- zeta = Math.max(0.02, Math.min(0.999, zeta))
45
- }
46
- // Use ~2% settling time approximation Ts ≈ 4/(ζ ω_n)
47
- const omega = 4 / (Math.max(0.02, zeta) * d)
48
- const freq = omega / (2 * Math.PI)
49
- return { frequency: freq, dampingRatio: zeta }
50
- }
51
-
52
- /**
53
- * Advance a 1D damped spring towards its target using a semi-implicit Euler step.
54
- * - Stable enough for small dt (<= ~1/30s) typical of frame ticks.
55
- * - Uses frequency/dampingRatio to feel similar across frame rates.
56
- */
57
- export function advanceSpring(
58
- state: SpringState,
59
- dtSeconds: number,
60
- config?: SpringConfig,
61
- ): SpringState & { settled: boolean } {
62
- let { frequency, dampingRatio, epsilon } = { ...DEFAULTS, ...(config ?? {}) }
63
- const bounce = config?.bounce ?? DEFAULTS.bounce
64
- // If duration is provided, derive frequency/dampingRatio from it and bounce
65
- if (config?.duration != null) {
66
- const derived = fromDurationBounce(config.duration, config.bounce ?? DEFAULTS.bounce)
67
- frequency = derived.frequency
68
- dampingRatio = derived.dampingRatio
69
- }
70
- const omega = Math.PI * 2 * Math.max(0.001, frequency)
71
- const k = omega * omega // stiffness
72
- const c = 2 * dampingRatio * omega // damping
73
-
74
- let { x, v, target } = state
75
- const prevX = x
76
- if (dtSeconds <= 0)
77
- return {
78
- x,
79
- v,
80
- target,
81
- settled: Math.abs(x - target) < epsilon && Math.abs(v) < epsilon,
82
- }
83
-
84
- // semi-implicit Euler integration
85
- const a = k * (target - x) - c * v
86
- v += a * dtSeconds
87
- x += v * dtSeconds
88
-
89
- // With bounce=0 users expect a monotonic approach; clamp if we cross the target.
90
- if (bounce <= 0) {
91
- const crossed = (prevX - target) * (x - target) <= 0
92
- if (crossed) {
93
- x = target
94
- v = 0
95
- }
96
- }
97
-
98
- // snap to target when sufficiently close to avoid long tails
99
- const settled = Math.abs(x - target) < epsilon && Math.abs(v) < epsilon
100
- if (settled) {
101
- x = target
102
- v = 0
103
- }
104
- return { x, v, target, settled }
105
- }
106
-
107
- /**
108
- * Internal exact spring step for 1D channel (frame-rate independent).
109
- * Uses closed-form analytical solution for underdamped/critically-damped/overdamped cases.
110
- */
111
- export function stepSpring1D(
112
- x: number,
113
- v: number,
114
- target: number,
115
- dt: number,
116
- freq: number,
117
- zeta: number,
118
- ): { x: number; v: number } {
119
- const omega = Math.max(0.001, freq) * 2 * Math.PI
120
- const A0 = x - target
121
- const exp = Math.exp(-zeta * omega * dt)
122
- if (zeta < 1) {
123
- // Underdamped
124
- const wd = omega * Math.sqrt(1 - zeta * zeta)
125
- const cos = Math.cos(wd * dt)
126
- const sin = Math.sin(wd * dt)
127
- const B0 = v + zeta * omega * A0
128
- const A = exp * (A0 * cos + (B0 / wd) * sin)
129
- const dA = exp * (-zeta * omega * (A0 * cos + (B0 / wd) * sin) + (-A0 * wd * sin + B0 * cos))
130
- return { x: target + A, v: dA }
131
- } else if (zeta === 1) {
132
- // Critically damped
133
- const t = dt
134
- const C2 = v + omega * A0
135
- const A = exp * (A0 + C2 * t)
136
- const dA = exp * (C2 - omega * (A0 + C2 * t))
137
- return { x: target + A, v: dA }
138
- } else {
139
- // Overdamped
140
- const s = Math.sqrt(zeta * zeta - 1)
141
- const r1 = -omega * (zeta - s)
142
- const r2 = -omega * (zeta + s)
143
- const C1 = (v - r2 * A0) / (r1 - r2)
144
- const C2 = A0 - C1
145
- const e1 = Math.exp(r1 * dt)
146
- const e2 = Math.exp(r2 * dt)
147
- const A = C1 * e1 + C2 * e2
148
- const dA = C1 * r1 * e1 + C2 * r2 * e2
149
- return { x: target + A, v: dA }
150
- }
151
- }
package/src/spring.ts DELETED
@@ -1,234 +0,0 @@
1
- // spring.ts - Higher-level spring API for number and color values
2
-
3
- import type { ColorLike, ColorValue } from "./render/surface.js"
4
- import { parseColor } from "./render/surface.js"
5
- import { idxToRGB } from "./render/color-utils.js"
6
- import { advanceSpring, DEFAULTS, type SpringConfig, type SpringState } from "./spring-physics.js"
7
-
8
- // ---- Types ----
9
-
10
- export type SpringValue = SpringState & {
11
- /** last timestamp in ms used for dt; hidden from callers */
12
- _last?: number
13
- /** default config for ticks (optional) */
14
- _config?: SpringConfig
15
- }
16
-
17
- export type SpringColorValue = {
18
- x: { r: number; g: number; b: number }
19
- v: { r: number; g: number; b: number }
20
- target: { r: number; g: number; b: number }
21
- _last?: number
22
- _config?: SpringConfig
23
- }
24
-
25
- export type AnySpringValue = SpringValue | SpringColorValue
26
-
27
- export type SpringAutoOptions = SpringConfig & {
28
- as?: "auto" | "number" | "color"
29
- }
30
-
31
- // ---- Helpers ----
32
-
33
- export function colorLikeToRGB(c: ColorLike): { r: number; g: number; b: number } {
34
- const v = parseColor(c)
35
- if (typeof v === "number") return idxToRGB(v)
36
- return { r: v.r | 0, g: v.g | 0, b: v.b | 0 }
37
- }
38
-
39
- function isColorSpring(s: AnySpringValue): s is SpringColorValue {
40
- return typeof (s as any).x === "object"
41
- }
42
-
43
- // ---- Number spring primitives ----
44
-
45
- function initNumber(initial: number, cfg?: SpringConfig): SpringValue {
46
- return {
47
- x: initial,
48
- v: 0,
49
- target: initial,
50
- ...(cfg && { _config: cfg }),
51
- }
52
- }
53
-
54
- function tickNumber(s: SpringValue, nowMs: number, cfg?: SpringConfig): SpringValue {
55
- const last = s._last ?? nowMs
56
- const dt = Math.max(0, Math.min(0.1, (nowMs - last) / 1000))
57
- const stepped = advanceSpring(s, dt, cfg ?? s._config)
58
- return {
59
- ...s,
60
- x: stepped.x,
61
- v: stepped.v,
62
- target: stepped.target,
63
- _last: nowMs,
64
- ...(cfg || s._config ? { _config: cfg ?? s._config } : {}),
65
- }
66
- }
67
-
68
- function toNumber(s: SpringValue, target: number): SpringValue {
69
- return { ...s, target }
70
- }
71
-
72
- function valueNumber(s: SpringValue): number {
73
- return s.x
74
- }
75
-
76
- function settledNumber(s: SpringValue, eps = DEFAULTS.epsilon): boolean {
77
- return Math.abs(s.x - s.target) < eps && Math.abs(s.v) < eps
78
- }
79
-
80
- // ---- Color spring primitives ----
81
-
82
- const SpringColor = {
83
- init(initial: ColorLike, cfg?: SpringConfig): SpringColorValue {
84
- const rgb = colorLikeToRGB(initial)
85
- return {
86
- x: { ...rgb },
87
- v: { r: 0, g: 0, b: 0 },
88
- target: { ...rgb },
89
- ...(cfg && { _config: cfg }),
90
- }
91
- },
92
- to(s: SpringColorValue, target: ColorLike): SpringColorValue {
93
- const rgb = colorLikeToRGB(target)
94
- return { ...s, target: { ...rgb } }
95
- },
96
- tick(s: SpringColorValue, nowMs: number, cfg?: SpringConfig): SpringColorValue {
97
- const last = s._last ?? nowMs
98
- const dt = Math.max(0, Math.min(0.1, (nowMs - last) / 1000))
99
- const rx = advanceSpring({ x: s.x.r, v: s.v.r, target: s.target.r }, dt, cfg ?? s._config)
100
- const gx = advanceSpring({ x: s.x.g, v: s.v.g, target: s.target.g }, dt, cfg ?? s._config)
101
- const bx = advanceSpring({ x: s.x.b, v: s.v.b, target: s.target.b }, dt, cfg ?? s._config)
102
- return {
103
- x: { r: rx.x, g: gx.x, b: bx.x },
104
- v: { r: rx.v, g: gx.v, b: bx.v },
105
- target: { r: s.target.r, g: s.target.g, b: s.target.b },
106
- _last: nowMs,
107
- ...(cfg || s._config ? { _config: cfg ?? s._config } : {}),
108
- }
109
- },
110
- value(s: SpringColorValue): ColorValue {
111
- const clamp = (n: number) => (n < 0 ? 0 : n > 255 ? 255 : n | 0)
112
- return { r: clamp(s.x.r), g: clamp(s.x.g), b: clamp(s.x.b) }
113
- },
114
- settled(s: SpringColorValue, eps = DEFAULTS.epsilon): boolean {
115
- const dx = Math.abs(s.x.r - s.target.r) + Math.abs(s.x.g - s.target.g) + Math.abs(s.x.b - s.target.b)
116
- const dv = Math.abs(s.v.r) + Math.abs(s.v.g) + Math.abs(s.v.b)
117
- return dx < eps * 3 && dv < eps * 3
118
- },
119
- }
120
-
121
- // ---- Unified, type-agnostic helpers ----
122
-
123
- // Overloads for the ergonomic, single-API surface
124
- function create(initial: number, cfg?: SpringAutoOptions): SpringValue
125
- function create(initial: ColorLike, cfg?: SpringAutoOptions): SpringColorValue
126
- function create(initial: number | ColorLike, cfg?: SpringAutoOptions): AnySpringValue {
127
- const as = cfg?.as ?? (typeof initial === "number" ? "number" : "color")
128
- return as === "number" ? initNumber(initial as number, cfg) : SpringColor.init(initial as ColorLike, cfg)
129
- }
130
-
131
- function to(s: SpringValue, target: number): SpringValue
132
- function to(s: SpringColorValue, target: ColorLike): SpringColorValue
133
- function to(s: AnySpringValue, target: number | ColorLike): AnySpringValue {
134
- return isColorSpring(s) ? SpringColor.to(s, target as ColorLike) : toNumber(s as SpringValue, target as number)
135
- }
136
-
137
- function tick(s: SpringValue, nowMs: number, cfg?: SpringConfig): SpringValue
138
- function tick(s: SpringColorValue, nowMs: number, cfg?: SpringConfig): SpringColorValue
139
- function tick(s: AnySpringValue, nowMs: number, cfg?: SpringConfig): AnySpringValue {
140
- return isColorSpring(s) ? SpringColor.tick(s, nowMs, cfg) : tickNumber(s as SpringValue, nowMs, cfg)
141
- }
142
-
143
- function read(s: SpringValue): number
144
- function read(s: SpringColorValue): ColorValue
145
- function read(s: AnySpringValue): number | ColorValue {
146
- return isColorSpring(s) ? SpringColor.value(s) : valueNumber(s as SpringValue)
147
- }
148
-
149
- function isSettled(s: SpringValue, eps?: number): boolean
150
- function isSettled(s: SpringColorValue, eps?: number): boolean
151
- function isSettled(s: AnySpringValue, eps = DEFAULTS.epsilon): boolean {
152
- return isColorSpring(s) ? SpringColor.settled(s, eps) : settledNumber(s as SpringValue, eps)
153
- }
154
-
155
- // ---- Public Spring API ----
156
-
157
- export type SpringAPI = {
158
- // number
159
- init(initial: number, cfg?: SpringConfig): SpringValue
160
- to(s: SpringValue, target: number): SpringValue
161
- tick(s: SpringValue, nowMs: number, cfg?: SpringConfig): SpringValue
162
- value(s: SpringValue): number
163
- settled(s: SpringValue, eps?: number): boolean
164
-
165
- // color (legacy explicit path kept for convenience)
166
- color: {
167
- init(initial: ColorLike, cfg?: SpringConfig): SpringColorValue
168
- to(s: SpringColorValue, target: ColorLike): SpringColorValue
169
- tick(s: SpringColorValue, nowMs: number, cfg?: SpringConfig): SpringColorValue
170
- value(s: SpringColorValue): ColorValue
171
- settled(s: SpringColorValue, eps?: number): boolean
172
- }
173
-
174
- // unified ergonomic API (overloads)
175
- create(initial: number, cfg?: SpringAutoOptions): SpringValue
176
- create(initial: ColorLike, cfg?: SpringAutoOptions): SpringColorValue
177
- to(s: SpringColorValue, target: ColorLike): SpringColorValue // overload of number variant
178
- tick(s: SpringColorValue, nowMs: number, cfg?: SpringConfig): SpringColorValue // overload of number variant
179
- advance(s: SpringValue, nowMs: number, cfg?: SpringConfig): SpringValue
180
- advance(s: SpringColorValue, nowMs: number, cfg?: SpringConfig): SpringColorValue
181
- read(s: SpringValue): number
182
- read(s: SpringColorValue): ColorValue
183
- isSettled(s: SpringValue, eps?: number): boolean
184
- isSettled(s: SpringColorValue, eps?: number): boolean
185
- }
186
-
187
- export const Spring: SpringAPI = {
188
- // number
189
- init: initNumber,
190
- to,
191
- tick,
192
- value: valueNumber,
193
- settled: settledNumber,
194
-
195
- // color (explicit path)
196
- color: SpringColor,
197
-
198
- // unified ergonomic API
199
- create,
200
- advance: tick,
201
- read,
202
- isSettled,
203
- }
204
-
205
- // ---- Stepper (frame-based discrete animation) ----
206
-
207
- export type Stepper = {
208
- index: number
209
- length: number
210
- intervalMs: number
211
- _accumMs: number
212
- _last?: number
213
- }
214
-
215
- export const Step = {
216
- init(length: number, intervalMs: number, startIndex = 0): Stepper {
217
- return {
218
- index: ((startIndex % length) + length) % length,
219
- length: Math.max(1, length | 0),
220
- intervalMs: Math.max(1, intervalMs | 0),
221
- _accumMs: 0,
222
- }
223
- },
224
- tick(s: Stepper, nowMs: number): Stepper {
225
- const last = s._last ?? nowMs
226
- let accum = s._accumMs + Math.max(0, nowMs - last)
227
- let idx = s.index
228
- while (accum >= s.intervalMs) {
229
- accum -= s.intervalMs
230
- idx = (idx + 1) % s.length
231
- }
232
- return { ...s, index: idx, _accumMs: accum, _last: nowMs }
233
- },
234
- }
@@ -1,57 +0,0 @@
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
- `;