list.tsx 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import { ComponentProps, createEffect, createSignal, type JSX } from "solid-js"
  2. import { VirtualizerHandle, VList } from "virtua/solid"
  3. import { createList } from "solid-list"
  4. import { createStore } from "solid-js/store"
  5. export interface ListProps<T> {
  6. data: T[]
  7. children: (x: T) => JSX.Element
  8. key: (x: T) => string
  9. current?: T
  10. onSelect?: (value: T | undefined) => void
  11. onHover?: (value: T | undefined) => void
  12. class?: ComponentProps<"div">["class"]
  13. }
  14. export function List<T>(props: ListProps<T>) {
  15. const [virtualizer, setVirtualizer] = createSignal<VirtualizerHandle | undefined>(undefined)
  16. const [store, setStore] = createStore({
  17. mouseActive: false,
  18. })
  19. const list = createList({
  20. items: () => props.data.map(props.key),
  21. initialActive: props.current ? props.key(props.current) : undefined,
  22. loop: true,
  23. })
  24. createEffect(() => {
  25. if (props.current) list.setActive(props.key(props.current))
  26. })
  27. // const resetSelection = () => {
  28. // if (props.data.length === 0) return
  29. // list.setActive(props.key(props.data[0]))
  30. // }
  31. const handleSelect = (item: T) => {
  32. props.onSelect?.(item)
  33. list.setActive(props.key(item))
  34. }
  35. const handleKey = (e: KeyboardEvent) => {
  36. setStore("mouseActive", false)
  37. if (e.key === "Enter") {
  38. e.preventDefault()
  39. const selected = props.data.find((x) => props.key(x) === list.active())
  40. if (selected) handleSelect(selected)
  41. } else {
  42. list.onKeyDown(e)
  43. }
  44. }
  45. createEffect(() => {
  46. if (store.mouseActive || props.data.length === 0) return
  47. const index = props.data.findIndex((x) => props.key(x) === list.active())
  48. props.onHover?.(props.data[index])
  49. if (index === 0) {
  50. virtualizer()?.scrollTo(0)
  51. return
  52. }
  53. // virtualizer()?.scrollTo(list.active())
  54. // const element = virtualizer()?.querySelector(`[data-key="${list.active()}"]`)
  55. // element?.scrollIntoView({ block: "nearest", behavior: "smooth" })
  56. })
  57. return (
  58. <VList data-component="list" ref={setVirtualizer} data={props.data} onKeyDown={handleKey} class={props.class}>
  59. {(item) => (
  60. <button
  61. data-slot="item"
  62. data-key={props.key(item)}
  63. data-active={props.key(item) === list.active()}
  64. onClick={() => handleSelect(item)}
  65. onMouseMove={() => {
  66. // e.currentTarget.focus()
  67. setStore("mouseActive", true)
  68. // list.setActive(props.key(item))
  69. }}
  70. >
  71. {props.children(item)}
  72. </button>
  73. )}
  74. </VList>
  75. )
  76. }