Fix suggestions component

This commit is contained in:
Jon Staab
2025-02-04 19:37:59 -08:00
parent d4df23545d
commit 506276f594
6 changed files with 57 additions and 138 deletions
+1 -3
View File
@@ -28,9 +28,7 @@
</ModalHeader> </ModalHeader>
<Field> <Field>
{#snippet input()} {#snippet input()}
<div> <ProfileMultiSelect autofocus bind:value={pubkeys} />
<ProfileMultiSelect autofocus bind:value={pubkeys} />
</div>
{/snippet} {/snippet}
</Field> </Field>
<ModalFooter> <ModalFooter>
+10 -9
View File
@@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import {derived} from "svelte/store" import {writable} from "svelte/store"
import {type Instance} from "tippy.js" import {type Instance} from "tippy.js"
import {append, remove, uniq} from "@welshman/lib" import {append, remove, uniq} from "@welshman/lib"
import {profileSearch} from "@welshman/app" import {profileSearch} from "@welshman/app"
@@ -19,15 +19,12 @@
let {value = $bindable(), autofocus = false}: Props = $props() let {value = $bindable(), autofocus = false}: Props = $props()
let term = $state("") const term = writable("")
let input: Element | undefined = $state()
let popover: Instance | undefined = $state()
let instance: any = $state()
const search = derived(profileSearch, $profileSearch => $profileSearch.searchValues) const search = (term: string) => $profileSearch.searchValues(term)
const selectPubkey = (pubkey: string) => { const selectPubkey = (pubkey: string) => {
term = "" term.set("")
popover?.hide() popover?.hide()
value = uniq(append(pubkey, value)) value = uniq(append(pubkey, value))
} }
@@ -42,8 +39,12 @@
} }
} }
let input: Element | undefined = $state()
let popover: Instance | undefined = $state()
let instance: any = $state()
$effect(() => { $effect(() => {
if (term) { if ($term) {
popover?.show() popover?.show()
} else { } else {
popover?.hide() popover?.hide()
@@ -73,7 +74,7 @@
class="grow" class="grow"
type="text" type="text"
placeholder="Search for profiles..." placeholder="Search for profiles..."
bind:value={term} bind:value={$term}
onkeydown={onKeyDown} /> onkeydown={onKeyDown} />
</label> </label>
<Tippy <Tippy
+2 -1
View File
@@ -10,8 +10,9 @@
import WotScore from "@lib/components/WotScore.svelte" import WotScore from "@lib/components/WotScore.svelte"
import ProfileCircle from "@app/components/ProfileCircle.svelte" import ProfileCircle from "@app/components/ProfileCircle.svelte"
const {pubkey} = $props() const {value} = $props()
const pubkey = value
const profileDisplay = deriveProfileDisplay(pubkey) const profileDisplay = deriveProfileDisplay(pubkey)
const handle = deriveHandleForPubkey(pubkey) const handle = deriveHandleForPubkey(pubkey)
const score = deriveUserWotScore(pubkey) const score = deriveUserWotScore(pubkey)
+2 -2
View File
@@ -85,10 +85,10 @@ export const makeEditor = ({
editor: (this as any).editor, editor: (this as any).editor,
search: (term: string) => get(profileSearch).searchValues(term), search: (term: string) => get(profileSearch).searchValues(term),
getRelays: (pubkey: string) => ctx.app.router.FromPubkeys([pubkey]).getUrls(), getRelays: (pubkey: string) => ctx.app.router.FromPubkeys([pubkey]).getUrls(),
createSuggestion: (pubkey: string) => { createSuggestion: (value: string) => {
const target = document.createElement("div") const target = document.createElement("div")
mount(ProfileSuggestion, {target, props: {pubkey}}) mount(ProfileSuggestion, {target, props: {value}})
return target return target
}, },
-83
View File
@@ -1,83 +0,0 @@
<script lang="ts">
import type {Snippet} from "svelte"
import {readable} from "svelte/store"
import {type Instance} from "tippy.js"
import {identity} from "@welshman/lib"
import {createSearch} from "@welshman/app"
import Suggestions from "@lib/components/Suggestions.svelte"
import SuggestionString from "@lib/components/SuggestionString.svelte"
import Icon from "@lib/components/Icon.svelte"
import Tippy from "@lib/components/Tippy.svelte"
interface Props {
value: string
options: string[]
before?: Snippet
allowCreate?: boolean
class?: string
}
let {value, options, before, allowCreate = false, ...restProps}: Props = $props()
let input: Element | undefined = $state()
let popover: Instance | undefined = $state()
let instance: any = $state()
const search = readable(
createSearch(options, {
getValue: identity,
fuseOptions: {keys: [""]},
}).searchValues,
)
const select = (newValue: string) => {
popover?.hide()
value = newValue
}
const onKeyDown = (e: Event) => {
if (instance?.onKeyDown(e)) {
e.preventDefault()
}
}
const onFocus = () => {
popover?.show()
}
const onBlur = () => {
popover?.hide()
}
</script>
<div class={restProps.class}>
<label class="input input-bordered flex w-full items-center gap-3" bind:this={input}>
{@render before?.()}
<input
class="grow"
type="text"
bind:value
onkeydown={onKeyDown}
onfocus={onFocus}
onblur={onBlur} />
<Icon icon="alt-arrow-down" class="cursor-pointer" />
</label>
<Tippy
bind:popover
bind:instance
component={Suggestions}
props={{
search,
select,
allowCreate,
term: value,
component: SuggestionString,
class: "rounded-box",
style: `left: 4px; width: ${input?.clientWidth + 12}px`,
}}
params={{
trigger: "manual",
interactive: true,
maxWidth: "none",
getReferenceClientRect: () => input!.getBoundingClientRect(),
}} />
</div>
+42 -40
View File
@@ -1,23 +1,17 @@
<svelte:options accessors />
<script lang="ts"> <script lang="ts">
import {fly} from "svelte/transition" import {fly} from "svelte/transition"
import {throttle, clamp} from "@welshman/lib" import {throttle, clamp} from "@welshman/lib"
import {preventDefault, stopPropagation} from "@lib/html"
export let term const {term, search, select, component: Component, allowCreate = false} = $props()
export let search
export let select
export let component
export let allowCreate = false
let index = 0 let index = $state(0)
let element: Element let items: string[] = $state([])
let items: string[] = []
$: populateItems(term) $inspect(items)
const populateItems = throttle(300, term => { const populateItems = throttle(300, term => {
items = $search(term).slice(0, 5) items = search(term).slice(0, 5)
}) })
const setIndex = (newIndex: number, block: any) => { const setIndex = (newIndex: number, block: any) => {
@@ -31,14 +25,14 @@
if (value) { if (value) {
select(value) select(value)
return true return true
} else if (term && allowCreate) { } else if ($term && allowCreate) {
select(term) select($term)
return true return true
} }
} }
if (e.code === "Space" && term && allowCreate) { if (e.code === "Space" && $term && allowCreate) {
select(term) select($term)
return true return true
} }
@@ -54,31 +48,39 @@
return true return true
} }
} }
const onmousedown = (e: Event) => {
e.preventDefault()
e.stopPropagation()
}
$effect(() => {
populateItems($term)
})
</script> </script>
{#if term} <div transition:fly|local={{duration: 200}} class="tiptap-suggestions">
<div bind:this={element} transition:fly|local={{duration: 200}} class="tiptap-suggestions"> <div class="tiptap-suggestions__content">
<div class="tiptap-suggestions__content"> {#if $term && allowCreate && !items.includes($term)}
{#if term && allowCreate && !items.includes(term)} <button
<button class="tiptap-suggestions__create"
class="tiptap-suggestions__create" {onmousedown}
on:mousedown|preventDefault|stopPropagation onclick={stopPropagation(preventDefault(() => select($term)))}>
on:click|preventDefault|stopPropagation={() => select(term)}> Use "<Component value={$term}></Component>"
Use "<svelte:component this={component} value={term} />" </button>
</button>
{/if}
{#each items as value, i (value)}
<button
class="tiptap-suggestions__item"
class:tiptap-suggestions__selected={index === i}
on:mousedown|preventDefault|stopPropagation
on:click|preventDefault|stopPropagation={() => select(value)}>
<svelte:component this={component} {value} />
</button>
{/each}
</div>
{#if items.length === 0}
<div class="tiptap-suggestions__empty">No results</div>
{/if} {/if}
{#each items as value, i (value)}
<button
aria-label={value}
class="tiptap-suggestions__item"
class:tiptap-suggestions__selected={index === i}
{onmousedown}
onclick={stopPropagation(preventDefault(() => select(value)))}>
<Component {value}></Component>
</button>
{/each}
</div> </div>
{/if} {#if items.length === 0}
<div class="tiptap-suggestions__empty">No results</div>
{/if}
</div>