UNPKG

9.82 kBJavaScriptView Raw
1"use strict";
2// *****************************************************************************
3// Copyright (C) 2018 TypeFox and others.
4//
5// This program and the accompanying materials are made available under the
6// terms of the Eclipse Public License v. 2.0 which is available at
7// http://www.eclipse.org/legal/epl-2.0.
8//
9// This Source Code may also be made available under the following Secondary
10// Licenses when the conditions for such availability set forth in the Eclipse
11// Public License v. 2.0 are satisfied: GNU General Public License, version 2
12// with the GNU Classpath Exception which is available at
13// https://www.gnu.org/software/classpath/license.html.
14//
15// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16// *****************************************************************************
17Object.defineProperty(exports, "__esModule", { value: true });
18exports.TreeSelectionState = exports.FocusableTreeSelection = void 0;
19const tree_iterator_1 = require("./tree-iterator");
20const tree_selection_1 = require("./tree-selection");
21var FocusableTreeSelection;
22(function (FocusableTreeSelection) {
23 /**
24 * `true` if the argument is a focusable tree selection. Otherwise, `false`.
25 */
26 function is(arg) {
27 return tree_selection_1.TreeSelection.is(arg) && 'focus' in arg;
28 }
29 FocusableTreeSelection.is = is;
30 /**
31 * Returns with the tree node that has the focus if the argument is a focusable tree selection.
32 * Otherwise, returns `undefined`.
33 */
34 function focus(arg) {
35 return is(arg) ? arg.focus : undefined;
36 }
37 FocusableTreeSelection.focus = focus;
38})(FocusableTreeSelection = exports.FocusableTreeSelection || (exports.FocusableTreeSelection = {}));
39/**
40 * Class for representing and managing the selection state and the focus of a tree.
41 */
42class TreeSelectionState {
43 constructor(tree, selectionStack = []) {
44 this.tree = tree;
45 this.selectionStack = selectionStack;
46 }
47 nextState(selection) {
48 const { node, type } = {
49 type: tree_selection_1.TreeSelection.SelectionType.DEFAULT,
50 ...selection
51 };
52 switch (type) {
53 case tree_selection_1.TreeSelection.SelectionType.DEFAULT: return this.handleDefault(this, node);
54 case tree_selection_1.TreeSelection.SelectionType.TOGGLE: return this.handleToggle(this, node);
55 case tree_selection_1.TreeSelection.SelectionType.RANGE: return this.handleRange(this, node);
56 default: throw new Error(`Unexpected tree selection type: ${type}.`);
57 }
58 }
59 selection() {
60 const copy = this.checkNoDefaultSelection(this.selectionStack);
61 const nodeIds = new Set();
62 for (let i = 0; i < copy.length; i++) {
63 const { node, type } = copy[i];
64 if (tree_selection_1.TreeSelection.isRange(type)) {
65 const selection = copy[i];
66 for (const id of this.selectionRange(selection).map(n => n.id)) {
67 nodeIds.add(id);
68 }
69 }
70 else if (tree_selection_1.TreeSelection.isToggle(type)) {
71 if (nodeIds.has(node.id)) {
72 nodeIds.delete(node.id);
73 }
74 else {
75 nodeIds.add(node.id);
76 }
77 }
78 }
79 return Array.from(nodeIds.keys()).map(id => this.tree.getNode(id)).filter(tree_selection_1.SelectableTreeNode.is).reverse();
80 }
81 get focus() {
82 var _a;
83 const copy = this.checkNoDefaultSelection(this.selectionStack);
84 const candidate = (_a = copy[copy.length - 1]) === null || _a === void 0 ? void 0 : _a.focus;
85 return this.toSelectableTreeNode(candidate);
86 }
87 get node() {
88 var _a;
89 const copy = this.checkNoDefaultSelection(this.selectionStack);
90 return this.toSelectableTreeNode((_a = copy[copy.length - 1]) === null || _a === void 0 ? void 0 : _a.node);
91 }
92 handleDefault(state, node) {
93 const { tree } = state;
94 return new TreeSelectionState(tree, [{
95 node,
96 type: tree_selection_1.TreeSelection.SelectionType.TOGGLE,
97 focus: node
98 }]);
99 }
100 handleToggle(state, node) {
101 const { tree, selectionStack } = state;
102 const copy = this.checkNoDefaultSelection(selectionStack).slice();
103 const focus = (() => {
104 const allRanges = copy.filter(selection => tree_selection_1.TreeSelection.isRange(selection));
105 for (let i = allRanges.length - 1; i >= 0; i--) {
106 const latestRangeIndex = copy.indexOf(allRanges[i]);
107 const latestRangeSelection = copy[latestRangeIndex];
108 const latestRange = (latestRangeSelection === null || latestRangeSelection === void 0 ? void 0 : latestRangeSelection.focus) ? this.selectionRange(latestRangeSelection) : [];
109 if (latestRange.indexOf(node) !== -1) {
110 if (this.focus === latestRangeSelection.focus) {
111 return latestRangeSelection.focus || node;
112 }
113 else {
114 return this.focus;
115 }
116 }
117 }
118 return node;
119 })();
120 return new TreeSelectionState(tree, [...copy, {
121 node,
122 type: tree_selection_1.TreeSelection.SelectionType.TOGGLE,
123 focus
124 }]);
125 }
126 handleRange(state, node) {
127 const { tree, selectionStack } = state;
128 const copy = this.checkNoDefaultSelection(selectionStack).slice();
129 let focus = FocusableTreeSelection.focus(copy[copy.length - 1]);
130 // Drop the previous range when we are trying to modify that.
131 if (tree_selection_1.TreeSelection.isRange(copy[copy.length - 1])) {
132 const range = this.selectionRange(copy.pop());
133 // And we drop all preceding individual nodes that were contained in the range we are dropping.
134 // That means, anytime we cover individual nodes with a range, they will belong to the range so we need to drop them now.
135 for (let i = copy.length - 1; i >= 0; i--) {
136 if (range.indexOf(copy[i].node) !== -1) {
137 // Make sure to keep a reference to the focus while we are discarding previous elements. Otherwise, we lose this information.
138 focus = copy[i].focus;
139 copy.splice(i, 1);
140 }
141 }
142 }
143 return new TreeSelectionState(tree, [...copy, {
144 node,
145 type: tree_selection_1.TreeSelection.SelectionType.RANGE,
146 focus
147 }]);
148 }
149 /**
150 * Returns with an array of items representing the selection range. The from node is the `focus` the to node
151 * is the selected node itself on the tree selection. Both the `from` node and the `to` node are inclusive.
152 */
153 selectionRange(selection) {
154 const fromNode = selection.focus;
155 const toNode = selection.node;
156 if (fromNode === undefined) {
157 return [];
158 }
159 if (toNode === fromNode) {
160 return [toNode];
161 }
162 const { root } = this.tree;
163 if (root === undefined) {
164 return [];
165 }
166 const to = this.tree.validateNode(toNode);
167 if (to === undefined) {
168 return [];
169 }
170 const from = this.tree.validateNode(fromNode);
171 if (from === undefined) {
172 return [];
173 }
174 let started = false;
175 let finished = false;
176 const range = [];
177 for (const node of new tree_iterator_1.DepthFirstTreeIterator(root, { pruneCollapsed: true })) {
178 if (finished) {
179 break;
180 }
181 // Only collect items which are between (inclusive) the `from` node and the `to` node.
182 if (node === from || node === to) {
183 if (started) {
184 finished = true;
185 }
186 else {
187 started = true;
188 }
189 }
190 if (started) {
191 range.push(node);
192 }
193 }
194 // We need to reverse the selection range order.
195 if (range.indexOf(from) > range.indexOf(to)) {
196 range.reverse();
197 }
198 return range.filter(tree_selection_1.SelectableTreeNode.is);
199 }
200 toSelectableTreeNode(node) {
201 if (!!node) {
202 const candidate = this.tree.getNode(node.id);
203 if (!!candidate) {
204 if (tree_selection_1.SelectableTreeNode.is(candidate)) {
205 return candidate;
206 }
207 else {
208 console.warn(`Could not map to a selectable tree node. Node with ID: ${node.id} is not a selectable node.`);
209 }
210 }
211 else {
212 console.warn(`Could not map to a selectable tree node. Node does not exist with ID: ${node.id}.`);
213 }
214 }
215 return undefined;
216 }
217 /**
218 * Checks whether the argument contains any `DEFAULT` tree selection type. If yes, throws an error, otherwise returns with a reference the argument.
219 */
220 checkNoDefaultSelection(selections) {
221 if (selections.some(selection => selection.type === undefined || selection.type === tree_selection_1.TreeSelection.SelectionType.DEFAULT)) {
222 throw new Error(`Unexpected DEFAULT selection type. [${selections.map(selection => `ID: ${selection.node.id} | ${selection.type}`).join(', ')}]`);
223 }
224 return selections;
225 }
226}
227exports.TreeSelectionState = TreeSelectionState;
228//# sourceMappingURL=tree-selection-state.js.map
\No newline at end of file