@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/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
- registerOverlay(overlay, { backdrop: true })
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, onSelect, onCancel, onChange, onCursorChange, focused = true, placeholder = 'search...', filter: filterFn, renderItem, maxVisible = 10, scrollbar = false, scrolloff = 0, itemHeight = 1, itemGap = 0, gap = 0, clearOnSelect = false, style: userStyle, cursor: cursorProp }) {
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: 'black',
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 && raw === '\x10')) {
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 && raw === '\x0e')) {
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 && onSelect) {
77
- onSelect(filtered[cursor])
78
- if (clearOnSelect) updateText('', 0)
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 && raw === '\x01')) { setTextCursor(0); event.stopPropagation(); return }
112
- if (key === 'end' || (ctrl && raw === '\x05')) { setTextCursor(v.length); event.stopPropagation(); return }
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 && raw === '\x15') { updateText(v.slice(c), 0); event.stopPropagation(); return }
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 && raw === '\x17') {
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: 'gray' }, children: placeholder })
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 ?? 'gray' } : { inverse: true, color: 'gray' }, children: placeholder[0] }),
146
- placeholder.length > 1 && jsx('text', { style: { color: 'gray' }, children: placeholder.slice(1) }),
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 : 'gray') : null },
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: 'gray' }, children: rightText }))
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, onSelect, focused = false }) {
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
- onSelect?.(options[cursor()])
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
- onSelect?.(options[idx])
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 ? 'black' : null
64
+ const color = isCursor ? accentText : null
54
65
 
55
66
  return jsxs('box', {
56
67
  key: option,