@trendr/core 0.2.11 → 0.4.0
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 +284 -24
- package/index.js +6 -2
- package/package.json +29 -34
- package/src/animation.js +25 -12
- package/src/ansi.js +6 -0
- package/src/buffer.js +63 -50
- package/src/button.js +2 -2
- package/src/checkbox.js +2 -2
- package/src/diff-engine.js +313 -0
- package/src/diff-view.js +238 -0
- package/src/diff.js +2 -2
- package/src/dropdown.js +147 -0
- package/src/focus.js +89 -27
- package/src/hooks.js +43 -25
- package/src/hotkey.js +51 -16
- package/src/input.js +211 -72
- package/src/layout.js +34 -13
- package/src/list.js +29 -16
- package/src/markdown.js +177 -0
- package/src/menu.js +13 -6
- package/src/menubar.js +46 -115
- package/src/miller-nav.js +36 -11
- package/src/modal.js +3 -1
- package/src/pick-list.js +16 -17
- package/src/progress.js +3 -3
- package/src/radio.js +16 -5
- package/src/renderer.js +291 -50
- package/src/scheduler.js +28 -10
- package/src/scroll-box.js +12 -11
- package/src/scrollable-text.js +3 -2
- package/src/select.js +65 -173
- package/src/selection.js +92 -0
- package/src/shimmer.js +2 -2
- package/src/signal.js +20 -3
- package/src/table.js +8 -27
- package/src/tabs.js +10 -7
- package/src/task.js +3 -3
- package/src/text-area.js +96 -41
- package/src/text-input.js +70 -28
- package/src/wrap.js +205 -84
package/src/modal.js
CHANGED
|
@@ -42,6 +42,8 @@ export function Modal({ open, onClose, title, children, width: w = 40, border =
|
|
|
42
42
|
children: content,
|
|
43
43
|
})
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
// capture routes input dispatch to this modal's subtree only, so components
|
|
46
|
+
// behind the backdrop never see keys or mouse regardless of mount order
|
|
47
|
+
registerOverlay(overlay, { backdrop: true, capture: true })
|
|
46
48
|
return null
|
|
47
49
|
}
|
package/src/pick-list.js
CHANGED
|
@@ -4,12 +4,12 @@ import { useInput, useLayout, useTheme, useCursor } from './hooks.js'
|
|
|
4
4
|
import { registerHook } from './renderer.js'
|
|
5
5
|
import { List } from './list.js'
|
|
6
6
|
|
|
7
|
-
export function PickList({ items,
|
|
8
|
-
const { accent = 'cyan' } = useTheme()
|
|
7
|
+
export function PickList({ items, onSubmit, onCancel, onChange, onCursorChange, focused = true, placeholder = 'search...', filter: filterFn, renderItem, maxVisible = 10, scrollbar = false, scrolloff = 0, itemHeight = 1, itemGap = 0, gap = 0, clearOnSubmit = false, style: userStyle, cursor: cursorProp }) {
|
|
8
|
+
const { accent = 'cyan', accentText = 'black', muted = 'gray' } = useTheme()
|
|
9
9
|
const defaults = {
|
|
10
10
|
borderColor: accent,
|
|
11
11
|
cursorBg: accent,
|
|
12
|
-
cursorTextColor:
|
|
12
|
+
cursorTextColor: accentText,
|
|
13
13
|
color: null,
|
|
14
14
|
}
|
|
15
15
|
const s = { ...defaults, ...userStyle }
|
|
@@ -55,12 +55,12 @@ export function PickList({ items, onSelect, onCancel, onChange, onCursorChange,
|
|
|
55
55
|
const { key, raw, ctrl } = event
|
|
56
56
|
const len = filtered.length
|
|
57
57
|
|
|
58
|
-
if (key === 'up' || (ctrl &&
|
|
58
|
+
if (key === 'up' || (ctrl && key === 'p')) {
|
|
59
59
|
if (len > 0) setListCursor(c => Math.max(0, c - 1))
|
|
60
60
|
event.stopPropagation()
|
|
61
61
|
return
|
|
62
62
|
}
|
|
63
|
-
if (key === 'down' || (ctrl &&
|
|
63
|
+
if (key === 'down' || (ctrl && key === 'n')) {
|
|
64
64
|
if (len > 0) setListCursor(c => Math.min(len - 1, c + 1))
|
|
65
65
|
event.stopPropagation()
|
|
66
66
|
return
|
|
@@ -73,9 +73,9 @@ export function PickList({ items, onSelect, onCancel, onChange, onCursorChange,
|
|
|
73
73
|
if (key === 'pagedown') { if (len > 0) setListCursor(c => Math.min(len - 1, c + 10)); event.stopPropagation(); return }
|
|
74
74
|
|
|
75
75
|
if (key === 'return') {
|
|
76
|
-
if (filtered.length > 0 &&
|
|
77
|
-
|
|
78
|
-
if (
|
|
76
|
+
if (filtered.length > 0 && onSubmit) {
|
|
77
|
+
onSubmit(filtered[cursor])
|
|
78
|
+
if (clearOnSubmit) updateText('', 0)
|
|
79
79
|
event.stopPropagation()
|
|
80
80
|
}
|
|
81
81
|
return
|
|
@@ -108,13 +108,12 @@ export function PickList({ items, onSelect, onCancel, onChange, onCursorChange,
|
|
|
108
108
|
if (key === 'left') { setTextCursor(Math.max(0, c - 1)); event.stopPropagation(); return }
|
|
109
109
|
if (key === 'right') { setTextCursor(Math.min(v.length, c + 1)); event.stopPropagation(); return }
|
|
110
110
|
|
|
111
|
-
if (key === 'home' || (ctrl &&
|
|
112
|
-
if (key === 'end' || (ctrl &&
|
|
111
|
+
if (key === 'home' || (ctrl && key === 'a')) { setTextCursor(0); event.stopPropagation(); return }
|
|
112
|
+
if (key === 'end' || (ctrl && key === 'e')) { setTextCursor(v.length); event.stopPropagation(); return }
|
|
113
113
|
|
|
114
|
-
if (ctrl &&
|
|
115
|
-
if (ctrl && raw === '\x0b') { updateText(v.slice(0, c), c); event.stopPropagation(); return }
|
|
114
|
+
if (ctrl && key === 'k') { updateText(v.slice(0, c), c); event.stopPropagation(); return }
|
|
116
115
|
|
|
117
|
-
if (ctrl &&
|
|
116
|
+
if (ctrl && key === 'w') {
|
|
118
117
|
const before = v.slice(0, c)
|
|
119
118
|
const after = v.slice(c)
|
|
120
119
|
const trimmed = before.replace(/\S+\s*$/, '')
|
|
@@ -137,13 +136,13 @@ export function PickList({ items, onSelect, onCancel, onChange, onCursorChange,
|
|
|
137
136
|
|
|
138
137
|
let inputEl
|
|
139
138
|
if (!v && placeholder && !focused) {
|
|
140
|
-
inputEl = jsx('text', { style: { color:
|
|
139
|
+
inputEl = jsx('text', { style: { color: muted }, children: placeholder })
|
|
141
140
|
} else if (!v && placeholder && focused) {
|
|
142
141
|
inputEl = jsxs('box', {
|
|
143
142
|
style: { flexDirection: 'row', height: 1, minHeight: 1 },
|
|
144
143
|
children: [
|
|
145
|
-
jsx('text', { style: cs ? { ...cs, color: cs.color ??
|
|
146
|
-
placeholder.length > 1 && jsx('text', { style: { color:
|
|
144
|
+
jsx('text', { style: cs ? { ...cs, color: cs.color ?? muted } : { inverse: true, color: muted }, children: placeholder[0] }),
|
|
145
|
+
placeholder.length > 1 && jsx('text', { style: { color: muted }, children: placeholder.slice(1) }),
|
|
147
146
|
],
|
|
148
147
|
})
|
|
149
148
|
} else {
|
|
@@ -183,7 +182,7 @@ export function PickList({ items, onSelect, onCancel, onChange, onCursorChange,
|
|
|
183
182
|
const defaultRenderItem = (item, { selected: isCursor, focused: isFocused }) => {
|
|
184
183
|
const label = typeof item === 'string' ? item : (item.label ?? item.name ?? String(item))
|
|
185
184
|
return jsx('box', {
|
|
186
|
-
style: { bg: isCursor ? (isFocused ? s.cursorBg :
|
|
185
|
+
style: { bg: isCursor ? (isFocused ? s.cursorBg : muted) : null },
|
|
187
186
|
children: jsx('text', {
|
|
188
187
|
style: { color: isCursor ? s.cursorTextColor : s.color },
|
|
189
188
|
children: ` ${label}`,
|
package/src/progress.js
CHANGED
|
@@ -36,14 +36,14 @@ function renderBar(variant, value, width) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
export function ProgressBar({ value = 0, variant = 'thin', width, color, label, count, percentage = true }) {
|
|
39
|
-
const { accent = 'cyan' } = useTheme()
|
|
39
|
+
const { accent = 'cyan', muted = 'gray' } = useTheme()
|
|
40
40
|
const c = color ?? accent
|
|
41
41
|
const clamped = Math.max(0, Math.min(1, value))
|
|
42
42
|
const layout = useLayout()
|
|
43
43
|
|
|
44
44
|
const labelPart = label ? `${label} ` : ''
|
|
45
45
|
const pctPart = percentage ? ` ${Math.round(clamped * 100)}%` : ''
|
|
46
|
-
const countPart = count ? ` (${count})` : ''
|
|
46
|
+
const countPart = count != null ? ` (${count})` : ''
|
|
47
47
|
const rightText = pctPart + countPart
|
|
48
48
|
const reservedWidth = labelPart.length + rightText.length
|
|
49
49
|
const barWidth = width ?? Math.max(5, (layout.width || 20) - reservedWidth)
|
|
@@ -62,7 +62,7 @@ export function ProgressBar({ value = 0, variant = 'thin', width, color, label,
|
|
|
62
62
|
if (bar.close) children.push(jsx('text', { children: bar.close }))
|
|
63
63
|
|
|
64
64
|
if (rightText) {
|
|
65
|
-
children.push(jsx('text', { style: { color:
|
|
65
|
+
children.push(jsx('text', { style: { color: muted }, children: rightText }))
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
return jsxs('box', { style: { flexDirection: 'row' }, children })
|
package/src/radio.js
CHANGED
|
@@ -1,10 +1,21 @@
|
|
|
1
1
|
import { jsx, jsxs } from '../jsx-runtime.js'
|
|
2
2
|
import { createSignal } from './signal.js'
|
|
3
3
|
import { useInput, useMouse, useLayout, useTheme } from './hooks.js'
|
|
4
|
+
import { registerHook } from './renderer.js'
|
|
4
5
|
|
|
5
|
-
export function Radio({ options, selected,
|
|
6
|
-
const { accent = 'cyan' } = useTheme()
|
|
6
|
+
export function Radio({ options = [], selected, onChange, focused = false }) {
|
|
7
|
+
const { accent = 'cyan', accentText = 'black' } = useTheme()
|
|
7
8
|
const [cursor, setCursor] = createSignal(Math.max(0, options.indexOf(selected)))
|
|
9
|
+
const prevSelected = registerHook(() => ({ value: selected }))
|
|
10
|
+
|
|
11
|
+
if (selected !== prevSelected.value) {
|
|
12
|
+
prevSelected.value = selected
|
|
13
|
+
const idx = options.indexOf(selected)
|
|
14
|
+
if (idx >= 0) setCursor(idx)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const maxIdx = Math.max(0, options.length - 1)
|
|
18
|
+
if (cursor() > maxIdx) setCursor(maxIdx)
|
|
8
19
|
|
|
9
20
|
useInput((event) => {
|
|
10
21
|
if (!focused) return
|
|
@@ -24,7 +35,7 @@ export function Radio({ options, selected, onSelect, focused = false }) {
|
|
|
24
35
|
event.stopPropagation()
|
|
25
36
|
}
|
|
26
37
|
} else if (key === 'return' || key === 'space') {
|
|
27
|
-
|
|
38
|
+
onChange?.(options[Math.min(cursor(), len - 1)])
|
|
28
39
|
event.stopPropagation()
|
|
29
40
|
}
|
|
30
41
|
})
|
|
@@ -38,7 +49,7 @@ export function Radio({ options, selected, onSelect, focused = false }) {
|
|
|
38
49
|
const idx = y - layout.y
|
|
39
50
|
if (idx >= 0 && idx < options.length) {
|
|
40
51
|
setCursor(idx)
|
|
41
|
-
|
|
52
|
+
onChange?.(options[idx])
|
|
42
53
|
event.stopPropagation()
|
|
43
54
|
}
|
|
44
55
|
})
|
|
@@ -50,7 +61,7 @@ export function Radio({ options, selected, onSelect, focused = false }) {
|
|
|
50
61
|
const isCursor = focused && i === c
|
|
51
62
|
const icon = isSelected ? '\u25cf' : '\u25cb'
|
|
52
63
|
const bg = isCursor ? accent : null
|
|
53
|
-
const color = isCursor ?
|
|
64
|
+
const color = isCursor ? accentText : null
|
|
54
65
|
|
|
55
66
|
return jsxs('box', {
|
|
56
67
|
key: option,
|