1 | export default class Selection {
|
2 | constructor({
|
3 | data = [],
|
4 | selected = new Set(),
|
5 | focused = null,
|
6 | getKey = item => item.id,
|
7 | getChildren = () => [],
|
8 | isItemSelectable = () => true
|
9 | } = {}) {
|
10 | this._rawData = data;
|
11 | this._getChildren = getChildren;
|
12 |
|
13 | this._data = this._buildData(data);
|
14 |
|
15 | this._selected = selected;
|
16 | this._focused = focused;
|
17 | this._getKey = getKey;
|
18 | this._isItemSelectable = isItemSelectable;
|
19 | }
|
20 |
|
21 | _buildData(data) {
|
22 | return new Set(data);
|
23 | }
|
24 |
|
25 | _buildSelected(data, selected) {
|
26 | return new Set(selected);
|
27 | }
|
28 |
|
29 | cloneWith({data, selected, focused}) {
|
30 | const newData = data || this._rawData;
|
31 |
|
32 | let newSelected;
|
33 | if (data && !selected) {
|
34 | newSelected = new Set([...this._buildData(newData)].
|
35 | filter(item => [...this._selected].some(it => this._getKey(item) === this._getKey(it))));
|
36 | newSelected = this._buildSelected(this._buildData(newData), newSelected);
|
37 | } else if (selected) {
|
38 | newSelected = selected;
|
39 | } else {
|
40 | newSelected = this._selected;
|
41 | }
|
42 | newSelected = new Set([...newSelected].filter(item => this._isItemSelectable(item)));
|
43 |
|
44 | const cloneFocus = () => [...this._buildData(data)].filter(
|
45 | item => this._focused && this._getKey(item) === this._getKey(this._focused)
|
46 | )[0];
|
47 |
|
48 | const newFocused = focused === undefined ? this._focused : focused;
|
49 |
|
50 | return new this.constructor({
|
51 | data: newData,
|
52 | selected: newSelected,
|
53 | focused: (data && !focused) ? cloneFocus() : newFocused,
|
54 | getKey: this._getKey,
|
55 | getChildren: this._getChildren,
|
56 | isItemSelectable: this._isItemSelectable
|
57 | });
|
58 | }
|
59 |
|
60 | focus(value) {
|
61 | return this.cloneWith({focused: value});
|
62 | }
|
63 |
|
64 | moveUp() {
|
65 | const focused = this._focused;
|
66 | const data = [...this._data];
|
67 |
|
68 | if (!focused) {
|
69 | return this.cloneWith({focused: data[data.length - 1]});
|
70 | }
|
71 |
|
72 | const nextItem = data[data.indexOf(focused) - 1];
|
73 | if (nextItem) {
|
74 | return this.cloneWith({focused: nextItem});
|
75 | }
|
76 |
|
77 | return undefined;
|
78 | }
|
79 |
|
80 | moveDown() {
|
81 | const focused = this._focused;
|
82 | const data = [...this._data];
|
83 |
|
84 | if (!focused) {
|
85 | return this.cloneWith({focused: data[0]});
|
86 | }
|
87 |
|
88 | const nextItem = data[data.indexOf(focused) + 1];
|
89 | if (nextItem) {
|
90 | return this.cloneWith({focused: nextItem});
|
91 | }
|
92 |
|
93 | return undefined;
|
94 | }
|
95 |
|
96 | moveStart() {
|
97 | const data = [...this._data];
|
98 |
|
99 | if (data.length) {
|
100 | return this.cloneWith({focused: data[0]});
|
101 | }
|
102 |
|
103 | return undefined;
|
104 | }
|
105 |
|
106 | moveEnd() {
|
107 | const data = [...this._data];
|
108 |
|
109 | if (data.length) {
|
110 | return this.cloneWith({focused: data.pop()});
|
111 | }
|
112 |
|
113 | return undefined;
|
114 | }
|
115 |
|
116 | select(value = this._focused) {
|
117 | if (!value || !this._isItemSelectable(value)) {
|
118 | return this;
|
119 | }
|
120 |
|
121 | const selected = new Set(this._selected);
|
122 | selected.add(value);
|
123 | return this.cloneWith({selected});
|
124 | }
|
125 |
|
126 | deselect(value = this._focused) {
|
127 | if (!value || !this._isItemSelectable(value)) {
|
128 | return this;
|
129 | }
|
130 |
|
131 | const selected = new Set(this._selected);
|
132 | selected.delete(value);
|
133 | return this.cloneWith({selected});
|
134 | }
|
135 |
|
136 | toggleSelection(value = this._focused) {
|
137 | if (this.isSelected(value)) {
|
138 | return this.deselect(value);
|
139 | } else {
|
140 | return this.select(value);
|
141 | }
|
142 | }
|
143 |
|
144 | selectAll() {
|
145 | return this.cloneWith({selected: [...this._data]});
|
146 | }
|
147 |
|
148 | resetFocus() {
|
149 | return this.cloneWith({focused: null});
|
150 | }
|
151 |
|
152 | resetSelection() {
|
153 | return this.cloneWith({selected: new Set()});
|
154 | }
|
155 |
|
156 | reset() {
|
157 | return this.resetFocus().resetSelection();
|
158 | }
|
159 |
|
160 | isFocused(value) {
|
161 | return this._focused === value;
|
162 | }
|
163 |
|
164 | isSelected(value) {
|
165 | return this._selected.has(value);
|
166 | }
|
167 |
|
168 | getFocused() {
|
169 | return this._focused;
|
170 | }
|
171 |
|
172 | getSelected() {
|
173 | return new Set(this._selected);
|
174 | }
|
175 |
|
176 | getActive() {
|
177 | if (this._selected.size) {
|
178 | return new Set(this._selected);
|
179 | } else if (this._focused) {
|
180 | return new Set([this._focused]);
|
181 | } else {
|
182 | return new Set();
|
183 | }
|
184 | }
|
185 | }
|