upend/webui/src/lib/components/utils/Editable.svelte

107 lines
2.0 KiB
Svelte

<script lang="ts">
import Selector, { type SELECTOR_TYPE, type SelectorValue } from './Selector.svelte';
import { createEventDispatcher } from 'svelte';
import type { IValue } from '@upnd/upend/types';
import IconButton from './IconButton.svelte';
const dispatch = createEventDispatcher<{ edit: SelectorValue }>();
export let editable = true;
export let value: IValue | undefined = undefined;
export let types: SELECTOR_TYPE[] | undefined = undefined;
let newValue: SelectorValue | undefined = value;
let editing = false;
let selector: Selector;
let hover = false;
let focus = false;
$: if (editing && selector) selector.focus();
$: if (!focus && !hover) editing = false;
function onInput(ev: CustomEvent<SelectorValue>) {
newValue = ev.detail;
selector.focus();
}
</script>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div
class="editable"
class:editing
on:mouseenter={() => (hover = true)}
on:mouseleave={() => (hover = false)}
>
<div class="inner">
{#if editing}
<div
class="selector"
on:keydown={(ev) => {
if (ev.key === 'Escape') {
editing = false;
}
}}
>
<Selector
{types}
initial={value}
bind:this={selector}
on:focus={(ev) => (focus = ev.detail)}
on:input={onInput}
/>
</div>
<IconButton
name="save"
on:click={() => {
if (newValue) dispatch('edit', newValue);
editing = false;
}}
/>
{:else}
<div class="content">
<slot />
</div>
{#if editable}
<div class="edit-icon">
<IconButton name="edit" on:click={() => (editing = true)} plain />
</div>
{/if}
{/if}
</div>
</div>
<style lang="scss">
.editable {
max-width: 100%;
}
.edit-icon {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
.editable:hover .edit-icon {
opacity: 0.8;
}
.inner {
display: flex;
gap: 0.25em;
align-items: center;
}
.content {
min-width: 0;
}
.selector {
flex-grow: 1;
min-width: 0;
}
.editing {
width: 100%;
}
</style>