Introduce AutocompleteItem

* needs too much custom styling / behavior to be done with DropdownItem
This commit is contained in:
Henrik Giesel 2021-06-29 01:37:23 +02:00
parent b2d2cb8715
commit b93646209a
5 changed files with 147 additions and 67 deletions

View file

@ -50,10 +50,6 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
background: none; background: none;
box-shadow: none !important; box-shadow: none !important;
border: none; border: none;
}
.btn-day {
color: black;
&:active, &:active,
&.active { &.active {
@ -62,19 +58,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
} }
} }
.btn-day {
color: black;
}
.btn-night { .btn-night {
color: white; color: white;
&:hover, &:hover,
&:focus, &:focus {
&.focus {
@include button.btn-night-base; @include button.btn-night-base;
} }
&:active,
&.active {
background-color: button.$focus-color;
color: white;
}
} }
</style> </style>

View file

@ -0,0 +1,81 @@
<!--
Copyright: Ankitects Pty Ltd and contributors
License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
-->
<script lang="typescript">
import { onMount, createEventDispatcher, getContext } from "svelte";
import { nightModeKey } from "components/contextKeys";
export let id: string | undefined = undefined;
let className = "";
export { className as class };
export let selected = false;
export let active = false;
const nightMode = getContext<boolean>(nightModeKey);
const dispatch = createEventDispatcher();
let buttonRef: HTMLButtonElement;
onMount(() => dispatch("mount", { button: buttonRef }));
</script>
<button
{id}
tabindex="-1"
bind:this={buttonRef}
class={`btn ${className}`}
class:btn-day={!nightMode}
class:btn-night={nightMode}
class:selected
class:active
on:mouseup
on:mouseenter
on:mousedown|preventDefault
>
<slot />
</button>
<style lang="scss">
@use 'ts/sass/button_mixins' as button;
button {
display: flex;
justify-content: space-between;
font-size: calc(var(--buttons-size) / 2.3);
background: none;
box-shadow: none !important;
border: none;
&.active {
background-color: button.$focus-color;
color: white;
}
}
/* reset global CSS from buttons.scss */
:global(.nightMode) button:hover {
background-color: inherit;
}
/* extra specificity bc of global CSS reset above */
button.btn-day {
color: black;
&.selected {
background-color: #e9ecef;
border-color: #e9ecef;
}
}
button.btn-night {
color: white;
&.selected {
@include button.btn-night-base;
}
}
</style>

View file

@ -245,6 +245,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
break; break;
case "Enter": case "Enter":
console.log("choose");
autocomplete.chooseSelected(); autocomplete.chooseSelected();
event.preventDefault(); event.preventDefault();
break; break;

View file

@ -9,7 +9,7 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
import WithDropdownMenu from "components/WithDropdownMenu.svelte"; import WithDropdownMenu from "components/WithDropdownMenu.svelte";
import DropdownMenu from "components/DropdownMenu.svelte"; import DropdownMenu from "components/DropdownMenu.svelte";
import DropdownItem from "components/DropdownItem.svelte"; import AutocompleteItem from "./AutocompleteItem.svelte";
let className: string = ""; let className: string = "";
export { className as class }; export { className as class };
@ -26,32 +26,32 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
function selectNext() { async function selectNext() {
suggestionsPromise.then((suggestions) => { const suggestions = await suggestionsPromise;
if (selected === null) {
selected = 0;
} else if (selected >= suggestions.length - 1) {
selected = null;
} else {
selected++;
}
dispatch("autocomplete", { selected: suggestions[selected ?? -1] }); if (selected === null) {
}); selected = 0;
} else if (selected >= suggestions.length - 1) {
selected = null;
} else {
selected++;
}
dispatch("autocomplete", { selected: suggestions[selected ?? -1] });
} }
function selectPrevious() { async function selectPrevious() {
suggestionsPromise.then((suggestions) => { const suggestions = await suggestionsPromise;
if (selected === null) {
selected = suggestions.length - 1;
} else if (selected === 0) {
selected = null;
} else {
selected--;
}
dispatch("autocomplete", { selected: suggestions[selected ?? -1] }); if (selected === null) {
}); selected = suggestions.length - 1;
} else if (selected === 0) {
selected = null;
} else {
selected--;
}
dispatch("autocomplete", { selected: suggestions[selected ?? -1] });
} }
function chooseSelected() { function chooseSelected() {
@ -63,18 +63,16 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
dropdown.update(); dropdown.update();
dispatch("update"); dispatch("update");
await tick(); const [, suggestions] = await Promise.all([tick(), suggestionsPromise]);
suggestionsPromise.then((suggestions) => { if (suggestions.length > 0) {
if (suggestions.length > 0) { dropdown.show();
dropdown.show(); // disabled class will tell Bootstrap not to show menu on clicking
// disabled class will tell Bootstrap not to show menu on clicking target.classList.remove("disabled");
target.classList.remove("disabled"); } else {
} else { dropdown.hide();
dropdown.hide(); target.classList.add("disabled");
target.classList.add("disabled"); }
}
});
} }
const createAutocomplete = const createAutocomplete =
@ -97,6 +95,21 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
}; };
onDestroy(() => dropdown?.dispose()); onDestroy(() => dropdown?.dispose());
function setSelected(index: number): void {
selected = index;
}
async function chooseIndex(index: number): Promise<void> {
const suggestions = await suggestionsPromise;
dispatch("autocomplete", { selected: suggestions[index] });
}
function selectIfMousedown(event: MouseEvent, index: number): void {
if (event.buttons === 1) {
setSelected(index);
}
}
</script> </script>
<WithDropdownMenu let:menuId let:createDropdown> <WithDropdownMenu let:menuId let:createDropdown>
@ -104,27 +117,17 @@ License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
<DropdownMenu id={menuId} class={className}> <DropdownMenu id={menuId} class={className}>
{#await suggestionsPromise} {#await suggestionsPromise}
<div class="suggestion-item"> <AutocompleteItem>...</AutocompleteItem>
<DropdownItem>...</DropdownItem>
</div>
{:then suggestions} {:then suggestions}
{#each suggestions as suggestion, i} {#each suggestions as suggestion, index}
<div class="suggestion-item"> <AutocompleteItem
<DropdownItem selected={index === selected}
class={i === selected ? (active ? "active" : "focus") : ""} active={index === selected && active}
on:click>{suggestion}</DropdownItem on:mousedown={() => setSelected(index)}
> on:mouseenter={(event) => selectIfMousedown(event, index)}
</div> on:mouseup={() => chooseIndex(index)}>{suggestion}</AutocompleteItem
>
{/each} {/each}
{/await} {/await}
</DropdownMenu> </DropdownMenu>
</WithDropdownMenu> </WithDropdownMenu>
<style lang="scss">
.suggestion-item {
:global(.dropdown-item:hover) {
background-color: inherit !important;
border-color: inherit !important;
}
}
</style>

View file

@ -28,7 +28,8 @@ $btn-base-color-day: white;
@content ($btn-base-color-day); @content ($btn-base-color-day);
@if ($with-hover) { @if ($with-hover) {
&:hover { &:hover,
&.hover {
background-color: darken($btn-base-color-day, 8%); background-color: darken($btn-base-color-day, 8%);
} }
} }
@ -76,7 +77,8 @@ $btn-base-color-night: #666;
@content ($btn-base-color-night); @content ($btn-base-color-night);
@if ($with-hover) { @if ($with-hover) {
&:hover { &:hover,
&.hover {
background-color: lighten($btn-base-color-night, 8%); background-color: lighten($btn-base-color-night, 8%);
border-color: lighten($btn-base-color-night, 8%); border-color: lighten($btn-base-color-night, 8%);
} }