add Numeric presentation to expression datum editor
This commit is contained in:
parent
fa782d2ed6
commit
292ebd470f
4 changed files with 52 additions and 83 deletions
|
|
@ -1,3 +1,9 @@
|
|||
<!--
|
||||
@component
|
||||
Provides user interface for specifying arbitrary scalar `Datum` values, for
|
||||
example within the `<TableViewer />` or `<ExpressionEditor />`.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import type { Datum } from "./datum.svelte";
|
||||
import {
|
||||
|
|
@ -5,19 +11,12 @@
|
|||
editor_state_from_datum,
|
||||
type EditorState,
|
||||
} from "./editor-state.svelte";
|
||||
import { type FieldInfo } from "./field.svelte";
|
||||
import { type Presentation } from "./presentation.svelte";
|
||||
|
||||
const BLUR_DEBOUNCE_MS = 100;
|
||||
|
||||
type Props = {
|
||||
/**
|
||||
* For use cases in which the user may select between multiple datum types,
|
||||
* such as when embedded in an expression editor, this array represents the
|
||||
* permissible set of field parameters.
|
||||
*/
|
||||
assignable_fields?: ReadonlyArray<FieldInfo>;
|
||||
|
||||
field_info: FieldInfo;
|
||||
current_presentation: Presentation;
|
||||
|
||||
on_blur?(ev: FocusEvent): unknown;
|
||||
|
||||
|
|
@ -36,25 +35,28 @@
|
|||
*/
|
||||
on_restore_focus?(): unknown;
|
||||
|
||||
/**
|
||||
* For use cases in which the user may select between multiple datum types,
|
||||
* such as when embedded in an expression editor, this array represents the
|
||||
* permissible set of available presentations.
|
||||
*/
|
||||
potential_presentations?: Presentation[];
|
||||
|
||||
value?: Datum;
|
||||
};
|
||||
|
||||
let {
|
||||
assignable_fields = [],
|
||||
field_info = $bindable(),
|
||||
current_presentation = $bindable(),
|
||||
on_blur,
|
||||
on_cancel_edit,
|
||||
on_change,
|
||||
on_focus,
|
||||
on_restore_focus,
|
||||
potential_presentations = [],
|
||||
value = $bindable(),
|
||||
}: Props = $props();
|
||||
|
||||
let editor_state = $state<EditorState | undefined>();
|
||||
let type_selector_menu_button_element = $state<
|
||||
HTMLButtonElement | undefined
|
||||
>();
|
||||
let type_selector_popover_element = $state<HTMLDivElement | undefined>();
|
||||
let date_input_element = $state<HTMLInputElement | undefined>();
|
||||
let text_input_element = $state<HTMLInputElement | undefined>();
|
||||
let blur_timeout = $state<number | undefined>();
|
||||
|
|
@ -67,13 +69,13 @@
|
|||
|
||||
export function focus() {
|
||||
if (
|
||||
field_info.field.presentation.t === "Dropdown" ||
|
||||
field_info.field.presentation.t === "Numeric" ||
|
||||
field_info.field.presentation.t === "Text" ||
|
||||
field_info.field.presentation.t === "Uuid"
|
||||
current_presentation.t === "Dropdown" ||
|
||||
current_presentation.t === "Numeric" ||
|
||||
current_presentation.t === "Text" ||
|
||||
current_presentation.t === "Uuid"
|
||||
) {
|
||||
text_input_element?.focus();
|
||||
} else if (field_info.field.presentation.t === "Timestamp") {
|
||||
} else if (current_presentation.t === "Timestamp") {
|
||||
date_input_element?.focus();
|
||||
}
|
||||
}
|
||||
|
|
@ -100,23 +102,10 @@
|
|||
console.warn("preconditions for handle_input() not met");
|
||||
return;
|
||||
}
|
||||
value = datum_from_editor_state(
|
||||
editor_state,
|
||||
field_info.field.presentation,
|
||||
);
|
||||
value = datum_from_editor_state(editor_state, current_presentation);
|
||||
on_change?.(value);
|
||||
}
|
||||
|
||||
function handle_type_selector_menu_button_click() {
|
||||
type_selector_popover_element?.togglePopover();
|
||||
}
|
||||
|
||||
function handle_type_selector_field_button_click(value: FieldInfo) {
|
||||
field_info = value;
|
||||
type_selector_popover_element?.hidePopover();
|
||||
type_selector_menu_button_element?.focus();
|
||||
}
|
||||
|
||||
function handle_keydown(ev: KeyboardEvent) {
|
||||
if (ev.key === "Enter") {
|
||||
on_restore_focus?.();
|
||||
|
|
@ -127,6 +116,9 @@
|
|||
}
|
||||
}
|
||||
|
||||
// These handlers must be passed to interactive children to correctly handle
|
||||
// focus/blur behavior, particularly when the `<DatumEditor />` is a child of
|
||||
// `<TableViewer />`.
|
||||
const interactive_handlers = {
|
||||
onblur: handle_blur,
|
||||
onfocus: handle_focus,
|
||||
|
|
@ -141,35 +133,19 @@
|
|||
onfocus={handle_focus}
|
||||
>
|
||||
{#if editor_state}
|
||||
{#if assignable_fields?.length > 0}
|
||||
{#if potential_presentations?.length > 0}
|
||||
<div class="datum-editor__type-selector">
|
||||
<button
|
||||
<select
|
||||
{...interactive_handlers}
|
||||
bind:this={type_selector_menu_button_element}
|
||||
class="datum-editor__type-selector-menu-button"
|
||||
onclick={handle_type_selector_menu_button_click}
|
||||
type="button"
|
||||
oninput={(ev) => {
|
||||
current_presentation =
|
||||
potential_presentations[parseInt(ev.currentTarget.value)];
|
||||
}}
|
||||
>
|
||||
{field_info.field.presentation.t}
|
||||
</button>
|
||||
<div
|
||||
bind:this={type_selector_popover_element}
|
||||
class="datum-editor__type-selector-popover"
|
||||
onblur={handle_blur}
|
||||
onfocus={handle_focus}
|
||||
popover="auto"
|
||||
>
|
||||
{#each assignable_fields as assignable_field_info}
|
||||
<button
|
||||
{...interactive_handlers}
|
||||
onclick={() =>
|
||||
handle_type_selector_field_button_click(assignable_field_info)}
|
||||
type="button"
|
||||
>
|
||||
{assignable_field_info.field.presentation.t}
|
||||
</button>
|
||||
{#each potential_presentations as presentation, i}
|
||||
<option value={`${i}`}>{presentation.t}</option>
|
||||
{/each}
|
||||
</div>
|
||||
</select>
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
|
|
@ -194,7 +170,7 @@
|
|||
<i class="ti ti-cube"></i>
|
||||
{/if}
|
||||
</button>
|
||||
{#if ["Dropdown", "Numeric", "Text", "Uuid"].includes(field_info.field.presentation.t)}
|
||||
{#if ["Dropdown", "Numeric", "Text", "Uuid"].includes(current_presentation.t)}
|
||||
<input
|
||||
{...interactive_handlers}
|
||||
bind:this={text_input_element}
|
||||
|
|
@ -213,7 +189,7 @@
|
|||
class="datum-editor__text-input"
|
||||
type="text"
|
||||
/>
|
||||
{:else if field_info.field.presentation.t === "Timestamp"}
|
||||
{:else if current_presentation.t === "Timestamp"}
|
||||
<div class="datum-editor__timestamp-inputs">
|
||||
<input
|
||||
{...interactive_handlers}
|
||||
|
|
@ -248,10 +224,10 @@
|
|||
</div>
|
||||
{/if}
|
||||
<div class="datum-editor__helpers" tabindex="-1">
|
||||
{#if field_info.field.presentation.t === "Dropdown"}
|
||||
{#if current_presentation.t === "Dropdown"}
|
||||
<!-- TODO: This is an awkward way to implement a keyboard-navigable listbox. -->
|
||||
<menu class="datum-editor__dropdown-options">
|
||||
{#each field_info.field.presentation.c.options as dropdown_option}
|
||||
{#each current_presentation.c.options as dropdown_option}
|
||||
<!-- FIXME: validate or escape dropdown_option.color -->
|
||||
<li
|
||||
class={[
|
||||
|
|
|
|||
|
|
@ -14,29 +14,15 @@
|
|||
import ExpressionSelector from "./expression-selector.svelte";
|
||||
import { type PgExpressionAny } from "./expression.svelte";
|
||||
import ExpressionEditor from "./expression-editor.webc.svelte";
|
||||
import { type FieldInfo } from "./field.svelte";
|
||||
import { RFC_3339_S, type Presentation } from "./presentation.svelte";
|
||||
import type { Datum } from "./datum.svelte";
|
||||
|
||||
const ASSIGNABLE_PRESENTATIONS: Presentation[] = [
|
||||
const POTENTIAL_PRESENTATIONS: Presentation[] = [
|
||||
{ t: "Numeric", c: {} },
|
||||
{ t: "Text", c: { input_mode: { t: "MultiLine", c: {} } } },
|
||||
{ t: "Timestamp", c: { format: RFC_3339_S } },
|
||||
{ t: "Uuid", c: {} },
|
||||
];
|
||||
const ASSIGNABLE_FIELDS: FieldInfo[] = ASSIGNABLE_PRESENTATIONS.map(
|
||||
(presentation) => ({
|
||||
field: {
|
||||
id: "",
|
||||
name: "",
|
||||
ordinality: -1,
|
||||
presentation,
|
||||
table_label: "",
|
||||
table_width_px: -1,
|
||||
},
|
||||
not_null: true,
|
||||
has_default: false,
|
||||
}),
|
||||
);
|
||||
|
||||
type Props = {
|
||||
identifier_hints?: string[];
|
||||
|
|
@ -47,7 +33,7 @@
|
|||
|
||||
// Dynamic state to bind to datum editor.
|
||||
let editor_value = $state<Datum | undefined>();
|
||||
let editor_field_info = $state<FieldInfo>(ASSIGNABLE_FIELDS[0]);
|
||||
let editor_presentation = $state<Presentation>(POTENTIAL_PRESENTATIONS[0]);
|
||||
|
||||
$effect(() => {
|
||||
editor_value = value?.t === "Literal" ? value.c : undefined;
|
||||
|
|
@ -93,9 +79,9 @@
|
|||
</select>
|
||||
{:else if value.t === "Literal"}
|
||||
<DatumEditor
|
||||
bind:field_info={editor_field_info}
|
||||
bind:current_presentation={editor_presentation}
|
||||
bind:value={editor_value}
|
||||
assignable_fields={ASSIGNABLE_FIELDS}
|
||||
potential_presentations={POTENTIAL_PRESENTATIONS}
|
||||
on_change={handle_editor_change}
|
||||
/>
|
||||
{/if}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,9 @@
|
|||
<!--
|
||||
@component
|
||||
Dropdown menu with grid of buttons for quickly selecting a Postgres expression
|
||||
type. Used by `<ExpressionEditor />`.
|
||||
-->
|
||||
|
||||
<script lang="ts">
|
||||
import { type PgExpressionAny, expression_icon } from "./expression.svelte";
|
||||
|
||||
|
|
|
|||
|
|
@ -718,7 +718,8 @@
|
|||
<DatumEditor
|
||||
bind:this={datum_editor}
|
||||
bind:value={editor_value}
|
||||
field_info={lazy_data.fields[selections[0].coords.field_idx]}
|
||||
current_presentation={lazy_data.fields[selections[0].coords.field_idx]
|
||||
.field.presentation}
|
||||
on_blur={() => try_queue_delta()}
|
||||
on_cancel_edit={cancel_edit}
|
||||
on_change={() => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue