diff --git a/static/portal-table.css b/static/portal-table.css
index 8297699..e551a4f 100644
--- a/static/portal-table.css
+++ b/static/portal-table.css
@@ -343,7 +343,7 @@
flex: 1;
grid-template:
'null-control value-control' max-content
- 'helpers helpers' auto / max-content auto;
+ 'helpers helpers' 1fr / max-content 1fr;
&:has(:focus) {
border-left-color: #07f;
diff --git a/svelte/src/datum-editor.svelte b/svelte/src/datum-editor.svelte
index 72d39f6..a1c0037 100644
--- a/svelte/src/datum-editor.svelte
+++ b/svelte/src/datum-editor.svelte
@@ -11,7 +11,10 @@ example within the `` or ``.
editor_state_from_datum,
type EditorState,
} from "./editor-state.svelte";
- import { type Presentation } from "./presentation.svelte";
+ import {
+ type DropdownOption,
+ type Presentation,
+ } from "./presentation.svelte";
const BLUR_DEBOUNCE_MS = 100;
@@ -67,6 +70,8 @@ example within the `` or ``.
}
});
+ let filtered_dropdown_options: DropdownOption[] = $state([]);
+
export function focus() {
if (
current_presentation.t === "Dropdown" ||
@@ -86,12 +91,16 @@ example within the `` or ``.
if (blur_timeout !== undefined) {
clearTimeout(blur_timeout);
}
- blur_timeout = setTimeout(() => on_blur?.(ev), BLUR_DEBOUNCE_MS);
+ blur_timeout = setTimeout(() => {
+ on_blur?.(ev);
+ filtered_dropdown_options = [];
+ }, BLUR_DEBOUNCE_MS);
}
function handle_focus(ev: FocusEvent) {
if (blur_timeout === undefined) {
on_focus?.(ev);
+ update_dropdown_filter();
} else {
clearTimeout(blur_timeout);
}
@@ -106,6 +115,29 @@ example within the `` or ``.
on_change?.(value);
}
+ function handle_textinput_input(input_value: string) {
+ if (!editor_state) {
+ console.warn("text input oninput() preconditions not met");
+ return;
+ }
+ editor_state.text_value = input_value;
+ if (input_value !== "") {
+ editor_state.is_null = false;
+ }
+ handle_input();
+ }
+
+ function update_dropdown_filter() {
+ if (current_presentation.t === "Dropdown") {
+ filtered_dropdown_options = current_presentation.c.options.filter(
+ (option) =>
+ option.value
+ .toLocaleLowerCase("en")
+ .includes(editor_state?.text_value.toLocaleLowerCase("en") ?? ""),
+ );
+ }
+ }
+
function handle_keydown(ev: KeyboardEvent) {
if (ev.key === "Enter") {
on_restore_focus?.();
@@ -113,6 +145,33 @@ example within the `` or ``.
// Cancel edit before blurring, or else the table will try to commit it.
on_cancel_edit?.();
on_restore_focus?.();
+ } else if (
+ current_presentation.t === "Dropdown" &&
+ filtered_dropdown_options.length > 0
+ ) {
+ const current_option_idx = filtered_dropdown_options.findIndex(
+ (option) =>
+ option.value.toLocaleLowerCase("en") ===
+ (editor_state?.text_value.toLocaleLowerCase("en") ?? ""),
+ );
+ if (ev.key === "ArrowUp" || (ev.key === "Tab" && ev.shiftKey)) {
+ // Prevent shifting focus with tab key. User can still use enter or esc.
+ ev.preventDefault();
+ handle_textinput_input(
+ filtered_dropdown_options[Math.max(current_option_idx - 1, 0)].value,
+ );
+ } else if (ev.key === "ArrowDown" || (ev.key === "Tab" && !ev.shiftKey)) {
+ // Prevent shifting focus with tab key. User can still use enter or esc.
+ ev.preventDefault();
+ handle_textinput_input(
+ filtered_dropdown_options[
+ Math.min(
+ current_option_idx + 1,
+ filtered_dropdown_options.length - 1,
+ )
+ ].value,
+ );
+ }
}
}
@@ -176,15 +235,8 @@ example within the `` or ``.
bind:this={text_input_element}
value={editor_state.text_value}
oninput={({ currentTarget }) => {
- if (!editor_state) {
- console.warn("text input oninput() preconditions not met");
- return;
- }
- editor_state.text_value = currentTarget.value;
- if (currentTarget.value !== "") {
- editor_state.is_null = false;
- }
- handle_input();
+ handle_textinput_input(currentTarget.value);
+ update_dropdown_filter();
}}
class="datum-editor__text-input"
type="text"
@@ -223,11 +275,10 @@ example within the `` or ``.
/>
{/if}
-
+
{#if current_presentation.t === "Dropdown"}
-