|
1 | 1 | <script lang="ts">
|
2 | 2 | import type { ModelEntryWithTokenizer } from "./types";
|
3 | 3 |
|
4 |
| - import { createEventDispatcher } from "svelte"; |
| 4 | + import { createEventDispatcher, tick } from "svelte"; |
5 | 5 |
|
6 | 6 | import { FEATUED_MODELS_IDS } from "./inferencePlaygroundUtils";
|
7 | 7 | import IconSearch from "../Icons/IconSearch.svelte";
|
|
11 | 11 |
|
12 | 12 | let backdropEl: HTMLDivElement;
|
13 | 13 | let query = "";
|
| 14 | + let highlightIdx = 0; |
| 15 | + let ignoreCursorHighlight = false; |
| 16 | + let containerEl: HTMLDivElement; |
14 | 17 |
|
15 | 18 | const dispatch = createEventDispatcher<{ modelSelected: string; close: void }>();
|
16 | 19 |
|
17 | 20 | function handleKeydown(event: KeyboardEvent) {
|
18 | 21 | const { key } = event;
|
| 22 | + let scrollLogicalPosition: ScrollLogicalPosition = "end"; |
19 | 23 | if (key === "Escape") {
|
20 | 24 | event.preventDefault();
|
21 | 25 | dispatch("close");
|
| 26 | + } else if (key === "Enter") { |
| 27 | + const searchEls = (containerEl.firstChild?.childNodes ?? []) as HTMLAnchorElement[]; |
| 28 | + searchEls?.[highlightIdx].click(); |
| 29 | + } else if (key === "ArrowUp") { |
| 30 | + event.preventDefault(); |
| 31 | + highlightIdx--; |
| 32 | + scrollLogicalPosition = "start"; |
| 33 | + ignoreCursorHighlight = true; |
| 34 | + } else if (key === "ArrowDown") { |
| 35 | + event.preventDefault(); |
| 36 | + highlightIdx++; |
| 37 | + ignoreCursorHighlight = true; |
| 38 | + } |
| 39 | + const n = models.length; |
| 40 | + highlightIdx = ((highlightIdx % n) + n) % n; |
| 41 | + scrollToResult(scrollLogicalPosition); |
| 42 | + } |
| 43 | +
|
| 44 | + async function scrollToResult(block: ScrollLogicalPosition) { |
| 45 | + await tick(); |
| 46 | + const highlightedEl = document.querySelector(".highlighted"); |
| 47 | + if (containerEl && highlightedEl) { |
| 48 | + const { bottom: containerBottom, top: containerTop } = containerEl.getBoundingClientRect(); |
| 49 | + const { bottom: highlightedBottom, top: highlightedTop } = highlightedEl.getBoundingClientRect(); |
| 50 | + if (highlightedBottom > containerBottom || containerTop > highlightedTop) { |
| 51 | + highlightedEl.scrollIntoView({ block }); |
| 52 | + } |
| 53 | + } |
| 54 | + } |
| 55 | +
|
| 56 | + function highlightRow(idx: number) { |
| 57 | + if (!ignoreCursorHighlight) { |
| 58 | + highlightIdx = idx; |
22 | 59 | }
|
23 | 60 | }
|
24 | 61 |
|
|
43 | 80 | );
|
44 | 81 | </script>
|
45 | 82 |
|
46 |
| -<svelte:window on:keydown={handleKeydown} /> |
| 83 | +<svelte:window on:keydown={handleKeydown} on:mousemove={() => (ignoreCursorHighlight = false)} /> |
47 | 84 |
|
48 | 85 | <div
|
49 | 86 | class="fixed inset-0 z-10 flex h-screen items-start justify-center bg-black/85 pt-32"
|
50 | 87 | bind:this={backdropEl}
|
51 | 88 | on:click|stopPropagation={handleBackdropClick}
|
52 | 89 | >
|
53 | 90 | <div class="flex w-full max-w-[600px] items-start justify-center p-10">
|
54 |
| - <div class="flex h-full w-full flex-col overflow-hidden rounded-lg border bg-white text-gray-900 shadow-md"> |
| 91 | + <div |
| 92 | + class="flex h-full w-full flex-col overflow-hidden rounded-lg border bg-white text-gray-900 shadow-md" |
| 93 | + bind:this={containerEl} |
| 94 | + > |
55 | 95 | <div class="flex items-center border-b px-3">
|
56 | 96 | <IconSearch classNames="mr-2 text-sm" />
|
57 | 97 | <input
|
|
65 | 105 | <div class="p-1">
|
66 | 106 | <div class="px-2 py-1.5 text-xs font-medium text-gray-500">Trending</div>
|
67 | 107 | <div>
|
68 |
| - {#each featuredModels as model} |
| 108 | + {#each featuredModels as model, idx} |
69 | 109 | {@const [nameSpace, modelName] = model.id.split("/")}
|
70 | 110 | <button
|
71 |
| - class="flex w-full cursor-pointer items-center px-2 py-1.5 text-sm hover:bg-gray-100" |
| 111 | + class="flex w-full cursor-pointer items-center px-2 py-1.5 text-sm {highlightIdx === idx |
| 112 | + ? 'highlighted bg-gray-100' |
| 113 | + : ''}" |
| 114 | + on:mouseenter={() => highlightRow(idx)} |
72 | 115 | on:click={() => {
|
73 | 116 | dispatch("modelSelected", model.id);
|
74 | 117 | dispatch("close");
|
|
88 | 131 | <div class="p-1">
|
89 | 132 | <div class="px-2 py-1.5 text-xs font-medium text-gray-500">Other Models</div>
|
90 | 133 | <div>
|
91 |
| - {#each otherModels as model} |
| 134 | + {#each otherModels as model, _idx} |
92 | 135 | {@const [nameSpace, modelName] = model.id.split("/")}
|
| 136 | + {@const idx = featuredModels.length + _idx} |
93 | 137 | <button
|
94 |
| - class="flex w-full cursor-pointer items-center px-2 py-1.5 text-sm hover:bg-gray-100" |
| 138 | + class="flex w-full cursor-pointer items-center px-2 py-1.5 text-sm {highlightIdx === idx |
| 139 | + ? 'highlighted bg-gray-100' |
| 140 | + : ''}" |
| 141 | + on:mouseenter={() => highlightRow(idx)} |
95 | 142 | on:click={() => {
|
96 | 143 | dispatch("modelSelected", model.id);
|
97 | 144 | dispatch("close");
|
|
0 commit comments