@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.
- package/README.md +31 -11
- package/dist/ansi.d.ts +127 -32
- package/dist/ansi.d.ts.map +1 -1
- package/dist/ansi.js +159 -37
- package/dist/ansi.js.map +1 -1
- package/dist/colors.d.ts +139 -0
- package/dist/colors.d.ts.map +1 -0
- package/dist/colors.js +339 -0
- package/dist/colors.js.map +1 -0
- package/dist/index.d.ts +6 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -11
- package/dist/index.js.map +1 -1
- package/dist/keys.d.ts +21 -0
- package/dist/keys.d.ts.map +1 -1
- package/dist/keys.js +199 -58
- package/dist/keys.js.map +1 -1
- package/dist/layout/axis-helpers.d.ts +19 -0
- package/dist/layout/axis-helpers.d.ts.map +1 -0
- package/dist/layout/axis-helpers.js +19 -0
- package/dist/layout/axis-helpers.js.map +1 -0
- package/dist/output.d.ts +59 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +142 -0
- package/dist/output.js.map +1 -0
- package/dist/render/buffer.d.ts.map +1 -1
- package/dist/render/buffer.js +6 -25
- package/dist/render/buffer.js.map +1 -1
- package/dist/render/graphemes.d.ts +15 -0
- package/dist/render/graphemes.d.ts.map +1 -0
- package/dist/render/graphemes.js +28 -0
- package/dist/render/graphemes.js.map +1 -0
- package/dist/render/measure.d.ts +1 -0
- package/dist/render/measure.d.ts.map +1 -1
- package/dist/render/measure.js +14 -36
- package/dist/render/measure.js.map +1 -1
- package/dist/render/palette.d.ts.map +1 -1
- package/dist/render/palette.js +26 -1
- package/dist/render/palette.js.map +1 -1
- package/dist/render/segmenter.d.ts +8 -0
- package/dist/render/segmenter.d.ts.map +1 -0
- package/dist/render/segmenter.js +23 -0
- package/dist/render/segmenter.js.map +1 -0
- package/dist/render/surface.d.ts +6 -32
- package/dist/render/surface.d.ts.map +1 -1
- package/dist/render/surface.js +11 -80
- package/dist/render/surface.js.map +1 -1
- package/dist/runtime/backend_node.d.ts.map +1 -1
- package/dist/runtime/backend_node.js.map +1 -1
- package/dist/tailwind-colors.d.ts +291 -0
- package/dist/tailwind-colors.d.ts.map +1 -0
- package/dist/tailwind-colors.js +291 -0
- package/dist/tailwind-colors.js.map +1 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -55
- package/src/ansi.ts +201 -73
- package/src/colors.ts +468 -0
- package/src/index.ts +28 -14
- package/src/keys.ts +467 -287
- package/src/layout/axis-helpers.ts +33 -0
- package/src/output.ts +175 -0
- package/src/render/buffer.ts +161 -184
- package/src/render/graphemes.ts +34 -0
- package/src/render/measure.ts +15 -38
- package/src/render/palette.ts +98 -77
- package/src/render/segmenter.ts +27 -0
- package/src/render/surface.ts +139 -225
- package/src/runtime/backend_node.ts +71 -71
- package/src/tailwind-colors.ts +295 -0
- package/src/types.ts +18 -0
- package/dist/anim.d.ts +0 -4
- package/dist/anim.d.ts.map +0 -1
- package/dist/anim.js +0 -5
- package/dist/anim.js.map +0 -1
- package/dist/layout/linearStack.d.ts +0 -17
- package/dist/layout/linearStack.d.ts.map +0 -1
- package/dist/layout/linearStack.js +0 -86
- package/dist/layout/linearStack.js.map +0 -1
- package/dist/motion-value.d.ts +0 -58
- package/dist/motion-value.d.ts.map +0 -1
- package/dist/motion-value.js +0 -250
- package/dist/motion-value.js.map +0 -1
- package/dist/present/display.d.ts +0 -58
- package/dist/present/display.d.ts.map +0 -1
- package/dist/present/display.js +0 -168
- package/dist/present/display.js.map +0 -1
- package/dist/present/writers/fullscreen.d.ts +0 -19
- package/dist/present/writers/fullscreen.d.ts.map +0 -1
- package/dist/present/writers/fullscreen.js +0 -55
- package/dist/present/writers/fullscreen.js.map +0 -1
- package/dist/present/writers/inline.d.ts +0 -20
- package/dist/present/writers/inline.d.ts.map +0 -1
- package/dist/present/writers/inline.js +0 -92
- package/dist/present/writers/inline.js.map +0 -1
- package/dist/render/color-utils.d.ts +0 -18
- package/dist/render/color-utils.d.ts.map +0 -1
- package/dist/render/color-utils.js +0 -58
- package/dist/render/color-utils.js.map +0 -1
- package/dist/render/diff.d.ts +0 -30
- package/dist/render/diff.d.ts.map +0 -1
- package/dist/render/diff.js +0 -83
- package/dist/render/diff.js.map +0 -1
- package/dist/spring-physics.d.ts +0 -36
- package/dist/spring-physics.d.ts.map +0 -1
- package/dist/spring-physics.js +0 -113
- package/dist/spring-physics.js.map +0 -1
- package/dist/spring.d.ts +0 -73
- package/dist/spring.d.ts.map +0 -1
- package/dist/spring.js +0 -136
- package/dist/spring.js.map +0 -1
- package/dist/ui/containers/canvas.d.ts +0 -13
- package/dist/ui/containers/canvas.d.ts.map +0 -1
- package/dist/ui/containers/canvas.js +0 -16
- package/dist/ui/containers/canvas.js.map +0 -1
- package/dist/ui/containers/geometry-reader.d.ts +0 -17
- package/dist/ui/containers/geometry-reader.d.ts.map +0 -1
- package/dist/ui/containers/geometry-reader.js +0 -24
- package/dist/ui/containers/geometry-reader.js.map +0 -1
- package/dist/ui/containers/hstack.d.ts +0 -12
- package/dist/ui/containers/hstack.d.ts.map +0 -1
- package/dist/ui/containers/hstack.js +0 -28
- package/dist/ui/containers/hstack.js.map +0 -1
- package/dist/ui/containers/scroll.d.ts +0 -28
- package/dist/ui/containers/scroll.d.ts.map +0 -1
- package/dist/ui/containers/scroll.js +0 -97
- package/dist/ui/containers/scroll.js.map +0 -1
- package/dist/ui/containers/shared.d.ts +0 -12
- package/dist/ui/containers/shared.d.ts.map +0 -1
- package/dist/ui/containers/shared.js +0 -19
- package/dist/ui/containers/shared.js.map +0 -1
- package/dist/ui/containers/vstack.d.ts +0 -12
- package/dist/ui/containers/vstack.d.ts.map +0 -1
- package/dist/ui/containers/vstack.js +0 -28
- package/dist/ui/containers/vstack.js.map +0 -1
- package/dist/ui/containers/zstack.d.ts +0 -14
- package/dist/ui/containers/zstack.d.ts.map +0 -1
- package/dist/ui/containers/zstack.js +0 -36
- package/dist/ui/containers/zstack.js.map +0 -1
- package/dist/ui/core/geometry-store.d.ts +0 -22
- package/dist/ui/core/geometry-store.d.ts.map +0 -1
- package/dist/ui/core/geometry-store.js +0 -29
- package/dist/ui/core/geometry-store.js.map +0 -1
- package/dist/ui/core/geometry.d.ts +0 -34
- package/dist/ui/core/geometry.d.ts.map +0 -1
- package/dist/ui/core/geometry.js +0 -14
- package/dist/ui/core/geometry.js.map +0 -1
- package/dist/ui/core/view.d.ts +0 -25
- package/dist/ui/core/view.d.ts.map +0 -1
- package/dist/ui/core/view.js +0 -34
- package/dist/ui/core/view.js.map +0 -1
- package/dist/ui/index.d.ts +0 -44
- package/dist/ui/index.d.ts.map +0 -1
- package/dist/ui/index.js +0 -39
- package/dist/ui/index.js.map +0 -1
- package/dist/ui/inlinetext.d.ts +0 -24
- package/dist/ui/inlinetext.d.ts.map +0 -1
- package/dist/ui/inlinetext.js +0 -131
- package/dist/ui/inlinetext.js.map +0 -1
- package/dist/ui/install.d.ts +0 -22
- package/dist/ui/install.d.ts.map +0 -1
- package/dist/ui/install.js +0 -66
- package/dist/ui/install.js.map +0 -1
- package/dist/ui/markdown.d.ts +0 -40
- package/dist/ui/markdown.d.ts.map +0 -1
- package/dist/ui/markdown.js +0 -351
- package/dist/ui/markdown.js.map +0 -1
- package/dist/ui/modifiers/border.d.ts +0 -33
- package/dist/ui/modifiers/border.d.ts.map +0 -1
- package/dist/ui/modifiers/border.js +0 -82
- package/dist/ui/modifiers/border.js.map +0 -1
- package/dist/ui/modifiers/fill.d.ts +0 -14
- package/dist/ui/modifiers/fill.d.ts.map +0 -1
- package/dist/ui/modifiers/fill.js +0 -25
- package/dist/ui/modifiers/fill.js.map +0 -1
- package/dist/ui/modifiers/frame.d.ts +0 -23
- package/dist/ui/modifiers/frame.d.ts.map +0 -1
- package/dist/ui/modifiers/frame.js +0 -54
- package/dist/ui/modifiers/frame.js.map +0 -1
- package/dist/ui/modifiers/offset.d.ts +0 -15
- package/dist/ui/modifiers/offset.d.ts.map +0 -1
- package/dist/ui/modifiers/offset.js +0 -21
- package/dist/ui/modifiers/offset.js.map +0 -1
- package/dist/ui/modifiers/opacity.d.ts +0 -15
- package/dist/ui/modifiers/opacity.d.ts.map +0 -1
- package/dist/ui/modifiers/opacity.js +0 -95
- package/dist/ui/modifiers/opacity.js.map +0 -1
- package/dist/ui/modifiers/padding.d.ts +0 -20
- package/dist/ui/modifiers/padding.d.ts.map +0 -1
- package/dist/ui/modifiers/padding.js +0 -36
- package/dist/ui/modifiers/padding.js.map +0 -1
- package/dist/ui/modifiers/styled.d.ts +0 -14
- package/dist/ui/modifiers/styled.d.ts.map +0 -1
- package/dist/ui/modifiers/styled.js +0 -26
- package/dist/ui/modifiers/styled.js.map +0 -1
- package/dist/ui/primitives/rectangle.d.ts +0 -15
- package/dist/ui/primitives/rectangle.d.ts.map +0 -1
- package/dist/ui/primitives/rectangle.js +0 -23
- package/dist/ui/primitives/rectangle.js.map +0 -1
- package/dist/ui/primitives/spacer.d.ts +0 -13
- package/dist/ui/primitives/spacer.d.ts.map +0 -1
- package/dist/ui/primitives/spacer.js +0 -16
- package/dist/ui/primitives/spacer.js.map +0 -1
- package/dist/ui/primitives/text.d.ts +0 -15
- package/dist/ui/primitives/text.d.ts.map +0 -1
- package/dist/ui/primitives/text.js +0 -79
- package/dist/ui/primitives/text.js.map +0 -1
- package/dist/ui/primitives/wrapped-text.d.ts +0 -30
- package/dist/ui/primitives/wrapped-text.d.ts.map +0 -1
- package/dist/ui/primitives/wrapped-text.js +0 -117
- package/dist/ui/primitives/wrapped-text.js.map +0 -1
- package/dist/ui/shinytext.d.ts +0 -66
- package/dist/ui/shinytext.d.ts.map +0 -1
- package/dist/ui/shinytext.js +0 -99
- package/dist/ui/shinytext.js.map +0 -1
- package/dist/ui/text/layout.d.ts +0 -35
- package/dist/ui/text/layout.d.ts.map +0 -1
- package/dist/ui/text/layout.js +0 -102
- package/dist/ui/text/layout.js.map +0 -1
- package/dist/ui/textinput.d.ts +0 -140
- package/dist/ui/textinput.d.ts.map +0 -1
- package/dist/ui/textinput.js +0 -402
- package/dist/ui/textinput.js.map +0 -1
- package/dist/ui/view-constructors.d.ts +0 -72
- package/dist/ui/view-constructors.d.ts.map +0 -1
- package/dist/ui/view-constructors.js +0 -74
- package/dist/ui/view-constructors.js.map +0 -1
- package/src/anim.ts +0 -5
- package/src/layout/linearStack.ts +0 -115
- package/src/motion-value.ts +0 -335
- package/src/present/display.ts +0 -206
- package/src/present/writers/fullscreen.ts +0 -58
- package/src/present/writers/inline.ts +0 -101
- package/src/render/color-utils.ts +0 -60
- package/src/render/diff.ts +0 -95
- package/src/spring-physics.ts +0 -151
- package/src/spring.ts +0 -234
- package/src/ui/__snapshots__/wrappedtext.test.ts.snap +0 -57
- package/src/ui/containers/canvas.ts +0 -18
- package/src/ui/containers/geometry-reader.ts +0 -32
- package/src/ui/containers/hstack.ts +0 -33
- package/src/ui/containers/scroll.ts +0 -106
- package/src/ui/containers/shared.ts +0 -27
- package/src/ui/containers/vstack.ts +0 -34
- package/src/ui/containers/zstack.ts +0 -37
- package/src/ui/core/geometry-store.ts +0 -42
- package/src/ui/core/geometry.ts +0 -30
- package/src/ui/core/view.ts +0 -49
- package/src/ui/index.ts +0 -84
- package/src/ui/inlinetext.ts +0 -135
- package/src/ui/install.ts +0 -110
- package/src/ui/markdown.test.ts +0 -74
- package/src/ui/markdown.ts +0 -388
- package/src/ui/modifiers/border.ts +0 -100
- package/src/ui/modifiers/fill.ts +0 -28
- package/src/ui/modifiers/frame.ts +0 -74
- package/src/ui/modifiers/offset.ts +0 -23
- package/src/ui/modifiers/opacity.ts +0 -93
- package/src/ui/modifiers/padding.ts +0 -53
- package/src/ui/modifiers/styled.ts +0 -31
- package/src/ui/primitives/rectangle.ts +0 -25
- package/src/ui/primitives/spacer.ts +0 -18
- package/src/ui/primitives/text.ts +0 -85
- package/src/ui/primitives/wrapped-text.ts +0 -131
- package/src/ui/shinytext.ts +0 -159
- package/src/ui/text/layout.ts +0 -119
- package/src/ui/textinput.ts +0 -496
- package/src/ui/view-constructors.ts +0 -96
- 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
|
-
}
|
package/src/ui/shinytext.ts
DELETED
|
@@ -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
|
-
}
|