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">
|
<script lang="ts">
|
||||||
import type { Datum } from "./datum.svelte";
|
import type { Datum } from "./datum.svelte";
|
||||||
import {
|
import {
|
||||||
|
|
@ -5,19 +11,12 @@
|
||||||
editor_state_from_datum,
|
editor_state_from_datum,
|
||||||
type EditorState,
|
type EditorState,
|
||||||
} from "./editor-state.svelte";
|
} from "./editor-state.svelte";
|
||||||
import { type FieldInfo } from "./field.svelte";
|
import { type Presentation } from "./presentation.svelte";
|
||||||
|
|
||||||
const BLUR_DEBOUNCE_MS = 100;
|
const BLUR_DEBOUNCE_MS = 100;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
/**
|
current_presentation: Presentation;
|
||||||
* 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;
|
|
||||||
|
|
||||||
on_blur?(ev: FocusEvent): unknown;
|
on_blur?(ev: FocusEvent): unknown;
|
||||||
|
|
||||||
|
|
@ -36,25 +35,28 @@
|
||||||
*/
|
*/
|
||||||
on_restore_focus?(): unknown;
|
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;
|
value?: Datum;
|
||||||
};
|
};
|
||||||
|
|
||||||
let {
|
let {
|
||||||
assignable_fields = [],
|
current_presentation = $bindable(),
|
||||||
field_info = $bindable(),
|
|
||||||
on_blur,
|
on_blur,
|
||||||
on_cancel_edit,
|
on_cancel_edit,
|
||||||
on_change,
|
on_change,
|
||||||
on_focus,
|
on_focus,
|
||||||
on_restore_focus,
|
on_restore_focus,
|
||||||
|
potential_presentations = [],
|
||||||
value = $bindable(),
|
value = $bindable(),
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let editor_state = $state<EditorState | undefined>();
|
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 date_input_element = $state<HTMLInputElement | undefined>();
|
||||||
let text_input_element = $state<HTMLInputElement | undefined>();
|
let text_input_element = $state<HTMLInputElement | undefined>();
|
||||||
let blur_timeout = $state<number | undefined>();
|
let blur_timeout = $state<number | undefined>();
|
||||||
|
|
@ -67,13 +69,13 @@
|
||||||
|
|
||||||
export function focus() {
|
export function focus() {
|
||||||
if (
|
if (
|
||||||
field_info.field.presentation.t === "Dropdown" ||
|
current_presentation.t === "Dropdown" ||
|
||||||
field_info.field.presentation.t === "Numeric" ||
|
current_presentation.t === "Numeric" ||
|
||||||
field_info.field.presentation.t === "Text" ||
|
current_presentation.t === "Text" ||
|
||||||
field_info.field.presentation.t === "Uuid"
|
current_presentation.t === "Uuid"
|
||||||
) {
|
) {
|
||||||
text_input_element?.focus();
|
text_input_element?.focus();
|
||||||
} else if (field_info.field.presentation.t === "Timestamp") {
|
} else if (current_presentation.t === "Timestamp") {
|
||||||
date_input_element?.focus();
|
date_input_element?.focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,23 +102,10 @@
|
||||||
console.warn("preconditions for handle_input() not met");
|
console.warn("preconditions for handle_input() not met");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
value = datum_from_editor_state(
|
value = datum_from_editor_state(editor_state, current_presentation);
|
||||||
editor_state,
|
|
||||||
field_info.field.presentation,
|
|
||||||
);
|
|
||||||
on_change?.(value);
|
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) {
|
function handle_keydown(ev: KeyboardEvent) {
|
||||||
if (ev.key === "Enter") {
|
if (ev.key === "Enter") {
|
||||||
on_restore_focus?.();
|
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 = {
|
const interactive_handlers = {
|
||||||
onblur: handle_blur,
|
onblur: handle_blur,
|
||||||
onfocus: handle_focus,
|
onfocus: handle_focus,
|
||||||
|
|
@ -141,35 +133,19 @@
|
||||||
onfocus={handle_focus}
|
onfocus={handle_focus}
|
||||||
>
|
>
|
||||||
{#if editor_state}
|
{#if editor_state}
|
||||||
{#if assignable_fields?.length > 0}
|
{#if potential_presentations?.length > 0}
|
||||||
<div class="datum-editor__type-selector">
|
<div class="datum-editor__type-selector">
|
||||||
<button
|
<select
|
||||||
{...interactive_handlers}
|
{...interactive_handlers}
|
||||||
bind:this={type_selector_menu_button_element}
|
oninput={(ev) => {
|
||||||
class="datum-editor__type-selector-menu-button"
|
current_presentation =
|
||||||
onclick={handle_type_selector_menu_button_click}
|
potential_presentations[parseInt(ev.currentTarget.value)];
|
||||||
type="button"
|
}}
|
||||||
>
|
>
|
||||||
{field_info.field.presentation.t}
|
{#each potential_presentations as presentation, i}
|
||||||
</button>
|
<option value={`${i}`}>{presentation.t}</option>
|
||||||
<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}
|
{/each}
|
||||||
</div>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<button
|
<button
|
||||||
|
|
@ -194,7 +170,7 @@
|
||||||
<i class="ti ti-cube"></i>
|
<i class="ti ti-cube"></i>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{#if ["Dropdown", "Numeric", "Text", "Uuid"].includes(field_info.field.presentation.t)}
|
{#if ["Dropdown", "Numeric", "Text", "Uuid"].includes(current_presentation.t)}
|
||||||
<input
|
<input
|
||||||
{...interactive_handlers}
|
{...interactive_handlers}
|
||||||
bind:this={text_input_element}
|
bind:this={text_input_element}
|
||||||
|
|
@ -213,7 +189,7 @@
|
||||||
class="datum-editor__text-input"
|
class="datum-editor__text-input"
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
{:else if field_info.field.presentation.t === "Timestamp"}
|
{:else if current_presentation.t === "Timestamp"}
|
||||||
<div class="datum-editor__timestamp-inputs">
|
<div class="datum-editor__timestamp-inputs">
|
||||||
<input
|
<input
|
||||||
{...interactive_handlers}
|
{...interactive_handlers}
|
||||||
|
|
@ -248,10 +224,10 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="datum-editor__helpers" tabindex="-1">
|
<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. -->
|
<!-- TODO: This is an awkward way to implement a keyboard-navigable listbox. -->
|
||||||
<menu class="datum-editor__dropdown-options">
|
<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 -->
|
<!-- FIXME: validate or escape dropdown_option.color -->
|
||||||
<li
|
<li
|
||||||
class={[
|
class={[
|
||||||
|
|
|
||||||
|
|
@ -14,29 +14,15 @@
|
||||||
import ExpressionSelector from "./expression-selector.svelte";
|
import ExpressionSelector from "./expression-selector.svelte";
|
||||||
import { type PgExpressionAny } from "./expression.svelte";
|
import { type PgExpressionAny } from "./expression.svelte";
|
||||||
import ExpressionEditor from "./expression-editor.webc.svelte";
|
import ExpressionEditor from "./expression-editor.webc.svelte";
|
||||||
import { type FieldInfo } from "./field.svelte";
|
|
||||||
import { RFC_3339_S, type Presentation } from "./presentation.svelte";
|
import { RFC_3339_S, type Presentation } from "./presentation.svelte";
|
||||||
import type { Datum } from "./datum.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: "Text", c: { input_mode: { t: "MultiLine", c: {} } } },
|
||||||
{ t: "Timestamp", c: { format: RFC_3339_S } },
|
{ t: "Timestamp", c: { format: RFC_3339_S } },
|
||||||
{ t: "Uuid", c: {} },
|
{ 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 = {
|
type Props = {
|
||||||
identifier_hints?: string[];
|
identifier_hints?: string[];
|
||||||
|
|
@ -47,7 +33,7 @@
|
||||||
|
|
||||||
// Dynamic state to bind to datum editor.
|
// Dynamic state to bind to datum editor.
|
||||||
let editor_value = $state<Datum | undefined>();
|
let editor_value = $state<Datum | undefined>();
|
||||||
let editor_field_info = $state<FieldInfo>(ASSIGNABLE_FIELDS[0]);
|
let editor_presentation = $state<Presentation>(POTENTIAL_PRESENTATIONS[0]);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
editor_value = value?.t === "Literal" ? value.c : undefined;
|
editor_value = value?.t === "Literal" ? value.c : undefined;
|
||||||
|
|
@ -93,9 +79,9 @@
|
||||||
</select>
|
</select>
|
||||||
{:else if value.t === "Literal"}
|
{:else if value.t === "Literal"}
|
||||||
<DatumEditor
|
<DatumEditor
|
||||||
bind:field_info={editor_field_info}
|
bind:current_presentation={editor_presentation}
|
||||||
bind:value={editor_value}
|
bind:value={editor_value}
|
||||||
assignable_fields={ASSIGNABLE_FIELDS}
|
potential_presentations={POTENTIAL_PRESENTATIONS}
|
||||||
on_change={handle_editor_change}
|
on_change={handle_editor_change}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/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">
|
<script lang="ts">
|
||||||
import { type PgExpressionAny, expression_icon } from "./expression.svelte";
|
import { type PgExpressionAny, expression_icon } from "./expression.svelte";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -718,7 +718,8 @@
|
||||||
<DatumEditor
|
<DatumEditor
|
||||||
bind:this={datum_editor}
|
bind:this={datum_editor}
|
||||||
bind:value={editor_value}
|
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_blur={() => try_queue_delta()}
|
||||||
on_cancel_edit={cancel_edit}
|
on_cancel_edit={cancel_edit}
|
||||||
on_change={() => {
|
on_change={() => {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue