aboutsummaryrefslogtreecommitdiff
path: root/src/console/components/console/Completion.tsx
blob: ed271aa375ed8c0c4078408e99caa319094ecce7 (plain) (blame)
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
102
103
import React from "react";
import CompletionItem from "./CompletionItem";
import CompletionTitle from "./CompletionTitle";

interface Item {
  icon?: string;
  caption?: string;
  url?: string;
}

interface Group {
  name: string;
  items: Item[];
}

interface Props {
  select: number;
  size: number;
  completions: Group[];
}

const Completion: React.FC<Props> = ({ select, size, completions }) => {
  const [viewOffset, setViewOffset] = React.useState(0);
  const [prevSelect, setPrevSelect] = React.useState(-1);

  React.useEffect(() => {
    if (select === prevSelect) {
      return;
    }

    const viewSelect = (() => {
      let index = 0;
      for (let i = 0; i < completions.length; ++i) {
        ++index;
        const g = completions[i];
        if (select + i + 1 < index + g.items.length) {
          return select + i + 1;
        }
        index += g.items.length;
      }
      return -1;
    })();

    const nextViewOffset = (() => {
      if (prevSelect < select) {
        return Math.max(viewOffset, viewSelect - size + 1);
      } else if (prevSelect > select) {
        return Math.min(viewOffset, viewSelect);
      }
      return 0;
    })();

    setPrevSelect(select);
    setViewOffset(nextViewOffset);
  }, [select]);

  let itemIndex = 0;
  let viewIndex = 0;
  const groups: Array<JSX.Element> = [];

  completions.forEach((group, groupIndex) => {
    const items = [];
    const title = (
      <CompletionTitle
        id={`title-${groupIndex}`}
        key={`group-${groupIndex}`}
        shown={viewOffset <= viewIndex && viewIndex < viewOffset + size}
        title={group.name}
      />
    );
    ++viewIndex;
    for (const item of group.items) {
      items.push(
        <CompletionItem
          shown={viewOffset <= viewIndex && viewIndex < viewOffset + size}
          key={`item-${itemIndex}`}
          icon={item.icon}
          caption={item.caption}
          url={item.url}
          highlight={itemIndex === select}
          aria-selected={itemIndex === select}
          role="menuitem"
        />
      );
      ++viewIndex;
      ++itemIndex;
    }
    groups.push(
      <div
        key={`group-${groupIndex}`}
        role="group"
        aria-describedby={`title-${groupIndex}`}
      >
        {title}
        <ul>{items}</ul>
      </div>
    );
  });

  return <div role="menu">{groups}</div>;
};

export default Completion;