2023-02-24 05:32:18 +08:00
|
|
|
<script lang="ts">
|
|
|
|
import { fly } from "svelte/transition";
|
|
|
|
import { createEventDispatcher } from "svelte";
|
2023-09-13 00:58:18 +08:00
|
|
|
export let choices: [string, string | number][];
|
|
|
|
export let filtered_indices: number[];
|
|
|
|
export let show_options = false;
|
2023-07-31 20:31:39 +08:00
|
|
|
export let disabled = false;
|
2023-09-13 00:58:18 +08:00
|
|
|
export let selected_indices: (string | number)[] = [];
|
|
|
|
export let active_index: number | null = null;
|
2023-02-24 05:32:18 +08:00
|
|
|
|
2023-03-21 13:57:49 +08:00
|
|
|
let distance_from_top: number;
|
|
|
|
let distance_from_bottom: number;
|
|
|
|
let input_height: number;
|
2023-06-29 05:00:27 +08:00
|
|
|
let input_width: number;
|
2023-03-21 13:57:49 +08:00
|
|
|
let refElement: HTMLDivElement;
|
2023-04-26 00:12:38 +08:00
|
|
|
let listElement: HTMLUListElement;
|
2023-03-21 13:57:49 +08:00
|
|
|
let top: string | null, bottom: string | null, max_height: number;
|
2023-06-29 05:00:27 +08:00
|
|
|
let innerHeight: number;
|
|
|
|
|
2023-07-31 20:31:39 +08:00
|
|
|
function calculate_window_distance(): void {
|
2023-06-29 05:00:27 +08:00
|
|
|
const { top: ref_top, bottom: ref_bottom } =
|
|
|
|
refElement.getBoundingClientRect();
|
|
|
|
distance_from_top = ref_top;
|
|
|
|
distance_from_bottom = innerHeight - ref_bottom;
|
|
|
|
}
|
|
|
|
|
|
|
|
let scroll_timeout: NodeJS.Timeout | null = null;
|
2023-07-31 20:31:39 +08:00
|
|
|
function scroll_listener(): void {
|
2023-09-13 00:58:18 +08:00
|
|
|
if (!show_options) return;
|
2023-06-29 05:00:27 +08:00
|
|
|
if (scroll_timeout !== null) {
|
|
|
|
clearTimeout(scroll_timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
scroll_timeout = setTimeout(() => {
|
|
|
|
calculate_window_distance();
|
|
|
|
scroll_timeout = null;
|
|
|
|
}, 10);
|
|
|
|
}
|
|
|
|
|
2023-03-21 13:57:49 +08:00
|
|
|
$: {
|
2023-09-13 00:58:18 +08:00
|
|
|
if (show_options && refElement) {
|
|
|
|
if (listElement && selected_indices.length > 0) {
|
2023-07-31 20:31:39 +08:00
|
|
|
let elements = listElement.querySelectorAll("li");
|
|
|
|
for (const element of Array.from(elements)) {
|
2023-09-13 00:58:18 +08:00
|
|
|
if (
|
|
|
|
element.getAttribute("data-index") ===
|
|
|
|
selected_indices[0].toString()
|
|
|
|
) {
|
2023-08-25 01:22:41 +08:00
|
|
|
listElement?.scrollTo?.(0, (element as HTMLLIElement).offsetTop);
|
2023-07-31 20:31:39 +08:00
|
|
|
break;
|
|
|
|
}
|
2023-04-26 00:12:38 +08:00
|
|
|
}
|
|
|
|
}
|
2023-06-29 05:00:27 +08:00
|
|
|
calculate_window_distance();
|
|
|
|
const rect = refElement.parentElement?.getBoundingClientRect();
|
|
|
|
input_height = rect?.height || 0;
|
|
|
|
input_width = rect?.width || 0;
|
2023-03-21 13:57:49 +08:00
|
|
|
}
|
|
|
|
if (distance_from_bottom > distance_from_top) {
|
2023-06-29 05:00:27 +08:00
|
|
|
top = `${distance_from_top}px`;
|
2023-03-21 13:57:49 +08:00
|
|
|
max_height = distance_from_bottom;
|
|
|
|
bottom = null;
|
|
|
|
} else {
|
2023-06-29 05:00:27 +08:00
|
|
|
bottom = `${distance_from_bottom + input_height}px`;
|
2023-03-21 13:57:49 +08:00
|
|
|
max_height = distance_from_top - input_height;
|
|
|
|
top = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-24 05:32:18 +08:00
|
|
|
const dispatch = createEventDispatcher();
|
|
|
|
</script>
|
|
|
|
|
2023-06-29 05:00:27 +08:00
|
|
|
<svelte:window on:scroll={scroll_listener} bind:innerHeight />
|
|
|
|
|
2023-03-21 13:57:49 +08:00
|
|
|
<div class="reference" bind:this={refElement} />
|
2023-09-13 00:58:18 +08:00
|
|
|
{#if show_options && !disabled}
|
2023-02-24 05:32:18 +08:00
|
|
|
<ul
|
|
|
|
class="options"
|
|
|
|
transition:fly={{ duration: 200, y: 5 }}
|
|
|
|
on:mousedown|preventDefault={(e) => dispatch("change", e)}
|
2023-03-21 13:57:49 +08:00
|
|
|
style:top
|
|
|
|
style:bottom
|
|
|
|
style:max-height={`calc(${max_height}px - var(--window-padding))`}
|
2023-06-29 05:00:27 +08:00
|
|
|
style:width={input_width + "px"}
|
2023-04-26 00:12:38 +08:00
|
|
|
bind:this={listElement}
|
2023-09-22 20:12:26 +08:00
|
|
|
role="listbox"
|
2023-02-24 05:32:18 +08:00
|
|
|
>
|
2023-09-13 00:58:18 +08:00
|
|
|
{#each filtered_indices as index}
|
2023-02-24 05:32:18 +08:00
|
|
|
<li
|
|
|
|
class="item"
|
2023-09-13 00:58:18 +08:00
|
|
|
class:selected={selected_indices.includes(index)}
|
|
|
|
class:active={index === active_index}
|
|
|
|
class:bg-gray-100={index === active_index}
|
|
|
|
class:dark:bg-gray-600={index === active_index}
|
|
|
|
data-index={index}
|
|
|
|
aria-label={choices[index][0]}
|
2023-08-25 01:22:41 +08:00
|
|
|
data-testid="dropdown-option"
|
2023-09-22 20:12:26 +08:00
|
|
|
role="option"
|
|
|
|
aria-selected={selected_indices.includes(index)}
|
2023-02-24 05:32:18 +08:00
|
|
|
>
|
2023-09-13 00:58:18 +08:00
|
|
|
<span class:hide={!selected_indices.includes(index)} class="inner-item">
|
2023-02-24 05:32:18 +08:00
|
|
|
✓
|
|
|
|
</span>
|
2023-09-13 00:58:18 +08:00
|
|
|
{choices[index][0]}
|
2023-02-24 05:32:18 +08:00
|
|
|
</li>
|
|
|
|
{/each}
|
|
|
|
</ul>
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
<style>
|
|
|
|
.options {
|
2023-03-21 13:57:49 +08:00
|
|
|
--window-padding: var(--size-8);
|
2023-06-29 05:00:27 +08:00
|
|
|
position: fixed;
|
2023-06-08 06:28:15 +08:00
|
|
|
z-index: var(--layer-top);
|
2023-02-24 05:32:18 +08:00
|
|
|
margin-left: 0;
|
|
|
|
box-shadow: var(--shadow-drop-lg);
|
2023-03-07 04:52:31 +08:00
|
|
|
border-radius: var(--container-radius);
|
2023-03-18 08:20:55 +08:00
|
|
|
background: var(--background-fill-primary);
|
2023-04-28 00:56:39 +08:00
|
|
|
min-width: fit-content;
|
|
|
|
max-width: inherit;
|
2023-02-24 05:32:18 +08:00
|
|
|
overflow: auto;
|
2023-03-07 04:52:31 +08:00
|
|
|
color: var(--body-text-color);
|
2023-02-24 05:32:18 +08:00
|
|
|
list-style: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
.item {
|
|
|
|
display: flex;
|
|
|
|
cursor: pointer;
|
|
|
|
padding: var(--size-2);
|
|
|
|
}
|
|
|
|
|
|
|
|
.item:hover,
|
|
|
|
.active {
|
2023-03-18 08:20:55 +08:00
|
|
|
background: var(--background-fill-secondary);
|
2023-02-24 05:32:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
.inner-item {
|
|
|
|
padding-right: var(--size-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
.hide {
|
|
|
|
visibility: hidden;
|
|
|
|
}
|
|
|
|
</style>
|