@trendr/core 0.4.14 → 0.4.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trendr/core",
3
- "version": "0.4.14",
3
+ "version": "0.4.15",
4
4
  "description": "direct-mode TUI renderer with JSX, signals, and per-cell diffing",
5
5
  "license": "MIT",
6
6
  "author": "Jonathan Pyers",
package/src/focus.js CHANGED
@@ -79,13 +79,14 @@ export function useFocus({ initial, cycle = 'tab' } = {}) {
79
79
  return state.items.filter(i => i.gen === state.gen).sort((a, b) => a.seq - b.seq)
80
80
  }
81
81
 
82
- function item(name) {
82
+ function item(name, layout) {
83
83
  const existing = state.items.find(i => i.name === name)
84
84
  if (existing) {
85
85
  existing.gen = state.gen
86
86
  existing.seq = state.seq++
87
+ existing.layout = layout
87
88
  } else {
88
- state.items.push({ name, type: 'item', gen: state.gen, seq: state.seq++ })
89
+ state.items.push({ name, type: 'item', gen: state.gen, seq: state.seq++, layout })
89
90
  }
90
91
  if (state.current() == null) state.setCurrent(name)
91
92
  }
@@ -157,6 +158,11 @@ export function useFocus({ initial, cycle = 'tab' } = {}) {
157
158
  return state.current()
158
159
  }
159
160
 
161
+ function currentLayout() {
162
+ const name = state.current()
163
+ return state.items.find(i => i.name === name)?.layout ?? null
164
+ }
165
+
160
166
  function findTopLevel(cur, items) {
161
167
  const parent = state.nameToParent.get(cur)
162
168
  if (parent) return items.findIndex(i => i.name === parent)
@@ -220,5 +226,5 @@ export function useFocus({ initial, cycle = 'tab' } = {}) {
220
226
  }
221
227
  })
222
228
 
223
- return { item, group, is, focus, push, pop, current }
229
+ return { item, group, is, focus, push, pop, current, currentLayout }
224
230
  }
package/src/scroll-box.js CHANGED
@@ -2,7 +2,7 @@ import { jsx, jsxs } from '../jsx-runtime.js'
2
2
  import { createSignal } from './signal.js'
3
3
  import { useInput, useMouse, useLayout, useTheme, useScrollDrag } from './hooks.js'
4
4
 
5
- export function ScrollBox({ children, focused = true, scrollOffset: offsetProp, onScroll, scrollbar = false, gap = 0, thumbChar = '\u2588', trackChar = '\u2502', style: userStyle }) {
5
+ export function ScrollBox({ children, focused = true, followFocus, focusPadding = 0, scrollOffset: offsetProp, onScroll, scrollbar = false, gap = 0, thumbChar = '\u2588', trackChar = '\u2502', style: userStyle }) {
6
6
  const { accent = 'cyan', muted = 'gray' } = useTheme()
7
7
  const [offsetInternal, setOffsetInternal] = createSignal(0)
8
8
  const layout = useLayout()
@@ -22,6 +22,21 @@ export function ScrollBox({ children, focused = true, scrollOffset: offsetProp,
22
22
  const clamp = (v) => Math.max(0, Math.min(maxOffset, v))
23
23
  const clamped = clamp(offset)
24
24
 
25
+ if (followFocus) {
26
+ const target = typeof followFocus === 'function' ? followFocus() : followFocus.currentLayout?.()
27
+ if (target && visibleH > 0) {
28
+ const top = target.y - layout.y + clamped
29
+ const bottom = top + Math.max(1, target.height ?? 1)
30
+ const padding = Math.max(0, focusPadding)
31
+ const next = top < clamped + padding
32
+ ? top - padding
33
+ : bottom > clamped + visibleH - padding
34
+ ? bottom - visibleH + padding
35
+ : clamped
36
+ if (clamp(next) !== clamped) setOffset(next)
37
+ }
38
+ }
39
+
25
40
  useInput(({ key, ctrl }) => {
26
41
  if (!focused) return
27
42
  if (contentH <= visibleH) return