aboutsummaryrefslogtreecommitdiff
path: root/src/console/components/console/Completion.tsx
blob: 09ae278a8083bad0aa4881005d502335f325e3a9 (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
104
105
106
107
108
109
110
111
112
113
114
115
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[];
}

interface State {
  viewOffset: number;
  select: number;
}

class Completion extends React.Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { viewOffset: 0, select: -1 };
  }

  static getDerivedStateFromProps(nextProps: Props, prevState: State) {
    if (prevState.select === nextProps.select) {
      return null;
    }

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

    let viewOffset = 0;
    if (nextProps.select < 0) {
      viewOffset = 0;
    } else if (prevState.select < nextProps.select) {
      viewOffset = Math.max(
        prevState.viewOffset,
        viewSelect - nextProps.size + 1
      );
    } else if (prevState.select > nextProps.select) {
      viewOffset = Math.min(prevState.viewOffset, viewSelect);
    }
    return { viewOffset, select: nextProps.select };
  }

  render() {
    let itemIndex = 0;
    let viewIndex = 0;
    const groups: Array<JSX.Element> = [];
    const viewOffset = this.state.viewOffset;
    const viewSize = this.props.size;

    this.props.completions.forEach((group, groupIndex) => {
      const items = [];
      const title = (
        <CompletionTitle
          id={`title-${groupIndex}`}
          key={`group-${groupIndex}`}
          shown={viewOffset <= viewIndex && viewIndex < viewOffset + viewSize}
          title={group.name}
        />
      );
      ++viewIndex;
      for (const item of group.items) {
        items.push(
          <CompletionItem
            shown={viewOffset <= viewIndex && viewIndex < viewOffset + viewSize}
            key={`item-${itemIndex}`}
            icon={item.icon}
            caption={item.caption}
            url={item.url}
            highlight={itemIndex === this.props.select}
            aria-selected={itemIndex === this.props.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;