@trendr/core 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +420 -56
- package/index.js +9 -1
- package/package.json +23 -3
- package/src/animation.js +204 -0
- package/src/ansi.js +3 -0
- package/src/buffer.js +49 -15
- package/src/button.js +10 -1
- package/src/checkbox.js +12 -3
- package/src/diff.js +115 -12
- package/src/hooks.js +142 -2
- package/src/input.js +59 -7
- package/src/layout.js +90 -18
- package/src/list.js +142 -17
- package/src/modal.js +2 -2
- package/src/pick-list.js +204 -0
- package/src/progress.js +57 -9
- package/src/radio.js +16 -2
- package/src/renderer.js +364 -68
- package/src/scroll-box.js +105 -0
- package/src/scrollable-text.js +28 -6
- package/src/select.js +188 -68
- package/src/shimmer.js +95 -0
- package/src/signal.js +12 -0
- package/src/spinner.js +13 -4
- package/src/split-pane.js +66 -0
- package/src/table.js +68 -23
- package/src/task.js +43 -0
- package/src/text-area.js +8 -5
- package/src/text-input.js +9 -6
- package/src/toast.js +4 -6
- package/src/wrap.js +110 -11
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { mount } from './src/renderer.js'
|
|
2
2
|
export { createSignal } from './src/signal.js'
|
|
3
3
|
export { createEffect, createMemo, batch, untrack, onCleanup } from './src/signal.js'
|
|
4
|
-
export { useInput, useResize, useInterval, useLayout, useStdout, useTitle, useTheme } from './src/hooks.js'
|
|
4
|
+
export { useState, useInput, useMouse, useResize, useInterval, useTimeout, useLayout, useStdout, useRepaint, useTitle, useTheme, useCursor, useFrameStats, useAsync } from './src/hooks.js'
|
|
5
5
|
export { Box, Text, Spacer } from './src/components.js'
|
|
6
6
|
export { TextInput } from './src/text-input.js'
|
|
7
7
|
export { TextArea } from './src/text-area.js'
|
|
@@ -16,7 +16,15 @@ export { Modal } from './src/modal.js'
|
|
|
16
16
|
export { Radio } from './src/radio.js'
|
|
17
17
|
export { Button } from './src/button.js'
|
|
18
18
|
export { ScrollableText } from './src/scrollable-text.js'
|
|
19
|
+
export { PickList } from './src/pick-list.js'
|
|
20
|
+
export { ScrollBox } from './src/scroll-box.js'
|
|
21
|
+
export { SplitPane } from './src/split-pane.js'
|
|
22
|
+
export { Task } from './src/task.js'
|
|
23
|
+
export { Shimmer } from './src/shimmer.js'
|
|
19
24
|
export { useFocus } from './src/focus.js'
|
|
20
25
|
export { useHotkey } from './src/hotkey.js'
|
|
21
26
|
export { useToast } from './src/toast.js'
|
|
22
27
|
export { Fragment } from './src/element.js'
|
|
28
|
+
export { altScreen, exitAltScreen, showCursor, hideCursor, clearScreen, clearLine } from './src/ansi.js'
|
|
29
|
+
export { animated, useAnimated, spring, ease, decay } from './src/animation.js'
|
|
30
|
+
export { linear, easeInQuad, easeOutQuad, easeInOutQuad, easeInCubic, easeOutCubic, easeInOutCubic, easeOutElastic, easeOutBounce } from './src/animation.js'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trendr/core",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "direct-mode TUI renderer with JSX, signals, and per-cell diffing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -27,9 +27,29 @@
|
|
|
27
27
|
"reader": "node esbuild.config.js && node dist/reader.js",
|
|
28
28
|
"focus-demo": "node esbuild.config.js && node dist/focus-demo.js",
|
|
29
29
|
"modal-form": "node esbuild.config.js && node dist/modal-form.js",
|
|
30
|
-
"highlight": "node esbuild.config.js && node dist/highlight.js src"
|
|
30
|
+
"highlight": "node esbuild.config.js && node dist/highlight.js src",
|
|
31
|
+
"multi-row-list": "node esbuild.config.js && node dist/multi-row-list.js",
|
|
32
|
+
"plasma": "node esbuild.config.js && node dist/plasma.js",
|
|
33
|
+
"rain": "node esbuild.config.js && node dist/rain.js",
|
|
34
|
+
"animation": "node esbuild.config.js && node dist/animation.js",
|
|
35
|
+
"texture": "node esbuild.config.js && node dist/texture.js",
|
|
36
|
+
"absolute": "node esbuild.config.js && node dist/absolute.js",
|
|
37
|
+
"scroll-box": "node esbuild.config.js && node dist/scroll-box.js",
|
|
38
|
+
"split-pane": "node esbuild.config.js && node dist/split-pane.js",
|
|
39
|
+
"custom-table": "node esbuild.config.js && node dist/custom-table.js",
|
|
40
|
+
"async": "node esbuild.config.js && node dist/async.js",
|
|
41
|
+
"timeout": "node esbuild.config.js && node dist/timeout.js",
|
|
42
|
+
"task": "node esbuild.config.js && node dist/task.js",
|
|
43
|
+
"progress": "node esbuild.config.js && node dist/progress.js",
|
|
44
|
+
"shimmer": "node esbuild.config.js && node dist/shimmer.js",
|
|
45
|
+
"spinner": "node esbuild.config.js && node dist/spinner.js",
|
|
46
|
+
"pick-list": "node esbuild.config.js && node dist/pick-list.js",
|
|
47
|
+
"table-demo": "node esbuild.config.js && node dist/table-demo.js",
|
|
48
|
+
"demo": "node esbuild.config.js && node demo/server.js",
|
|
49
|
+
"demo:build": "node esbuild.config.js"
|
|
31
50
|
},
|
|
32
51
|
"devDependencies": {
|
|
33
|
-
"esbuild": "^0.25.0"
|
|
52
|
+
"esbuild": "^0.25.0",
|
|
53
|
+
"ssh2": "^1.16.0"
|
|
34
54
|
}
|
|
35
55
|
}
|
package/src/animation.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { createSignalRaw, batch } from './signal.js'
|
|
2
|
+
import { registerHook } from './renderer.js'
|
|
3
|
+
import { onCleanup } from './signal.js'
|
|
4
|
+
|
|
5
|
+
const active = new Set()
|
|
6
|
+
let loopTimer = null
|
|
7
|
+
|
|
8
|
+
function startLoop() {
|
|
9
|
+
if (loopTimer) return
|
|
10
|
+
loopTimer = setInterval(tickAll, 1000 / 60)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function stopLoop() {
|
|
14
|
+
if (loopTimer) {
|
|
15
|
+
clearInterval(loopTimer)
|
|
16
|
+
loopTimer = null
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function tickAll() {
|
|
21
|
+
if (active.size === 0) {
|
|
22
|
+
stopLoop()
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const dt = 1 / 60
|
|
27
|
+
|
|
28
|
+
batch(() => {
|
|
29
|
+
for (const anim of active) {
|
|
30
|
+
const result = anim.interpolator(anim.current, anim.target, anim.velocity, dt)
|
|
31
|
+
anim.current = result.value
|
|
32
|
+
anim.velocity = result.velocity
|
|
33
|
+
anim.setter(result.value)
|
|
34
|
+
if (anim.onTick) anim.onTick(result.value)
|
|
35
|
+
|
|
36
|
+
if (result.done) {
|
|
37
|
+
anim.current = anim.target
|
|
38
|
+
anim.setter(anim.target)
|
|
39
|
+
if (anim.onTick) anim.onTick(anim.target)
|
|
40
|
+
active.delete(anim)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
if (active.size === 0) stopLoop()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function animated(initial, interpolator) {
|
|
49
|
+
const [get, set] = createSignalRaw(initial)
|
|
50
|
+
|
|
51
|
+
const anim = {
|
|
52
|
+
current: initial,
|
|
53
|
+
target: initial,
|
|
54
|
+
velocity: 0,
|
|
55
|
+
interpolator,
|
|
56
|
+
setter: set,
|
|
57
|
+
onTick: null,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function setTarget(target) {
|
|
61
|
+
if (target === anim.target && !active.has(anim)) return
|
|
62
|
+
anim.target = target
|
|
63
|
+
active.add(anim)
|
|
64
|
+
startLoop()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function getValue() {
|
|
68
|
+
return get()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
getValue.set = setTarget
|
|
72
|
+
getValue.get = get
|
|
73
|
+
getValue.snap = (v) => {
|
|
74
|
+
anim.target = v
|
|
75
|
+
anim.current = v
|
|
76
|
+
anim.velocity = 0
|
|
77
|
+
active.delete(anim)
|
|
78
|
+
set(v)
|
|
79
|
+
}
|
|
80
|
+
getValue.setInterpolator = (interp) => {
|
|
81
|
+
anim.interpolator = interp
|
|
82
|
+
anim.velocity = 0
|
|
83
|
+
}
|
|
84
|
+
getValue.onTick = (fn) => { anim.onTick = fn }
|
|
85
|
+
getValue._anim = anim
|
|
86
|
+
|
|
87
|
+
return getValue
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function useAnimated(initial, interpolator) {
|
|
91
|
+
return registerHook(() => {
|
|
92
|
+
const a = animated(initial, interpolator)
|
|
93
|
+
onCleanup(() => {
|
|
94
|
+
active.delete(a._anim)
|
|
95
|
+
})
|
|
96
|
+
return a
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// -- interpolators --
|
|
101
|
+
|
|
102
|
+
export function spring({ frequency = 2, damping = 0.3 } = {}) {
|
|
103
|
+
const omega = frequency * 2 * Math.PI
|
|
104
|
+
const zeta = damping
|
|
105
|
+
|
|
106
|
+
return function stepSpring(current, target, velocity, dt) {
|
|
107
|
+
const x0 = current - target
|
|
108
|
+
const v0 = velocity
|
|
109
|
+
|
|
110
|
+
let newX, newV
|
|
111
|
+
|
|
112
|
+
if (zeta > 1) {
|
|
113
|
+
// overdamped
|
|
114
|
+
const za = -omega * (zeta + Math.sqrt(zeta * zeta - 1))
|
|
115
|
+
const zb = -omega * (zeta - Math.sqrt(zeta * zeta - 1))
|
|
116
|
+
const expA = Math.exp(za * dt)
|
|
117
|
+
const expB = Math.exp(zb * dt)
|
|
118
|
+
const c2 = (v0 - x0 * za) / (zb - za)
|
|
119
|
+
const c1 = x0 - c2
|
|
120
|
+
newX = c1 * expA + c2 * expB
|
|
121
|
+
newV = c1 * za * expA + c2 * zb * expB
|
|
122
|
+
} else if (zeta === 1) {
|
|
123
|
+
// critically damped
|
|
124
|
+
const expW = Math.exp(-omega * dt)
|
|
125
|
+
newX = (x0 + (v0 + omega * x0) * dt) * expW
|
|
126
|
+
newV = (v0 - (v0 + omega * x0) * omega * dt) * expW
|
|
127
|
+
} else {
|
|
128
|
+
// underdamped
|
|
129
|
+
const alpha = omega * Math.sqrt(1 - zeta * zeta)
|
|
130
|
+
const expW = Math.exp(-zeta * omega * dt)
|
|
131
|
+
const cosA = Math.cos(alpha * dt)
|
|
132
|
+
const sinA = Math.sin(alpha * dt)
|
|
133
|
+
newX = expW * (x0 * cosA + ((v0 + zeta * omega * x0) / alpha) * sinA)
|
|
134
|
+
newV = expW * (v0 * cosA - ((v0 * zeta * omega + omega * omega * x0) / alpha) * sinA)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const done = Math.abs(newX) < 0.01 && Math.abs(newV) < 0.01
|
|
138
|
+
|
|
139
|
+
return { value: target + newX, velocity: newV, done }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function ease(durationMs, easingFn = easeOutCubic) {
|
|
144
|
+
let elapsed = 0
|
|
145
|
+
let startValue = null
|
|
146
|
+
|
|
147
|
+
return function stepEase(current, target, velocity, dt) {
|
|
148
|
+
if (startValue === null) startValue = current
|
|
149
|
+
|
|
150
|
+
elapsed += dt * 1000
|
|
151
|
+
const t = Math.min(1, elapsed / durationMs)
|
|
152
|
+
const eased = easingFn(t)
|
|
153
|
+
const value = startValue + (target - startValue) * eased
|
|
154
|
+
|
|
155
|
+
if (t >= 1) {
|
|
156
|
+
startValue = null
|
|
157
|
+
elapsed = 0
|
|
158
|
+
return { value: target, velocity: 0, done: true }
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return { value, velocity: 0, done: false }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function decay({ deceleration = 0.998 } = {}) {
|
|
166
|
+
return function stepDecay(current, target, velocity, dt) {
|
|
167
|
+
const newVelocity = velocity * Math.pow(deceleration, dt * 1000)
|
|
168
|
+
const newValue = current + newVelocity * dt
|
|
169
|
+
|
|
170
|
+
if (Math.abs(newVelocity) < 0.1) {
|
|
171
|
+
return { value: newValue, velocity: 0, done: true }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return { value: newValue, velocity: newVelocity, done: false }
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// -- easing functions --
|
|
179
|
+
|
|
180
|
+
export function linear(t) { return t }
|
|
181
|
+
export function easeInQuad(t) { return t * t }
|
|
182
|
+
export function easeOutQuad(t) { return t * (2 - t) }
|
|
183
|
+
export function easeInOutQuad(t) { return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t }
|
|
184
|
+
export function easeInCubic(t) { return t * t * t }
|
|
185
|
+
export function easeOutCubic(t) { return (--t) * t * t + 1 }
|
|
186
|
+
export function easeInOutCubic(t) { return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1 }
|
|
187
|
+
export function easeOutElastic({ amplitude = 1, period = 0.3 } = {}) {
|
|
188
|
+
const a = Math.max(1, amplitude)
|
|
189
|
+
const s = period / (2 * Math.PI) * Math.asin(1 / a)
|
|
190
|
+
return function (t) {
|
|
191
|
+
if (t === 0 || t === 1) return t
|
|
192
|
+
return a * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / period) + 1
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export function easeOutBounce({ bounciness = 7.5625, threshold = 2.75 } = {}) {
|
|
197
|
+
return function (t) {
|
|
198
|
+
const inv = 1 / threshold
|
|
199
|
+
if (t < inv) return bounciness * t * t
|
|
200
|
+
if (t < 2 * inv) return bounciness * (t -= 1.5 / threshold) * t + 0.75
|
|
201
|
+
if (t < 2.5 / threshold) return bounciness * (t -= 2.25 / threshold) * t + 0.9375
|
|
202
|
+
return bounciness * (t -= 2.625 / threshold) * t + 0.984375
|
|
203
|
+
}
|
|
204
|
+
}
|
package/src/ansi.js
CHANGED
|
@@ -16,6 +16,9 @@ export const sgrReset = `${ESC}0m`
|
|
|
16
16
|
|
|
17
17
|
export const setTitle = (title) => `\x1b]2;${title}\x07`
|
|
18
18
|
|
|
19
|
+
export const enableMouse = `${ESC}?1002h${ESC}?1006h`
|
|
20
|
+
export const disableMouse = `${ESC}?1002l${ESC}?1006l`
|
|
21
|
+
|
|
19
22
|
export const BOLD = 1
|
|
20
23
|
export const DIM = 2
|
|
21
24
|
export const ITALIC = 4
|
package/src/buffer.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { parseSgr } from './ansi.js'
|
|
2
|
+
import { charWidth } from './wrap.js'
|
|
2
3
|
|
|
3
4
|
const EMPTY = { ch: ' ', fg: null, bg: null, attrs: 0 }
|
|
4
5
|
|
|
@@ -31,17 +32,29 @@ export function writeText(buf, x, y, text, fg, bg, attrs, maxWidth) {
|
|
|
31
32
|
const max = maxWidth ?? (buf.width - x)
|
|
32
33
|
|
|
33
34
|
if (text.indexOf('\x1b') === -1) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
35
|
+
let col = 0
|
|
36
|
+
let i = 0
|
|
37
|
+
while (i < text.length && col < max) {
|
|
38
|
+
const code = text.codePointAt(i)
|
|
39
|
+
const len = code > 0xffff ? 2 : 1
|
|
40
|
+
const w = charWidth(code)
|
|
41
|
+
const cx = x + col
|
|
42
|
+
if (cx >= 0 && cx < buf.width) {
|
|
43
|
+
const ch = len === 1 ? text[i] : text.slice(i, i + len)
|
|
44
|
+
const prev = buf.cells[y * buf.width + cx]
|
|
45
|
+
const transparent = ch === ' ' && !bg && prev.ch !== ' '
|
|
46
|
+
buf.cells[y * buf.width + cx] = {
|
|
47
|
+
ch: transparent ? prev.ch : ch,
|
|
48
|
+
fg: transparent ? prev.fg : (fg ?? prev.fg),
|
|
49
|
+
bg: bg ?? prev.bg,
|
|
50
|
+
attrs: transparent ? prev.attrs : (attrs || prev.attrs),
|
|
51
|
+
}
|
|
52
|
+
if (w === 2 && cx + 1 < buf.width) {
|
|
53
|
+
buf.cells[y * buf.width + cx + 1] = { ch: '', fg: fg ?? null, bg: bg ?? null, attrs: attrs ?? 0 }
|
|
54
|
+
}
|
|
44
55
|
}
|
|
56
|
+
col += w
|
|
57
|
+
i += len
|
|
45
58
|
}
|
|
46
59
|
return
|
|
47
60
|
}
|
|
@@ -60,18 +73,26 @@ export function writeText(buf, x, y, text, fg, bg, attrs, maxWidth) {
|
|
|
60
73
|
}
|
|
61
74
|
}
|
|
62
75
|
|
|
76
|
+
const code = text.codePointAt(i)
|
|
77
|
+
const len = code > 0xffff ? 2 : 1
|
|
78
|
+
const w = charWidth(code)
|
|
63
79
|
const cx = x + col
|
|
64
80
|
if (cx >= 0 && cx < buf.width) {
|
|
81
|
+
const ch = len === 1 ? text[i] : text.slice(i, i + len)
|
|
65
82
|
const prev = buf.cells[y * buf.width + cx]
|
|
83
|
+
const transparent = ch === ' ' && !bg && prev.ch !== ' '
|
|
66
84
|
buf.cells[y * buf.width + cx] = {
|
|
67
|
-
ch:
|
|
68
|
-
fg: ansi.fg ?? fg ?? prev.fg,
|
|
85
|
+
ch: transparent ? prev.ch : ch,
|
|
86
|
+
fg: transparent ? prev.fg : (ansi.fg ?? fg ?? prev.fg),
|
|
69
87
|
bg: ansi.bg ?? bg ?? prev.bg,
|
|
70
|
-
attrs: ansi.attrs || attrs || prev.attrs,
|
|
88
|
+
attrs: transparent ? prev.attrs : (ansi.attrs || attrs || prev.attrs),
|
|
89
|
+
}
|
|
90
|
+
if (w === 2 && cx + 1 < buf.width) {
|
|
91
|
+
buf.cells[y * buf.width + cx + 1] = { ch: '', fg: ansi.fg ?? fg ?? null, bg: ansi.bg ?? bg ?? null, attrs: ansi.attrs || attrs || 0 }
|
|
71
92
|
}
|
|
72
93
|
}
|
|
73
|
-
col
|
|
74
|
-
i
|
|
94
|
+
col += w
|
|
95
|
+
i += len
|
|
75
96
|
}
|
|
76
97
|
}
|
|
77
98
|
|
|
@@ -99,3 +120,16 @@ export function copyBuffer(src, dst) {
|
|
|
99
120
|
const len = Math.min(src.cells.length, dst.cells.length)
|
|
100
121
|
for (let i = 0; i < len; i++) dst.cells[i] = src.cells[i]
|
|
101
122
|
}
|
|
123
|
+
|
|
124
|
+
export function blitRect(src, dst, x, y, w, h) {
|
|
125
|
+
const x1 = Math.max(x, 0)
|
|
126
|
+
const y1 = Math.max(y, 0)
|
|
127
|
+
const x2 = Math.min(x + w, src.width, dst.width)
|
|
128
|
+
const y2 = Math.min(y + h, src.height, dst.height)
|
|
129
|
+
for (let row = y1; row < y2; row++) {
|
|
130
|
+
const base = row * dst.width
|
|
131
|
+
for (let col = x1; col < x2; col++) {
|
|
132
|
+
dst.cells[base + col] = src.cells[base + col]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
package/src/button.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx } from '../jsx-runtime.js'
|
|
2
|
-
import { useInput, useTheme } from './hooks.js'
|
|
2
|
+
import { useInput, useMouse, useLayout, useTheme } from './hooks.js'
|
|
3
3
|
|
|
4
4
|
export function Button({ label, onPress, focused = false, variant }) {
|
|
5
5
|
const { accent = 'cyan' } = useTheme()
|
|
6
|
+
const layout = useLayout()
|
|
6
7
|
|
|
7
8
|
useInput((event) => {
|
|
8
9
|
if (!focused) return
|
|
@@ -12,6 +13,14 @@ export function Button({ label, onPress, focused = false, variant }) {
|
|
|
12
13
|
}
|
|
13
14
|
})
|
|
14
15
|
|
|
16
|
+
useMouse((event) => {
|
|
17
|
+
if (event.action !== 'press' || event.button !== 'left') return
|
|
18
|
+
const { x, y } = event
|
|
19
|
+
if (x < layout.x || x >= layout.x + layout.width || y < layout.y || y >= layout.y + layout.height) return
|
|
20
|
+
onPress?.()
|
|
21
|
+
event.stopPropagation()
|
|
22
|
+
})
|
|
23
|
+
|
|
15
24
|
const dim = variant === 'dim'
|
|
16
25
|
|
|
17
26
|
return jsx('text', {
|
package/src/checkbox.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx, jsxs } from '../jsx-runtime.js'
|
|
2
|
-
import { useInput, useTheme } from './hooks.js'
|
|
2
|
+
import { useInput, useMouse, useLayout, useTheme } from './hooks.js'
|
|
3
3
|
|
|
4
|
-
export function Checkbox({ checked = false, label, onChange, focused = false }) {
|
|
4
|
+
export function Checkbox({ checked = false, label, onChange, focused = false, checkedIcon = '[x]', uncheckedIcon = '[ ]' }) {
|
|
5
5
|
const { accent = 'cyan' } = useTheme()
|
|
6
|
+
const layout = useLayout()
|
|
6
7
|
|
|
7
8
|
useInput((event) => {
|
|
8
9
|
if (!focused) return
|
|
@@ -12,7 +13,15 @@ export function Checkbox({ checked = false, label, onChange, focused = false })
|
|
|
12
13
|
}
|
|
13
14
|
})
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
useMouse((event) => {
|
|
17
|
+
if (event.action !== 'press' || event.button !== 'left') return
|
|
18
|
+
const { x, y } = event
|
|
19
|
+
if (x < layout.x || x >= layout.x + layout.width || y < layout.y || y >= layout.y + layout.height) return
|
|
20
|
+
onChange?.(!checked)
|
|
21
|
+
event.stopPropagation()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const icon = checked ? checkedIcon : uncheckedIcon
|
|
16
25
|
const bg = focused ? accent : null
|
|
17
26
|
const color = focused ? 'black' : null
|
|
18
27
|
|
package/src/diff.js
CHANGED
|
@@ -1,4 +1,104 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BOLD, DIM, ITALIC, UNDERLINE, INVERSE, STRIKETHROUGH } from './ansi.js'
|
|
2
|
+
|
|
3
|
+
const NAMED_COLORS = {
|
|
4
|
+
black: 0, red: 1, green: 2, yellow: 3,
|
|
5
|
+
blue: 4, magenta: 5, cyan: 6, white: 7,
|
|
6
|
+
gray: 8, grey: 8,
|
|
7
|
+
brightRed: 9, brightGreen: 10, brightYellow: 11,
|
|
8
|
+
brightBlue: 12, brightMagenta: 13, brightCyan: 14, brightWhite: 15,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const ATTR_MASKS = [BOLD, DIM, ITALIC, UNDERLINE, INVERSE, STRIKETHROUGH]
|
|
12
|
+
const ATTR_SGRCODES = [49, 50, 51, 52, 55, 57] // '1','2','3','4','7','9' as ascii
|
|
13
|
+
|
|
14
|
+
const bufA = Buffer.allocUnsafe(2 * 1024 * 1024)
|
|
15
|
+
const bufB = Buffer.allocUnsafe(2 * 1024 * 1024)
|
|
16
|
+
let buf = bufA
|
|
17
|
+
let pos = 0
|
|
18
|
+
|
|
19
|
+
function ensure(n) {
|
|
20
|
+
if (pos + n > buf.length) throw new Error('diff output buffer overflow')
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function writeNum(n) {
|
|
24
|
+
if (n >= 1000) { buf[pos++] = 48 + (n / 1000 | 0); n %= 1000; buf[pos++] = 48 + (n / 100 | 0); n %= 100; buf[pos++] = 48 + (n / 10 | 0); buf[pos++] = 48 + (n % 10) }
|
|
25
|
+
else if (n >= 100) { buf[pos++] = 48 + (n / 100 | 0); n %= 100; buf[pos++] = 48 + (n / 10 | 0); buf[pos++] = 48 + (n % 10) }
|
|
26
|
+
else if (n >= 10) { buf[pos++] = 48 + (n / 10 | 0); buf[pos++] = 48 + (n % 10) }
|
|
27
|
+
else buf[pos++] = 48 + n
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function writeMoveTo(row, col) {
|
|
31
|
+
ensure(12)
|
|
32
|
+
buf[pos++] = 0x1b
|
|
33
|
+
buf[pos++] = 0x5b
|
|
34
|
+
writeNum(row)
|
|
35
|
+
buf[pos++] = 0x3b
|
|
36
|
+
writeNum(col)
|
|
37
|
+
buf[pos++] = 0x48
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function writeSgr(fg, bg, attrs) {
|
|
41
|
+
ensure(52)
|
|
42
|
+
buf[pos++] = 0x1b
|
|
43
|
+
buf[pos++] = 0x5b
|
|
44
|
+
buf[pos++] = 48 // '0' - reset
|
|
45
|
+
|
|
46
|
+
for (let i = 0; i < 6; i++) {
|
|
47
|
+
if (attrs & ATTR_MASKS[i]) {
|
|
48
|
+
buf[pos++] = 0x3b
|
|
49
|
+
buf[pos++] = ATTR_SGRCODES[i]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (fg != null) writeColor(fg, 38)
|
|
54
|
+
if (bg != null) writeColor(bg, 48)
|
|
55
|
+
|
|
56
|
+
buf[pos++] = 0x6d
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function writeColor(color, offset) {
|
|
60
|
+
if (typeof color === 'number' || color in NAMED_COLORS) {
|
|
61
|
+
const idx = typeof color === 'number' ? color : NAMED_COLORS[color]
|
|
62
|
+
buf[pos++] = 0x3b
|
|
63
|
+
writeNum(offset)
|
|
64
|
+
buf[pos++] = 0x3b
|
|
65
|
+
buf[pos++] = 53 // '5'
|
|
66
|
+
buf[pos++] = 0x3b
|
|
67
|
+
writeNum(idx)
|
|
68
|
+
} else if (color.charCodeAt(0) === 35 && color.length === 7) {
|
|
69
|
+
const r = parseInt(color.slice(1, 3), 16)
|
|
70
|
+
const g = parseInt(color.slice(3, 5), 16)
|
|
71
|
+
const b = parseInt(color.slice(5, 7), 16)
|
|
72
|
+
buf[pos++] = 0x3b
|
|
73
|
+
writeNum(offset)
|
|
74
|
+
buf[pos++] = 0x3b
|
|
75
|
+
buf[pos++] = 50 // '2'
|
|
76
|
+
buf[pos++] = 0x3b
|
|
77
|
+
writeNum(r)
|
|
78
|
+
buf[pos++] = 0x3b
|
|
79
|
+
writeNum(g)
|
|
80
|
+
buf[pos++] = 0x3b
|
|
81
|
+
writeNum(b)
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function writeReset() {
|
|
86
|
+
ensure(4)
|
|
87
|
+
buf[pos++] = 0x1b
|
|
88
|
+
buf[pos++] = 0x5b
|
|
89
|
+
buf[pos++] = 48
|
|
90
|
+
buf[pos++] = 0x6d
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function writeChar(ch) {
|
|
94
|
+
ensure(4)
|
|
95
|
+
const code = ch.charCodeAt(0)
|
|
96
|
+
if (code < 0x80) {
|
|
97
|
+
buf[pos++] = code
|
|
98
|
+
} else {
|
|
99
|
+
pos += buf.write(ch, pos)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
2
102
|
|
|
3
103
|
function cellEq(a, b) {
|
|
4
104
|
return a === b || (a.ch === b.ch && a.fg === b.fg && a.bg === b.bg && a.attrs === b.attrs)
|
|
@@ -7,7 +107,8 @@ function cellEq(a, b) {
|
|
|
7
107
|
export function diff(prev, curr) {
|
|
8
108
|
const w = curr.width
|
|
9
109
|
const h = curr.height
|
|
10
|
-
|
|
110
|
+
pos = 0
|
|
111
|
+
let changed = 0
|
|
11
112
|
|
|
12
113
|
let lastFg = undefined
|
|
13
114
|
let lastBg = undefined
|
|
@@ -17,35 +118,37 @@ export function diff(prev, curr) {
|
|
|
17
118
|
let x = 0
|
|
18
119
|
while (x < w) {
|
|
19
120
|
const idx = y * w + x
|
|
20
|
-
if (cellEq(prev.cells[idx], curr.cells[idx])) {
|
|
121
|
+
if (curr.cells[idx].ch === '' || cellEq(prev.cells[idx], curr.cells[idx])) {
|
|
21
122
|
x++
|
|
22
123
|
continue
|
|
23
124
|
}
|
|
24
125
|
|
|
25
|
-
|
|
126
|
+
writeMoveTo(y + 1, x + 1)
|
|
26
127
|
|
|
27
128
|
while (x < w) {
|
|
28
129
|
const i = y * w + x
|
|
29
|
-
if (cellEq(prev.cells[i], curr.cells[i])) break
|
|
30
|
-
|
|
31
130
|
const c = curr.cells[i]
|
|
131
|
+
if (c.ch === '') { x++; continue }
|
|
132
|
+
if (cellEq(prev.cells[i], c)) break
|
|
133
|
+
|
|
134
|
+
changed++
|
|
32
135
|
|
|
33
136
|
if (c.fg !== lastFg || c.bg !== lastBg || c.attrs !== lastAttrs) {
|
|
34
|
-
|
|
137
|
+
writeSgr(c.fg, c.bg, c.attrs)
|
|
35
138
|
lastFg = c.fg
|
|
36
139
|
lastBg = c.bg
|
|
37
140
|
lastAttrs = c.attrs
|
|
38
141
|
}
|
|
39
142
|
|
|
40
|
-
|
|
143
|
+
writeChar(c.ch)
|
|
41
144
|
x++
|
|
42
145
|
}
|
|
43
146
|
}
|
|
44
147
|
}
|
|
45
148
|
|
|
46
|
-
if (
|
|
47
|
-
parts.push(sgrReset)
|
|
48
|
-
}
|
|
149
|
+
if (pos > 0) writeReset()
|
|
49
150
|
|
|
50
|
-
|
|
151
|
+
const output = pos > 0 ? buf.subarray(0, pos) : ''
|
|
152
|
+
buf = buf === bufA ? bufB : bufA
|
|
153
|
+
return { output, changed }
|
|
51
154
|
}
|