File size: 2,571 Bytes
fc7156e
5427205
 
 
fc7156e
5427205
 
aa0792f
5427205
 
dc3ebef
942065e
5427205
 
 
4237e78
fc7156e
5427205
 
 
 
 
 
 
 
05acf81
5427205
 
 
 
05acf81
 
3010d5b
 
05acf81
 
 
 
 
 
 
5427205
fc7156e
 
3010d5b
5427205
 
 
 
05acf81
5427205
c20da94
 
 
 
 
 
 
 
 
942065e
c20da94
 
 
fc7156e
 
 
5427205
 
 
 
 
 
 
 
 
 
05acf81
5427205
 
f45aace
5427205
 
fc7156e
3010d5b
fc7156e
 
5427205
fc7156e
 
 
c20da94
 
 
 
 
 
 
fc7156e
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
<script lang="ts">
  import { createEventDispatcher, onMount } from 'svelte';
  import Fuse from 'fuse.js'
  const dispatch = createEventDispatcher();
  export let pos;
  export let boxes;
  let searchBox: HTMLInputElement;
  let hits = Object.values(boxes).map(box => ({item: box}));
  let selectedIndex = 0;
  onMount(() => searchBox.focus());
  $: fuse = new Fuse(Object.values(boxes), {
    keys: ['name']
  })
  function onInput() {
    hits = fuse.search(searchBox.value);
    selectedIndex = Math.max(0, Math.min(selectedIndex, hits.length - 1));
  }
  function onKeyDown(e) {
    if (e.key === 'ArrowDown') {
      e.preventDefault();
      selectedIndex = Math.min(selectedIndex + 1, hits.length - 1);
    } else if (e.key === 'ArrowUp') {
      e.preventDefault();
      selectedIndex = Math.max(selectedIndex - 1, 0);
    } else if (e.key === 'Enter') {
      addSelected();
    } else if (e.key === 'Escape') {
      dispatch('cancel');
    }
  }
  function addSelected() {
    const node = {...hits[selectedIndex].item};
    delete node.sub_nodes;
    node.position = pos;
    dispatch('add', node);
  }
  async function lostFocus(e) {
    // If it's a click on a result, let the click handler handle it.
    if (e.relatedTarget && e.relatedTarget.closest('.node-search')) return;
    dispatch('cancel');
  }

</script>

<div class="node-search" style="top: {pos.y}px; left: {pos.x}px;">
  <input
    bind:this={searchBox}
    on:input={onInput}
    on:keydown={onKeyDown}
    on:focusout={lostFocus}
    placeholder="Search for box">
  <div class="matches">
    {#each hits as box, index}
      <div
        tabindex="0"
        on:focus={() => selectedIndex = index}
        on:mouseenter={() => selectedIndex = index}
        on:click={addSelected}
        class="search-result"
        class:selected={index == selectedIndex}>
        {box.item.name}
      </div>
    {/each}
  </div>
</div>

<style>
  input {
    width: calc(100% - 26px);
    font-size: 20px;
    padding: 8px;
    border-radius: 4px;
    border: 1px solid #eee;
    margin: 4px;
  }
  .search-result {
    padding: 4px;
    cursor: pointer;
  }
  .search-result.selected {
    background-color: oklch(75% 0.2 55);
    border-radius: 4px;
  }
  .node-search {
    position: fixed;
    width: 300px;
    z-index: 5;
    padding: 4px;
    border-radius: 4px;
    border: 1px solid #888;
    background-color: white;
    max-height: -webkit-fill-available;
    max-height: -moz-available;
    display: flex;
    flex-direction: column;
  }
  .matches {
    overflow-y: auto;
  }
</style>