Skip to content

Commit 22da956

Browse files
committed
More work, still more bugs to fix
1 parent 01deef2 commit 22da956

File tree

3 files changed

+70
-32
lines changed

3 files changed

+70
-32
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,5 +46,10 @@
4646
"files.insertFinalNewline": true,
4747
"files.associations": {
4848
"*.graphite": "json"
49+
},
50+
"[javascript][scss][svelte][typescript]": {
51+
"editor.codeActionsOnSave": {
52+
"source.fixAll.eslint": "explicit"
53+
}
4954
}
5055
}

frontend/src/components/floating-menus/MenuList.svelte

Lines changed: 59 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
3434
// Keep the child references outside of the entries array so as to avoid infinite recursion.
3535
let childReferences: (typeof self)[][] = [];
36-
let search: string | undefined;
36+
let search = "";
3737
3838
let highlighted = activeEntry as MenuListEntry | undefined;
3939
let virtualScrollingEntriesStart = 0;
@@ -46,7 +46,7 @@
4646
});
4747
$: watchHighlightedWithSearch(filteredEntries);
4848
49-
$: filteredEntries = entries.map((section) => section.filter(inSearch(search)));
49+
$: filteredEntries = entries.map((section) => section.filter((entry) => inSearch(search, entry)));
5050
$: virtualScrollingTotalHeight = filteredEntries.length === 0 ? 0 : filteredEntries[0].length * virtualScrollingEntryHeight;
5151
$: virtualScrollingStartIndex = Math.floor(virtualScrollingEntriesStart / virtualScrollingEntryHeight) || 0;
5252
$: virtualScrollingEndIndex = filteredEntries.length === 0 ? 0 : Math.min(filteredEntries[0].length, virtualScrollingStartIndex + 1 + 400 / virtualScrollingEntryHeight);
@@ -64,44 +64,45 @@
6464
}
6565
6666
// Detect when the user types, which creates a search box
67-
async function startSearch(event: KeyboardEvent) {
68-
if (search !== undefined || event.key.length !== 1) return;
67+
async function startSearch(e: KeyboardEvent) {
68+
// Only accept single-character symbol inputs other than space
69+
if (e.key.length !== 1 || e.key === " ") return;
6970
7071
// Stop shortcuts being activated
71-
event.stopPropagation();
72-
event.preventDefault();
72+
e.stopPropagation();
73+
e.preventDefault();
7374
74-
// Open the search bar
75-
search = "";
75+
// Forward the input's first character to the search box, which after that point the user will continue typing into directly
76+
search = e.key;
7677
77-
// Must wait until the DOM elements have been created before focusing the search box
78+
// Must wait until the DOM elements have been created (after the if condition becomes true) before the search box exists
7879
await tick();
79-
searchTextInput?.focus();
80-
81-
// Forward the input's first character to the search box, which after that point the user will continue typing into directly
82-
search = event.key;
8380
8481
// Get the search box element
85-
let searchElement = searchTextInput?.element();
86-
if (!searchElement) return;
82+
const searchElement = searchTextInput?.element();
83+
if (!searchTextInput || !searchElement) return;
8784
88-
// Allow arrow key navigation whilst in the search box
89-
searchElement.onkeydown = (event) => {
90-
if (["Enter", "Escape", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(event.key)) {
91-
keydown(event, false);
85+
// Focus the search box and move the cursor to the end
86+
searchTextInput.focus();
87+
searchElement.setSelectionRange(search.length, search.length);
88+
89+
// Continue listening for keyboard navigation even when the search box is focused
90+
searchElement.onkeydown = (e) => {
91+
if (["Enter", "Escape", "ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"].includes(e.key)) {
92+
keydown(e, false);
9293
}
9394
};
9495
}
9596
96-
function inSearch(search: string | undefined): (entry: MenuListEntry) => boolean {
97-
return (entry: MenuListEntry) => !search || entry.label.toLowerCase().includes(search.toLowerCase());
97+
function inSearch(search: string, entry: MenuListEntry): boolean {
98+
return !search || entry.label.toLowerCase().includes(search.toLowerCase());
9899
}
99100
100101
function watchOpen(open: boolean) {
101102
highlighted = activeEntry;
102103
dispatch("open", open);
103104
104-
search = undefined;
105+
search = "";
105106
}
106107
107108
function watchRemeasureWidth(_: MenuListEntry[][], __: boolean) {
@@ -160,6 +161,23 @@
160161
return getChildReference(menuListEntry)?.open || false;
161162
}
162163
164+
function includeSeparator(entries: MenuListEntry[][], section: MenuListEntry[], sectionIndex: number, search: string): boolean {
165+
const elementsBeforeCurrentSection = entries
166+
.slice(0, sectionIndex)
167+
.flat()
168+
.filter((entry) => inSearch(search, entry));
169+
const entriesInCurrentSection = section.filter((entry) => inSearch(search, entry));
170+
171+
return elementsBeforeCurrentSection.length > 0 && entriesInCurrentSection.length > 0;
172+
}
173+
174+
function currentEntries(section: MenuListEntry[], virtualScrollingEntryHeight: number, virtualScrollingStartIndex: number, virtualScrollingEndIndex: number, search: string) {
175+
if (!virtualScrollingEntryHeight) {
176+
return section.filter((entry) => inSearch(search, entry));
177+
}
178+
return section.filter((entry) => inSearch(search, entry)).slice(virtualScrollingStartIndex, virtualScrollingEndIndex);
179+
}
180+
163181
/// Handles keyboard navigation for the menu. Returns if the entire menu stack should be dismissed
164182
export function keydown(e: KeyboardEvent, submenu: boolean): boolean {
165183
// Interactive menus should keep the active entry the same as the highlighted one
@@ -248,7 +266,7 @@
248266
249267
e.preventDefault();
250268
}
251-
} else if (menuOpen && e.key !== " ") {
269+
} else if (menuOpen && search === "") {
252270
startSearch(e);
253271
}
254272
@@ -305,8 +323,16 @@
305323
scrollableY={scrollableY && virtualScrollingEntryHeight === 0}
306324
bind:this={self}
307325
>
308-
{#if search !== undefined}
309-
<TextInput value={search} on:value={(value) => (search = value.detail)} bind:this={searchTextInput}></TextInput>
326+
{#if search.length > 0}
327+
<TextInput
328+
class="search"
329+
value={search}
330+
on:value={({ detail }) => {
331+
search = detail;
332+
console.log(detail);
333+
}}
334+
bind:this={searchTextInput}
335+
></TextInput>
310336
{/if}
311337
<!-- If we put the scrollableY on the layoutcol for non-font dropdowns then for some reason it always creates a tiny scrollbar.
312338
However when we are using the virtual scrolling then we need the layoutcol to be scrolling so we can bind the events without using `self`. -->
@@ -320,12 +346,10 @@
320346
<LayoutRow class="scroll-spacer" styles={{ height: `${virtualScrollingStartIndex * virtualScrollingEntryHeight}px` }} />
321347
{/if}
322348
{#each entries as section, sectionIndex (sectionIndex)}
323-
{#if entries.slice(undefined, sectionIndex).flat().filter(inSearch(search)).length > 0 && section.filter(inSearch(search)).length > 0}
349+
{#if includeSeparator(entries, section, sectionIndex, search)}
324350
<Separator type="Section" direction="Vertical" />
325351
{/if}
326-
{#each virtualScrollingEntryHeight ? section
327-
.filter(inSearch(search))
328-
.slice(virtualScrollingStartIndex, virtualScrollingEndIndex) : section.filter(inSearch(search)) as entry, entryIndex (entryIndex + startIndex)}
352+
{#each currentEntries(section, virtualScrollingEntryHeight, virtualScrollingStartIndex, virtualScrollingEndIndex, search) as entry, entryIndex (entryIndex + startIndex)}
329353
<LayoutRow
330354
class="row"
331355
classes={{ open: isEntryOpen(entry), active: entry.label === highlighted?.label, disabled: Boolean(entry.disabled) }}
@@ -358,7 +382,6 @@
358382
{/if}
359383

360384
{#if entry.children}
361-
<!-- TODO: Solve the red underline error on the bind:this below -->
362385
<svelte:self
363386
on:naturalWidth
364387
open={getChildReference(entry)?.open || false}
@@ -369,6 +392,7 @@
369392
{scrollableY}
370393
bind:this={childReferences[sectionIndex][entryIndex + startIndex]}
371394
/>
395+
<!-- TODO: Solve the red underline error on the bind:this above -->
372396
{/if}
373397
</LayoutRow>
374398
{/each}
@@ -381,6 +405,11 @@
381405

382406
<style lang="scss" global>
383407
.menu-list {
408+
.search {
409+
margin: 4px;
410+
margin-top: 0;
411+
}
412+
384413
.floating-menu-container .floating-menu-content.floating-menu-content {
385414
padding: 4px 0;
386415

frontend/src/components/widgets/inputs/TextInput.svelte

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717
export let centered = false;
1818
export let minWidth = 0;
1919
20+
let className = "";
21+
export { className as class };
22+
export let classes: Record<string, boolean> = {};
23+
2024
let self: FieldInput | undefined;
2125
let editing = false;
2226
@@ -57,8 +61,8 @@
5761
</script>
5862

5963
<FieldInput
60-
class="text-input"
61-
classes={{ centered }}
64+
class={`text-input ${className}`.trim()}
65+
classes={{ centered, ...classes }}
6266
styles={{ "min-width": minWidth > 0 ? `${minWidth}px` : undefined }}
6367
{value}
6468
on:value

0 commit comments

Comments
 (0)