|
|
@@ -1,10 +1,10 @@
|
|
|
-import { InputRenderable, RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core"
|
|
|
+import { CliRenderEvents, InputRenderable, RGBA, ScrollBoxRenderable, TextAttributes } from "@opentui/core"
|
|
|
import { Keymap, type KeymapCommand } from "../context/keymap"
|
|
|
import { useTheme, useThemes } from "../context/theme"
|
|
|
import { entries, filter, flatMap, groupBy, pipe } from "remeda"
|
|
|
import { batch, createEffect, createMemo, createSignal, For, Show, type JSX, on, onCleanup } from "solid-js"
|
|
|
import { createStore } from "solid-js/store"
|
|
|
-import { useTerminalDimensions } from "@opentui/solid"
|
|
|
+import { useRenderer, useTerminalDimensions } from "@opentui/solid"
|
|
|
import * as fuzzysort from "fuzzysort"
|
|
|
import { isDeepEqual } from "remeda"
|
|
|
import { useDialog, type DialogContext } from "./dialog"
|
|
|
@@ -100,6 +100,7 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|
|
const mode = themes.mode
|
|
|
const config = useConfig().data
|
|
|
const scrollAcceleration = createMemo(() => getScrollAcceleration(config))
|
|
|
+ const renderer = useRenderer()
|
|
|
|
|
|
const [store, setStore] = createStore({
|
|
|
selected: 0,
|
|
|
@@ -110,7 +111,18 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|
|
const actionFocused = createMemo(() => focusedAction() !== undefined)
|
|
|
let selection: { value: T; category?: string } | undefined
|
|
|
let resetSelection = false
|
|
|
- let visibilityGeneration = 0
|
|
|
+ let pendingScroll: (() => void) | undefined
|
|
|
+
|
|
|
+ function scrollAfterLayout(center: boolean, value: T) {
|
|
|
+ if (pendingScroll) renderer.off(CliRenderEvents.FRAME, pendingScroll)
|
|
|
+ pendingScroll = () => {
|
|
|
+ pendingScroll = undefined
|
|
|
+ if (!isDeepEqual(selected()?.value, value)) return
|
|
|
+ scrollToSelection(center)
|
|
|
+ }
|
|
|
+ renderer.once(CliRenderEvents.FRAME, pendingScroll)
|
|
|
+ renderer.requestRender()
|
|
|
+ }
|
|
|
|
|
|
createEffect(
|
|
|
on(
|
|
|
@@ -264,16 +276,8 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|
|
setStore("selected", index)
|
|
|
selection = option
|
|
|
if (!moved) return
|
|
|
- const value = option.value
|
|
|
- const generation = ++visibilityGeneration
|
|
|
- requestAnimationFrame(() => {
|
|
|
- requestAnimationFrame(() => {
|
|
|
- if (generation !== visibilityGeneration) return
|
|
|
- if (!props.preserveSelection || store.filter.length > 0) return
|
|
|
- if (!isDeepEqual(selected()?.value, value)) return
|
|
|
- scrollToSelection(false)
|
|
|
- })
|
|
|
- })
|
|
|
+ if (!props.preserveSelection || store.filter.length > 0) return
|
|
|
+ scrollAfterLayout(false, option.value)
|
|
|
return
|
|
|
}
|
|
|
const next = reconcileSelection(store.selected, flat().length)
|
|
|
@@ -284,22 +288,26 @@ export function DialogSelect<T>(props: DialogSelectProps<T>) {
|
|
|
),
|
|
|
)
|
|
|
onCleanup(() => {
|
|
|
- visibilityGeneration++
|
|
|
+ if (!pendingScroll) return
|
|
|
+ renderer.off(CliRenderEvents.FRAME, pendingScroll)
|
|
|
+ pendingScroll = undefined
|
|
|
})
|
|
|
|
|
|
createEffect(
|
|
|
on([() => store.filter, () => props.current], ([filter, current]) => {
|
|
|
if (filter.length > 0) resetSelection = true
|
|
|
- setTimeout(() => {
|
|
|
- if (filter.length > 0) {
|
|
|
- moveTo(0, true, false)
|
|
|
- } else if (current && props.focusCurrent !== false) {
|
|
|
- const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current))
|
|
|
- if (currentIndex >= 0) {
|
|
|
- moveTo(currentIndex, true)
|
|
|
- }
|
|
|
- }
|
|
|
- }, 0)
|
|
|
+ if (filter.length > 0) {
|
|
|
+ const option = flat()[0]
|
|
|
+ if (!option) return
|
|
|
+ moveTo(0, true, false)
|
|
|
+ scrollAfterLayout(true, option.value)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if (!current || props.focusCurrent === false) return
|
|
|
+ const currentIndex = flat().findIndex((opt) => isDeepEqual(opt.value, current))
|
|
|
+ if (currentIndex < 0) return
|
|
|
+ moveTo(currentIndex, true)
|
|
|
+ scrollAfterLayout(true, current)
|
|
|
}),
|
|
|
)
|
|
|
|