@trendr/core 0.4.1 → 0.4.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/package.json +1 -1
- package/src/focus.js +17 -5
- package/src/menu.js +18 -2
- package/src/pick-list.js +11 -2
- package/src/text-area.js +4 -4
package/package.json
CHANGED
package/src/focus.js
CHANGED
|
@@ -40,6 +40,7 @@ export function useFocus({ initial, cycle = 'tab' } = {}) {
|
|
|
40
40
|
function sweep() {
|
|
41
41
|
const prevGen = state.gen
|
|
42
42
|
state.gen = prevGen + 1
|
|
43
|
+
state.seq = 0
|
|
43
44
|
if (prevGen === 0) return
|
|
44
45
|
|
|
45
46
|
const stale = state.items.filter(i => i.gen < prevGen)
|
|
@@ -71,21 +72,32 @@ export function useFocus({ initial, cycle = 'tab' } = {}) {
|
|
|
71
72
|
return name
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
// cycle order follows this frame's registration order, not first-ever
|
|
76
|
+
// registration: a conditionally rendered field slots in where it is
|
|
77
|
+
// declared instead of appending to the end of the tab order
|
|
74
78
|
function liveItems() {
|
|
75
|
-
return state.items.filter(i => i.gen === state.gen)
|
|
79
|
+
return state.items.filter(i => i.gen === state.gen).sort((a, b) => a.seq - b.seq)
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
function item(name) {
|
|
79
83
|
const existing = state.items.find(i => i.name === name)
|
|
80
|
-
if (existing)
|
|
81
|
-
|
|
84
|
+
if (existing) {
|
|
85
|
+
existing.gen = state.gen
|
|
86
|
+
existing.seq = state.seq++
|
|
87
|
+
} else {
|
|
88
|
+
state.items.push({ name, type: 'item', gen: state.gen, seq: state.seq++ })
|
|
89
|
+
}
|
|
82
90
|
if (state.current() == null) state.setCurrent(name)
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
function group(name, { items: subItems = [], navigate = 'both', wrap = false } = {}) {
|
|
86
94
|
const existing = state.items.find(i => i.name === name)
|
|
87
|
-
if (existing)
|
|
88
|
-
|
|
95
|
+
if (existing) {
|
|
96
|
+
existing.gen = state.gen
|
|
97
|
+
existing.seq = state.seq++
|
|
98
|
+
} else {
|
|
99
|
+
state.items.push({ name, type: 'group', gen: state.gen, seq: state.seq++ })
|
|
100
|
+
}
|
|
89
101
|
|
|
90
102
|
let g = state.groups.get(name)
|
|
91
103
|
if (!g) {
|
package/src/menu.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx } from '../jsx-runtime.js'
|
|
1
|
+
import { jsx, jsxs } from '../jsx-runtime.js'
|
|
2
2
|
import { createSignal } from './signal.js'
|
|
3
3
|
import { useInput, useTheme } from './hooks.js'
|
|
4
4
|
import { List } from './list.js'
|
|
@@ -21,6 +21,7 @@ export function Menu({
|
|
|
21
21
|
renderItem,
|
|
22
22
|
arrow = '›',
|
|
23
23
|
vimKeys = false,
|
|
24
|
+
counter = false,
|
|
24
25
|
}) {
|
|
25
26
|
const { accent = 'cyan', muted = 'gray' } = useTheme()
|
|
26
27
|
const [internal, setInternal] = createSignal(0)
|
|
@@ -77,7 +78,7 @@ export function Menu({
|
|
|
77
78
|
|
|
78
79
|
const render = renderItem ?? defaultRender
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
const list = jsx('box', {
|
|
81
82
|
style: { height, minHeight: height },
|
|
82
83
|
children: jsx(List, {
|
|
83
84
|
items,
|
|
@@ -91,4 +92,19 @@ export function Menu({
|
|
|
91
92
|
renderItem: (item, ctx) => render(item, { active: ctx.selected, index: ctx.index }),
|
|
92
93
|
}),
|
|
93
94
|
})
|
|
95
|
+
|
|
96
|
+
// counter renders only when the list overflows its viewport, so callers
|
|
97
|
+
// can enable it unconditionally without adding noise to short lists
|
|
98
|
+
if (!counter || len <= maxVisible) return list
|
|
99
|
+
|
|
100
|
+
return jsxs('box', {
|
|
101
|
+
style: { flexDirection: 'column' },
|
|
102
|
+
children: [
|
|
103
|
+
list,
|
|
104
|
+
jsx('text', {
|
|
105
|
+
style: { color: muted, dim: true },
|
|
106
|
+
children: `${(selected + 1).toLocaleString()}/${len.toLocaleString()}`,
|
|
107
|
+
}),
|
|
108
|
+
],
|
|
109
|
+
})
|
|
94
110
|
}
|
package/src/pick-list.js
CHANGED
|
@@ -4,7 +4,7 @@ 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, 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 }) {
|
|
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, counter = false, style: userStyle, cursor: cursorProp }) {
|
|
8
8
|
const { accent = 'cyan', accentText = 'black', muted = 'gray' } = useTheme()
|
|
9
9
|
const defaults = {
|
|
10
10
|
borderColor: accent,
|
|
@@ -205,8 +205,17 @@ export function PickList({ items, onSubmit, onCancel, onChange, onCursorChange,
|
|
|
205
205
|
renderItem: listRenderItem,
|
|
206
206
|
})
|
|
207
207
|
|
|
208
|
+
// counter renders only when the filtered list overflows its viewport, so
|
|
209
|
+
// callers can enable it unconditionally without adding noise to short lists
|
|
210
|
+
const counterEl = counter && filtered.length > maxVisible
|
|
211
|
+
? jsx('text', {
|
|
212
|
+
style: { color: muted, dim: true },
|
|
213
|
+
children: `${(filtered.length === 0 ? 0 : cursor + 1).toLocaleString()}/${filtered.length.toLocaleString()}`,
|
|
214
|
+
})
|
|
215
|
+
: null
|
|
216
|
+
|
|
208
217
|
return jsxs('box', {
|
|
209
218
|
style: { flexDirection: 'column', flexGrow: 1, gap },
|
|
210
|
-
children: [inputEl, list],
|
|
219
|
+
children: counterEl ? [inputEl, list, counterEl] : [inputEl, list],
|
|
211
220
|
})
|
|
212
221
|
}
|
package/src/text-area.js
CHANGED
|
@@ -124,7 +124,7 @@ function ensureVisible(cursorRow, scroll, height, totalLines) {
|
|
|
124
124
|
return scroll
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
export function TextArea({ onSubmit, onCancel, onChange, onKeyDown, placeholder, focused = true, maxHeight = 10, clearOnSubmit = true, cursor: cursorProp, value: valueProp, submitOnEnter = false }) {
|
|
127
|
+
export function TextArea({ onSubmit, onCancel, onChange, onKeyDown, placeholder, focused = true, maxHeight = 10, clearOnSubmit = true, cursor: cursorProp, value: valueProp, submitOnEnter = false, color }) {
|
|
128
128
|
const [value, setValue] = createSignal('')
|
|
129
129
|
const [cursor, setCursor] = createSignal(0)
|
|
130
130
|
if (valueProp !== undefined && valueProp !== value()) {
|
|
@@ -345,7 +345,7 @@ export function TextArea({ onSubmit, onCancel, onChange, onKeyDown, placeholder,
|
|
|
345
345
|
const hasCursor = focused && row === displayPos.row
|
|
346
346
|
|
|
347
347
|
if (!hasCursor) {
|
|
348
|
-
return jsx('text', { key: row, children: content || ' ' })
|
|
348
|
+
return jsx('text', { key: row, style: color ? { color } : {}, children: content || ' ' })
|
|
349
349
|
}
|
|
350
350
|
|
|
351
351
|
const cursorIdx = Math.max(line.start, Math.min(c, line.end))
|
|
@@ -358,9 +358,9 @@ export function TextArea({ onSubmit, onCancel, onChange, onKeyDown, placeholder,
|
|
|
358
358
|
key: row,
|
|
359
359
|
style: { flexDirection: 'row', height: 1 },
|
|
360
360
|
children: [
|
|
361
|
-
before && jsx('text', { children: before }),
|
|
361
|
+
before && jsx('text', { style: color ? { color } : {}, children: before }),
|
|
362
362
|
jsx('text', { style: cs ?? {}, children: cursorChar }),
|
|
363
|
-
after && jsx('text', { children: after }),
|
|
363
|
+
after && jsx('text', { style: color ? { color } : {}, children: after }),
|
|
364
364
|
],
|
|
365
365
|
})
|
|
366
366
|
})
|