UNPKG

52.8 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// *****************************************************************************
17var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
18 var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
19 if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
20 else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
21 return c > 3 && r && Object.defineProperty(target, key, r), r;
22};
23var __metadata = (this && this.__metadata) || function (k, v) {
24 if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
25};
26var __param = (this && this.__param) || function (paramIndex, decorator) {
27 return function (target, key) { decorator(target, key, paramIndex); }
28};
29var TreeWidget_1;
30Object.defineProperty(exports, "__esModule", { value: true });
31exports.TreeWidget = exports.defaultTreeProps = exports.TreeProps = exports.TREE_NODE_INDENT_GUIDE_CLASS = exports.TREE_NODE_CAPTION_CLASS = exports.COMPOSITE_TREE_NODE_CLASS = exports.EXPANDABLE_TREE_NODE_CLASS = exports.TREE_NODE_SEGMENT_GROW_CLASS = exports.TREE_NODE_SEGMENT_CLASS = exports.TREE_NODE_TAIL_CLASS = exports.TREE_NODE_INFO_CLASS = exports.TREE_NODE_CONTENT_CLASS = exports.TREE_NODE_CLASS = exports.TREE_CONTAINER_CLASS = exports.TREE_CLASS = void 0;
32const inversify_1 = require("inversify");
33const common_1 = require("../../common");
34const keys_1 = require("../keyboard/keys");
35const context_menu_renderer_1 = require("../context-menu-renderer");
36const widgets_1 = require("../widgets");
37const tree_1 = require("./tree");
38const tree_model_1 = require("./tree-model");
39const tree_expansion_1 = require("./tree-expansion");
40const tree_selection_1 = require("./tree-selection");
41const tree_decorator_1 = require("./tree-decorator");
42const objects_1 = require("../../common/objects");
43const os_1 = require("../../common/os");
44const react_widget_1 = require("../widgets/react-widget");
45const React = require("react");
46const react_virtuoso_1 = require("react-virtuoso");
47const tree_iterator_1 = require("./tree-iterator");
48const search_box_1 = require("./search-box");
49const tree_search_1 = require("./tree-search");
50const domutils_1 = require("@phosphor/domutils");
51const tree_widget_selection_1 = require("./tree-widget-selection");
52const label_provider_1 = require("../label-provider");
53const core_preferences_1 = require("../core-preferences");
54const tree_focus_service_1 = require("./tree-focus-service");
55const react_1 = require("react");
56const debounce = require('lodash.debounce');
57exports.TREE_CLASS = 'theia-Tree';
58exports.TREE_CONTAINER_CLASS = 'theia-TreeContainer';
59exports.TREE_NODE_CLASS = 'theia-TreeNode';
60exports.TREE_NODE_CONTENT_CLASS = 'theia-TreeNodeContent';
61exports.TREE_NODE_INFO_CLASS = 'theia-TreeNodeInfo';
62exports.TREE_NODE_TAIL_CLASS = 'theia-TreeNodeTail';
63exports.TREE_NODE_SEGMENT_CLASS = 'theia-TreeNodeSegment';
64exports.TREE_NODE_SEGMENT_GROW_CLASS = 'theia-TreeNodeSegmentGrow';
65exports.EXPANDABLE_TREE_NODE_CLASS = 'theia-ExpandableTreeNode';
66exports.COMPOSITE_TREE_NODE_CLASS = 'theia-CompositeTreeNode';
67exports.TREE_NODE_CAPTION_CLASS = 'theia-TreeNodeCaption';
68exports.TREE_NODE_INDENT_GUIDE_CLASS = 'theia-tree-node-indent';
69exports.TreeProps = Symbol('TreeProps');
70/**
71 * The default tree properties.
72 */
73exports.defaultTreeProps = {
74 leftPadding: 8,
75 expansionTogglePadding: 22
76};
77let TreeWidget = TreeWidget_1 = class TreeWidget extends react_widget_1.ReactWidget {
78 constructor(props, model, contextMenuRenderer) {
79 super();
80 this.props = props;
81 this.model = model;
82 this.contextMenuRenderer = contextMenuRenderer;
83 this.decorations = new Map();
84 this.shouldScrollToRow = true;
85 this.rows = new Map();
86 this.updateRows = debounce(() => this.doUpdateRows(), 10);
87 this.scheduleUpdateScrollToRow = debounce(this.updateScrollToRow);
88 /**
89 * Update tree decorations.
90 * - Updating decorations are debounced in order to limit the number of expensive updates.
91 */
92 this.updateDecorations = debounce(() => this.doUpdateDecorations(), 150);
93 this.ScrollingRowRenderer = ({ rows }) => {
94 (0, react_1.useEffect)(() => this.scrollToSelected());
95 return React.createElement(React.Fragment, null, rows.map(row => React.createElement("div", { key: row.index }, this.renderNodeRow(row))));
96 };
97 this.scrollArea = this.node;
98 /**
99 * Render the node row.
100 */
101 this.renderNodeRow = (row) => this.doRenderNodeRow(row);
102 /**
103 * Toggle the node.
104 */
105 this.toggle = (event) => this.doToggle(event);
106 /**
107 * Handle the double-click mouse event on the expansion toggle.
108 */
109 this.handleExpansionToggleDblClickEvent = (event) => this.doHandleExpansionToggleDblClickEvent(event);
110 this.scrollOptions = {
111 suppressScrollX: true,
112 minScrollbarLength: 35
113 };
114 this.addClass(exports.TREE_CLASS);
115 this.node.tabIndex = 0;
116 }
117 init() {
118 if (this.props.search) {
119 this.searchBox = this.searchBoxFactory({ ...search_box_1.SearchBoxProps.DEFAULT, showButtons: true, showFilter: true });
120 this.searchBox.node.addEventListener('focus', () => {
121 this.node.focus();
122 });
123 this.toDispose.pushAll([
124 this.searchBox,
125 this.searchBox.onTextChange(async (data) => {
126 await this.treeSearch.filter(data);
127 this.searchHighlights = this.treeSearch.getHighlights();
128 this.searchBox.updateHighlightInfo({
129 filterText: data,
130 total: this.rows.size,
131 matched: this.searchHighlights.size
132 });
133 this.update();
134 }),
135 this.searchBox.onClose(data => this.treeSearch.filter(undefined)),
136 this.searchBox.onNext(() => {
137 // Enable next selection if there are currently highlights.
138 if (this.searchHighlights.size > 1) {
139 this.model.selectNextNode();
140 }
141 }),
142 this.searchBox.onPrevious(() => {
143 // Enable previous selection if there are currently highlights.
144 if (this.searchHighlights.size > 1) {
145 this.model.selectPrevNode();
146 }
147 }),
148 this.searchBox.onFilterToggled(e => {
149 this.updateRows();
150 }),
151 this.treeSearch,
152 this.treeSearch.onFilteredNodesChanged(nodes => {
153 if (this.searchBox.isFiltering) {
154 this.updateRows();
155 }
156 const node = nodes.find(tree_selection_1.SelectableTreeNode.is);
157 if (node) {
158 this.model.selectNode(node);
159 }
160 }),
161 ]);
162 }
163 this.node.addEventListener('mousedown', this.handleMiddleClickEvent.bind(this));
164 this.node.addEventListener('mouseup', this.handleMiddleClickEvent.bind(this));
165 this.node.addEventListener('auxclick', this.handleMiddleClickEvent.bind(this));
166 this.toDispose.pushAll([
167 this.model,
168 this.model.onChanged(() => this.updateRows()),
169 this.model.onSelectionChanged(() => this.scheduleUpdateScrollToRow({ resize: false })),
170 this.focusService.onDidChangeFocus(() => this.scheduleUpdateScrollToRow({ resize: false })),
171 this.model.onDidChangeBusy(() => this.update()),
172 this.model.onDidUpdate(() => this.update()),
173 this.model.onNodeRefreshed(() => this.updateDecorations()),
174 this.model.onExpansionChanged(() => this.updateDecorations()),
175 this.decoratorService,
176 this.decoratorService.onDidChangeDecorations(() => this.updateDecorations()),
177 this.labelProvider.onDidChange(e => {
178 for (const row of this.rows.values()) {
179 if (e.affects(row)) {
180 this.update();
181 return;
182 }
183 }
184 })
185 ]);
186 setTimeout(() => {
187 this.updateRows();
188 this.updateDecorations();
189 });
190 if (this.props.globalSelection) {
191 this.toDispose.pushAll([
192 this.model.onSelectionChanged(() => {
193 if (this.node.contains(document.activeElement)) {
194 this.updateGlobalSelection();
195 }
196 }),
197 this.focusService.onDidChangeFocus(focus => {
198 if (focus && this.node.contains(document.activeElement) && this.model.selectedNodes[0] !== focus && this.model.selectedNodes.includes(focus)) {
199 this.updateGlobalSelection();
200 }
201 }),
202 common_1.Disposable.create(() => {
203 const selection = this.selectionService.selection;
204 if (tree_widget_selection_1.TreeWidgetSelection.isSource(selection, this)) {
205 this.selectionService.selection = undefined;
206 }
207 })
208 ]);
209 }
210 this.toDispose.push(this.corePreferences.onPreferenceChanged(preference => {
211 if (preference.preferenceName === 'workbench.tree.renderIndentGuides') {
212 this.update();
213 }
214 }));
215 }
216 /**
217 * Update the global selection for the tree.
218 */
219 updateGlobalSelection() {
220 this.selectionService.selection = tree_widget_selection_1.TreeWidgetSelection.create(this);
221 }
222 doUpdateRows() {
223 const root = this.model.root;
224 const rowsToUpdate = [];
225 if (root) {
226 const depths = new Map();
227 let index = 0;
228 for (const node of new tree_iterator_1.TopDownTreeIterator(root, {
229 pruneCollapsed: true,
230 pruneSiblings: true
231 })) {
232 if (this.shouldDisplayNode(node)) {
233 const depth = this.getDepthForNode(node, depths);
234 if (tree_1.CompositeTreeNode.is(node)) {
235 depths.set(node, depth);
236 }
237 rowsToUpdate.push([node.id, this.toNodeRow(node, index++, depth)]);
238 }
239 }
240 }
241 this.rows = new Map(rowsToUpdate);
242 this.updateScrollToRow();
243 }
244 getDepthForNode(node, depths) {
245 const parentDepth = depths.get(node.parent);
246 return parentDepth === undefined ? 0 : tree_1.TreeNode.isVisible(node.parent) ? parentDepth + 1 : parentDepth;
247 }
248 toNodeRow(node, index, depth) {
249 return { node, index, depth };
250 }
251 shouldDisplayNode(node) {
252 var _a;
253 return tree_1.TreeNode.isVisible(node) && (!((_a = this.searchBox) === null || _a === void 0 ? void 0 : _a.isFiltering) || this.treeSearch.passesFilters(node));
254 }
255 /**
256 * Update the `scrollToRow`.
257 * @param updateOptions the tree widget force update options.
258 */
259 updateScrollToRow() {
260 this.scrollToRow = this.getScrollToRow();
261 this.update();
262 }
263 /**
264 * Get the `scrollToRow`.
265 *
266 * @returns the `scrollToRow` if available.
267 */
268 getScrollToRow() {
269 var _a;
270 if (!this.shouldScrollToRow) {
271 return undefined;
272 }
273 const { focusedNode } = this.focusService;
274 return focusedNode && ((_a = this.rows.get(focusedNode.id)) === null || _a === void 0 ? void 0 : _a.index);
275 }
276 async doUpdateDecorations() {
277 this.decorations = await this.decoratorService.getDecorations(this.model);
278 this.update();
279 }
280 onActivateRequest(msg) {
281 super.onActivateRequest(msg);
282 this.node.focus({ preventScroll: true });
283 }
284 /**
285 * Actually focus the tree node.
286 */
287 doFocus() {
288 if (!this.model.selectedNodes.length) {
289 const node = this.getNodeToFocus();
290 if (tree_selection_1.SelectableTreeNode.is(node)) {
291 this.model.selectNode(node);
292 }
293 }
294 }
295 /**
296 * Get the tree node to focus.
297 *
298 * @returns the node to focus if available.
299 */
300 getNodeToFocus() {
301 const { focusedNode } = this.focusService;
302 if (focusedNode) {
303 return focusedNode;
304 }
305 const { root } = this.model;
306 if (tree_selection_1.SelectableTreeNode.isVisible(root)) {
307 return root;
308 }
309 return this.model.getNextSelectableNode(root);
310 }
311 onUpdateRequest(msg) {
312 if (!this.isAttached || !this.isVisible) {
313 return;
314 }
315 super.onUpdateRequest(msg);
316 }
317 onResize(msg) {
318 super.onResize(msg);
319 this.update();
320 }
321 render() {
322 return React.createElement('div', this.createContainerAttributes(), this.renderTree(this.model));
323 }
324 /**
325 * Create the container attributes for the widget.
326 */
327 createContainerAttributes() {
328 const classNames = [exports.TREE_CONTAINER_CLASS];
329 if (!this.rows.size) {
330 classNames.push('empty');
331 }
332 if (this.model.selectedNodes.length === 0 && !this.focusService.focusedNode) {
333 classNames.push('focused');
334 }
335 return {
336 className: classNames.join(' '),
337 onContextMenu: event => this.handleContextMenuEvent(this.getContainerTreeNode(), event)
338 };
339 }
340 /**
341 * Get the container tree node.
342 *
343 * @returns the tree node for the container if available.
344 */
345 getContainerTreeNode() {
346 return this.model.root;
347 }
348 /**
349 * Render the tree widget.
350 * @param model the tree model.
351 */
352 renderTree(model) {
353 if (model.root) {
354 const rows = Array.from(this.rows.values());
355 if (this.props.virtualized === false) {
356 return React.createElement(this.ScrollingRowRenderer, { rows: rows });
357 }
358 return React.createElement(TreeWidget_1.View, { ref: view => this.view = (view || undefined), width: this.node.offsetWidth, height: this.node.offsetHeight, rows: rows, renderNodeRow: this.renderNodeRow, scrollToRow: this.scrollToRow });
359 }
360 // eslint-disable-next-line no-null/no-null
361 return null;
362 }
363 /**
364 * Scroll to the selected tree node.
365 */
366 scrollToSelected() {
367 if (this.props.scrollIfActive === true && !this.node.contains(document.activeElement)) {
368 return;
369 }
370 const focus = this.node.getElementsByClassName(widgets_1.FOCUS_CLASS)[0];
371 if (focus) {
372 domutils_1.ElementExt.scrollIntoViewIfNeeded(this.scrollArea, focus);
373 }
374 else {
375 const selected = this.node.getElementsByClassName(widgets_1.SELECTED_CLASS)[0];
376 if (selected) {
377 domutils_1.ElementExt.scrollIntoViewIfNeeded(this.scrollArea, selected);
378 }
379 }
380 }
381 /**
382 * Actually render the node row.
383 */
384 doRenderNodeRow({ node, depth }) {
385 return React.createElement(React.Fragment, null,
386 this.renderIndent(node, { depth }),
387 this.renderNode(node, { depth }));
388 }
389 /**
390 * Render the tree node given the node properties.
391 * @param node the tree node.
392 * @param props the node properties.
393 */
394 renderIcon(node, props) {
395 // eslint-disable-next-line no-null/no-null
396 return null;
397 }
398 /**
399 * Actually toggle the tree node.
400 * @param event the mouse click event.
401 */
402 doToggle(event) {
403 const nodeId = event.currentTarget.getAttribute('data-node-id');
404 if (nodeId) {
405 const node = this.model.getNode(nodeId);
406 if (node && this.props.expandOnlyOnExpansionToggleClick) {
407 if (this.isExpandable(node) && !this.hasShiftMask(event) && !this.hasCtrlCmdMask(event)) {
408 this.model.toggleNodeExpansion(node);
409 }
410 }
411 else {
412 this.handleClickEvent(node, event);
413 }
414 }
415 event.stopPropagation();
416 }
417 /**
418 * Render the node expansion toggle.
419 * @param node the tree node.
420 * @param props the node properties.
421 */
422 renderExpansionToggle(node, props) {
423 if (!this.isExpandable(node)) {
424 // eslint-disable-next-line no-null/no-null
425 return null;
426 }
427 const classes = [exports.TREE_NODE_SEGMENT_CLASS, widgets_1.EXPANSION_TOGGLE_CLASS];
428 if (!node.expanded) {
429 classes.push(widgets_1.COLLAPSED_CLASS);
430 }
431 if (node.busy) {
432 classes.push(widgets_1.BUSY_CLASS, ...widgets_1.CODICON_LOADING_CLASSES);
433 }
434 else {
435 classes.push(...widgets_1.CODICON_TREE_ITEM_CLASSES);
436 }
437 const className = classes.join(' ');
438 return React.createElement("div", { "data-node-id": node.id, className: className, onClick: this.toggle, onDoubleClick: this.handleExpansionToggleDblClickEvent });
439 }
440 /**
441 * Render the node expansion toggle.
442 * @param node the tree node.
443 * @param props the node properties.
444 */
445 renderCheckbox(node, props) {
446 var _a, _b;
447 if (node.checkboxInfo === undefined) {
448 // eslint-disable-next-line no-null/no-null
449 return null;
450 }
451 return React.createElement("input", { "data-node-id": node.id, readOnly: true, type: 'checkbox', checked: !!node.checkboxInfo.checked, title: node.checkboxInfo.tooltip, "aria-label": (_a = node.checkboxInfo.accessibilityInformation) === null || _a === void 0 ? void 0 : _a.label, role: (_b = node.checkboxInfo.accessibilityInformation) === null || _b === void 0 ? void 0 : _b.role, className: 'theia-input', onClick: event => this.toggleChecked(event) });
452 }
453 toggleChecked(event) {
454 const nodeId = event.currentTarget.getAttribute('data-node-id');
455 if (nodeId) {
456 const node = this.model.getNode(nodeId);
457 if (node) {
458 this.model.markAsChecked(node, !node.checkboxInfo.checked);
459 }
460 else {
461 this.handleClickEvent(node, event);
462 }
463 }
464 event.preventDefault();
465 event.stopPropagation();
466 }
467 /**
468 * Render the tree node caption given the node properties.
469 * @param node the tree node.
470 * @param props the node properties.
471 */
472 renderCaption(node, props) {
473 const attrs = this.getCaptionAttributes(node, props);
474 const children = this.getCaptionChildren(node, props);
475 return React.createElement('div', attrs, children);
476 }
477 getCaptionAttributes(node, props) {
478 const tooltip = this.getDecorationData(node, 'tooltip').filter(objects_1.notEmpty).join(' • ');
479 const classes = [exports.TREE_NODE_SEGMENT_CLASS];
480 if (!this.hasTrailingSuffixes(node)) {
481 classes.push(exports.TREE_NODE_SEGMENT_GROW_CLASS);
482 }
483 const className = classes.join(' ');
484 let attrs = this.decorateCaption(node, {
485 className, id: node.id
486 });
487 if (tooltip.length > 0) {
488 attrs = {
489 ...attrs,
490 title: tooltip
491 };
492 }
493 return attrs;
494 }
495 getCaptionChildren(node, props) {
496 const children = [];
497 const caption = this.toNodeName(node);
498 const highlight = this.getDecorationData(node, 'highlight')[0];
499 if (highlight) {
500 children.push(this.toReactNode(caption, highlight));
501 }
502 const searchHighlight = this.searchHighlights ? this.searchHighlights.get(node.id) : undefined;
503 if (searchHighlight) {
504 children.push(...this.toReactNode(caption, searchHighlight));
505 }
506 else if (!highlight) {
507 children.push(caption);
508 }
509 return children;
510 }
511 /**
512 * Update the node given the caption and highlight.
513 * @param caption the caption.
514 * @param highlight the tree decoration caption highlight.
515 */
516 toReactNode(caption, highlight) {
517 let style = {};
518 if (highlight.color) {
519 style = {
520 ...style,
521 color: highlight.color
522 };
523 }
524 if (highlight.backgroundColor) {
525 style = {
526 ...style,
527 backgroundColor: highlight.backgroundColor
528 };
529 }
530 const createChildren = (fragment, index) => {
531 const { data } = fragment;
532 if (fragment.highlight) {
533 return React.createElement("mark", { className: tree_decorator_1.TreeDecoration.Styles.CAPTION_HIGHLIGHT_CLASS, style: style, key: index }, data);
534 }
535 else {
536 return data;
537 }
538 };
539 return tree_decorator_1.TreeDecoration.CaptionHighlight.split(caption, highlight).map(createChildren);
540 }
541 /**
542 * Decorate the tree caption.
543 * @param node the tree node.
544 * @param attrs the additional attributes.
545 */
546 decorateCaption(node, attrs) {
547 const style = this.getDecorationData(node, 'fontData')
548 .filter(objects_1.notEmpty)
549 .reverse()
550 .map(fontData => this.applyFontStyles({}, fontData))
551 .reduce((acc, current) => ({
552 ...acc,
553 ...current
554 }), {});
555 return {
556 ...attrs,
557 style
558 };
559 }
560 /**
561 * Determine if the tree node contains trailing suffixes.
562 * @param node the tree node.
563 *
564 * @returns `true` if the tree node contains trailing suffices.
565 */
566 hasTrailingSuffixes(node) {
567 return this.getDecorationData(node, 'captionSuffixes').filter(objects_1.notEmpty).reduce((acc, current) => acc.concat(current), []).length > 0;
568 }
569 /**
570 * Apply font styles to the tree.
571 * @param original the original css properties.
572 * @param fontData the optional `fontData`.
573 */
574 applyFontStyles(original, fontData) {
575 if (fontData === undefined) {
576 return original;
577 }
578 const modified = { ...original }; // make a copy to mutate
579 const { color, style } = fontData;
580 if (color) {
581 modified.color = color;
582 }
583 if (style) {
584 (Array.isArray(style) ? style : [style]).forEach(s => {
585 switch (s) {
586 case 'bold':
587 modified.fontWeight = s;
588 break;
589 case 'normal':
590 case 'oblique':
591 case 'italic':
592 modified.fontStyle = s;
593 break;
594 case 'underline':
595 case 'line-through':
596 modified.textDecoration = s;
597 break;
598 default:
599 throw new Error(`Unexpected font style: "${s}".`);
600 }
601 });
602 }
603 return modified;
604 }
605 /**
606 * Render caption affixes for the given tree node.
607 * @param node the tree node.
608 * @param props the node properties.
609 * @param affixKey the affix key.
610 */
611 renderCaptionAffixes(node, props, affixKey) {
612 const suffix = affixKey === 'captionSuffixes';
613 const affixClass = suffix ? tree_decorator_1.TreeDecoration.Styles.CAPTION_SUFFIX_CLASS : tree_decorator_1.TreeDecoration.Styles.CAPTION_PREFIX_CLASS;
614 const classes = [exports.TREE_NODE_SEGMENT_CLASS, affixClass];
615 const affixes = this.getDecorationData(node, affixKey).filter(objects_1.notEmpty).reduce((acc, current) => acc.concat(current), []);
616 const children = [];
617 for (let i = 0; i < affixes.length; i++) {
618 const affix = affixes[i];
619 if (suffix && i === affixes.length - 1) {
620 classes.push(exports.TREE_NODE_SEGMENT_GROW_CLASS);
621 }
622 const style = this.applyFontStyles({}, affix.fontData);
623 const className = classes.join(' ');
624 const key = node.id + '_' + i;
625 const attrs = {
626 className,
627 style,
628 key
629 };
630 children.push(React.createElement('div', attrs, affix.data));
631 }
632 return React.createElement(React.Fragment, null, children);
633 }
634 /**
635 * Decorate the tree node icon.
636 * @param node the tree node.
637 * @param icon the icon.
638 */
639 decorateIcon(node, icon) {
640 if (!icon) {
641 return;
642 }
643 const overlayIcons = [];
644 // if multiple overlays have the same overlay.position attribute, we'll de-duplicate those and only process the first one from the decoration array
645 const seenPositions = new Set();
646 const overlays = this.getDecorationData(node, 'iconOverlay').filter(objects_1.notEmpty);
647 for (const overlay of overlays) {
648 if (!seenPositions.has(overlay.position)) {
649 seenPositions.add(overlay.position);
650 const iconClasses = [tree_decorator_1.TreeDecoration.Styles.DECORATOR_SIZE_CLASS, tree_decorator_1.TreeDecoration.IconOverlayPosition.getStyle(overlay.position)];
651 const style = (color) => color === undefined ? {} : { color };
652 if (overlay.background) {
653 overlayIcons.push(React.createElement("span", { key: node.id + 'bg', className: this.getIconClass(overlay.background.shape, iconClasses), style: style(overlay.background.color) }));
654 }
655 const overlayIcon = 'icon' in overlay ? overlay.icon : overlay.iconClass;
656 overlayIcons.push(React.createElement("span", { key: node.id, className: this.getIconClass(overlayIcon, iconClasses), style: style(overlay.color) }));
657 }
658 }
659 if (overlayIcons.length > 0) {
660 return React.createElement("div", { className: tree_decorator_1.TreeDecoration.Styles.ICON_WRAPPER_CLASS },
661 icon,
662 overlayIcons);
663 }
664 return icon;
665 }
666 /**
667 * Render the tree node tail decorations.
668 * @param node the tree node.
669 * @param props the node properties.
670 */
671 renderTailDecorations(node, props) {
672 const tailDecorations = this.getDecorationData(node, 'tailDecorations').reduce((acc, current) => acc.concat(current), []);
673 if (tailDecorations.length === 0) {
674 return;
675 }
676 return this.renderTailDecorationsForNode(node, props, tailDecorations);
677 }
678 renderTailDecorationsForNode(node, props, tailDecorations) {
679 let dotDecoration;
680 const otherDecorations = [];
681 tailDecorations.reverse().forEach(decoration => {
682 if (tree_decorator_1.TreeDecoration.TailDecoration.isDotDecoration(decoration)) {
683 dotDecoration || (dotDecoration = decoration);
684 }
685 else if (decoration.data || decoration.icon || decoration.iconClass) {
686 otherDecorations.push(decoration);
687 }
688 });
689 const decorationsToRender = dotDecoration ? [dotDecoration, ...otherDecorations] : otherDecorations;
690 return React.createElement(React.Fragment, null, decorationsToRender.map((decoration, index) => {
691 const { tooltip, data, fontData, color, icon, iconClass } = decoration;
692 const iconToRender = icon !== null && icon !== void 0 ? icon : iconClass;
693 const className = [exports.TREE_NODE_SEGMENT_CLASS, exports.TREE_NODE_TAIL_CLASS, 'flex'].join(' ');
694 const style = fontData ? this.applyFontStyles({}, fontData) : color ? { color } : undefined;
695 const content = data ? data : iconToRender
696 ? React.createElement("span", { key: node.id + 'icon' + index, className: this.getIconClass(iconToRender, iconToRender === 'circle' ? [tree_decorator_1.TreeDecoration.Styles.DECORATOR_SIZE_CLASS] : []) })
697 : '';
698 return React.createElement("div", { key: node.id + className + index, className: className, style: style, title: tooltip },
699 content,
700 index !== decorationsToRender.length - 1 ? ',' : '');
701 }));
702 }
703 /**
704 * Determine the classes to use for an icon
705 * - Assumes a Font Awesome name when passed a single string, otherwise uses the passed string array
706 * @param iconName the icon name or list of icon names.
707 * @param additionalClasses additional CSS classes.
708 *
709 * @returns the icon class name.
710 */
711 getIconClass(iconName, additionalClasses = []) {
712 const iconClass = (typeof iconName === 'string') ? ['a', 'fa', `fa-${iconName}`] : ['a'].concat(iconName);
713 return iconClass.concat(additionalClasses).join(' ');
714 }
715 /**
716 * Render indent for the file tree based on the depth
717 * @param node the tree node.
718 * @param depth the depth of the tree node.
719 */
720 renderIndent(node, props) {
721 const renderIndentGuides = this.corePreferences['workbench.tree.renderIndentGuides'];
722 if (renderIndentGuides === 'none') {
723 return undefined;
724 }
725 const indentDivs = [];
726 let current = node;
727 let depth = props.depth;
728 while (current && depth) {
729 const classNames = [exports.TREE_NODE_INDENT_GUIDE_CLASS];
730 if (this.needsActiveIndentGuideline(current)) {
731 classNames.push('active');
732 }
733 else {
734 classNames.push(renderIndentGuides === 'onHover' ? 'hover' : 'always');
735 }
736 const paddingLeft = this.getDepthPadding(depth);
737 indentDivs.unshift(React.createElement("div", { key: depth, className: classNames.join(' '), style: {
738 paddingLeft: `${paddingLeft}px`
739 } }));
740 current = current.parent;
741 depth--;
742 }
743 return indentDivs;
744 }
745 needsActiveIndentGuideline(node) {
746 const parent = node.parent;
747 if (!parent || !this.isExpandable(parent)) {
748 return false;
749 }
750 if (tree_selection_1.SelectableTreeNode.isSelected(parent)) {
751 return true;
752 }
753 if (parent.expanded) {
754 for (const sibling of parent.children) {
755 if (tree_selection_1.SelectableTreeNode.isSelected(sibling) && !(this.isExpandable(sibling) && sibling.expanded)) {
756 return true;
757 }
758 }
759 }
760 return false;
761 }
762 /**
763 * Render the node given the tree node and node properties.
764 * @param node the tree node.
765 * @param props the node properties.
766 */
767 renderNode(node, props) {
768 if (!tree_1.TreeNode.isVisible(node)) {
769 return undefined;
770 }
771 const attributes = this.createNodeAttributes(node, props);
772 const content = React.createElement("div", { className: exports.TREE_NODE_CONTENT_CLASS },
773 this.renderExpansionToggle(node, props),
774 this.renderCheckbox(node, props),
775 this.decorateIcon(node, this.renderIcon(node, props)),
776 this.renderCaptionAffixes(node, props, 'captionPrefixes'),
777 this.renderCaption(node, props),
778 this.renderCaptionAffixes(node, props, 'captionSuffixes'),
779 this.renderTailDecorations(node, props));
780 return React.createElement('div', attributes, content);
781 }
782 /**
783 * Create node attributes for the tree node given the node properties.
784 * @param node the tree node.
785 * @param props the node properties.
786 */
787 createNodeAttributes(node, props) {
788 const className = this.createNodeClassNames(node, props).join(' ');
789 const style = this.createNodeStyle(node, props);
790 return {
791 className,
792 style,
793 onClick: event => this.handleClickEvent(node, event),
794 onDoubleClick: event => this.handleDblClickEvent(node, event),
795 onAuxClick: event => this.handleAuxClickEvent(node, event),
796 onContextMenu: event => this.handleContextMenuEvent(node, event),
797 };
798 }
799 /**
800 * Create the node class names.
801 * @param node the tree node.
802 * @param props the node properties.
803 *
804 * @returns the list of tree node class names.
805 */
806 createNodeClassNames(node, props) {
807 const classNames = [exports.TREE_NODE_CLASS];
808 if (tree_1.CompositeTreeNode.is(node)) {
809 classNames.push(exports.COMPOSITE_TREE_NODE_CLASS);
810 }
811 if (this.isExpandable(node)) {
812 classNames.push(exports.EXPANDABLE_TREE_NODE_CLASS);
813 }
814 if (this.rowIsSelected(node, props)) {
815 classNames.push(widgets_1.SELECTED_CLASS);
816 }
817 if (this.focusService.hasFocus(node)) {
818 classNames.push(widgets_1.FOCUS_CLASS);
819 }
820 return classNames;
821 }
822 rowIsSelected(node, props) {
823 return tree_selection_1.SelectableTreeNode.isSelected(node);
824 }
825 /**
826 * Get the default node style.
827 * @param node the tree node.
828 * @param props the node properties.
829 *
830 * @returns the CSS properties if available.
831 */
832 getDefaultNodeStyle(node, props) {
833 const paddingLeft = this.getPaddingLeft(node, props) + 'px';
834 return { paddingLeft };
835 }
836 getPaddingLeft(node, props) {
837 return this.getDepthPadding(props.depth) + (this.needsExpansionTogglePadding(node) ? this.props.expansionTogglePadding : 0);
838 }
839 /**
840 * If the node is a composite, a toggle will be rendered.
841 * Otherwise we need to add the width and the left, right padding => 18px
842 */
843 needsExpansionTogglePadding(node) {
844 return !this.isExpandable(node);
845 }
846 /**
847 * Create the tree node style.
848 * @param node the tree node.
849 * @param props the node properties.
850 */
851 createNodeStyle(node, props) {
852 return this.decorateNodeStyle(node, this.getDefaultNodeStyle(node, props));
853 }
854 /**
855 * Decorate the node style.
856 * @param node the tree node.
857 * @param style the optional CSS properties.
858 *
859 * @returns the CSS styles if available.
860 */
861 decorateNodeStyle(node, style) {
862 const backgroundColor = this.getDecorationData(node, 'backgroundColor').filter(objects_1.notEmpty).shift();
863 if (backgroundColor) {
864 style = {
865 ...(style || {}),
866 backgroundColor
867 };
868 }
869 return style;
870 }
871 /**
872 * Determine if the tree node is expandable.
873 * @param node the tree node.
874 *
875 * @returns `true` if the tree node is expandable.
876 */
877 isExpandable(node) {
878 return tree_expansion_1.ExpandableTreeNode.is(node);
879 }
880 /**
881 * Get the tree node decorations.
882 * @param node the tree node.
883 *
884 * @returns the list of tree decoration data.
885 */
886 getDecorations(node) {
887 const decorations = [];
888 if (tree_decorator_1.DecoratedTreeNode.is(node)) {
889 decorations.push(node.decorationData);
890 }
891 if (this.decorations.has(node.id)) {
892 decorations.push(...this.decorations.get(node.id));
893 }
894 return decorations.sort(tree_decorator_1.TreeDecoration.Data.comparePriority);
895 }
896 /**
897 * Get the tree decoration data for the given key.
898 * @param node the tree node.
899 * @param key the tree decoration data key.
900 *
901 * @returns the tree decoration data at the given key.
902 */
903 getDecorationData(node, key) {
904 return this.getDecorations(node).filter(data => data[key] !== undefined).map(data => data[key]);
905 }
906 /**
907 * Get the scroll container.
908 */
909 getScrollContainer() {
910 this.toDisposeOnDetach.push(common_1.Disposable.create(() => {
911 const { scrollTop, scrollLeft } = this.node;
912 this.lastScrollState = { scrollTop, scrollLeft };
913 }));
914 if (this.lastScrollState) {
915 const { scrollTop, scrollLeft } = this.lastScrollState;
916 this.node.scrollTop = scrollTop;
917 this.node.scrollLeft = scrollLeft;
918 }
919 return this.node;
920 }
921 onAfterAttach(msg) {
922 const up = [
923 keys_1.Key.ARROW_UP,
924 keys_1.KeyCode.createKeyCode({ first: keys_1.Key.ARROW_UP, modifiers: [keys_1.KeyModifier.Shift] })
925 ];
926 const down = [
927 keys_1.Key.ARROW_DOWN,
928 keys_1.KeyCode.createKeyCode({ first: keys_1.Key.ARROW_DOWN, modifiers: [keys_1.KeyModifier.Shift] })
929 ];
930 if (this.props.search) {
931 if (this.searchBox.isAttached) {
932 widgets_1.Widget.detach(this.searchBox);
933 }
934 widgets_1.UnsafeWidgetUtilities.attach(this.searchBox, this.node.parentElement);
935 this.addKeyListener(this.node, this.searchBox.keyCodePredicate.bind(this.searchBox), this.searchBox.handle.bind(this.searchBox));
936 this.toDisposeOnDetach.push(common_1.Disposable.create(() => {
937 widgets_1.Widget.detach(this.searchBox);
938 }));
939 }
940 super.onAfterAttach(msg);
941 this.addKeyListener(this.node, keys_1.Key.ARROW_LEFT, event => this.handleLeft(event));
942 this.addKeyListener(this.node, keys_1.Key.ARROW_RIGHT, event => this.handleRight(event));
943 this.addKeyListener(this.node, up, event => this.handleUp(event));
944 this.addKeyListener(this.node, down, event => this.handleDown(event));
945 this.addKeyListener(this.node, keys_1.Key.ENTER, event => this.handleEnter(event));
946 this.addKeyListener(this.node, keys_1.Key.SPACE, event => this.handleSpace(event));
947 this.addKeyListener(this.node, keys_1.Key.ESCAPE, event => this.handleEscape(event));
948 // eslint-disable-next-line @typescript-eslint/no-explicit-any
949 this.addEventListener(this.node, 'ps-scroll-y', (e) => {
950 if (this.view && this.view.list) {
951 const { scrollTop } = e.target;
952 this.view.list.scrollTo({
953 top: scrollTop
954 });
955 }
956 });
957 }
958 /**
959 * Handle the `left arrow` keyboard event.
960 * @param event the `left arrow` keyboard event.
961 */
962 async handleLeft(event) {
963 if (!!this.props.multiSelect && (this.hasCtrlCmdMask(event) || this.hasShiftMask(event))) {
964 return;
965 }
966 if (!await this.model.collapseNode()) {
967 this.model.selectParent();
968 }
969 }
970 /**
971 * Handle the `right arrow` keyboard event.
972 * @param event the `right arrow` keyboard event.
973 */
974 async handleRight(event) {
975 if (!!this.props.multiSelect && (this.hasCtrlCmdMask(event) || this.hasShiftMask(event))) {
976 return;
977 }
978 if (!await this.model.expandNode()) {
979 this.model.selectNextNode();
980 }
981 }
982 /**
983 * Handle the `up arrow` keyboard event.
984 * @param event the `up arrow` keyboard event.
985 */
986 handleUp(event) {
987 if (!!this.props.multiSelect && this.hasShiftMask(event)) {
988 this.model.selectPrevNode(tree_selection_1.TreeSelection.SelectionType.RANGE);
989 }
990 else {
991 this.model.selectPrevNode();
992 }
993 this.node.focus();
994 }
995 /**
996 * Handle the `down arrow` keyboard event.
997 * @param event the `down arrow` keyboard event.
998 */
999 handleDown(event) {
1000 if (!!this.props.multiSelect && this.hasShiftMask(event)) {
1001 this.model.selectNextNode(tree_selection_1.TreeSelection.SelectionType.RANGE);
1002 }
1003 else {
1004 this.model.selectNextNode();
1005 }
1006 this.node.focus();
1007 }
1008 /**
1009 * Handle the `enter key` keyboard event.
1010 * - `enter` opens the tree node.
1011 * @param event the `enter key` keyboard event.
1012 */
1013 handleEnter(event) {
1014 this.model.openNode();
1015 }
1016 /**
1017 * Handle the `space key` keyboard event.
1018 * - By default should be similar to a single-click action.
1019 * @param event the `space key` keyboard event.
1020 */
1021 handleSpace(event) {
1022 const { focusedNode } = this.focusService;
1023 if (!this.props.multiSelect || (!event.ctrlKey && !event.metaKey && !event.shiftKey)) {
1024 this.tapNode(focusedNode);
1025 }
1026 }
1027 handleEscape(event) {
1028 if (this.model.selectedNodes.length <= 1) {
1029 this.focusService.setFocus(undefined);
1030 this.node.focus();
1031 }
1032 this.model.clearSelection();
1033 }
1034 /**
1035 * Handle the single-click mouse event.
1036 * @param node the tree node if available.
1037 * @param event the mouse single-click event.
1038 */
1039 handleClickEvent(node, event) {
1040 if (node) {
1041 event.stopPropagation();
1042 const shiftMask = this.hasShiftMask(event);
1043 const ctrlCmdMask = this.hasCtrlCmdMask(event);
1044 if (this.props.multiSelect && (shiftMask || ctrlCmdMask) && tree_selection_1.SelectableTreeNode.is(node)) {
1045 if (shiftMask) {
1046 this.model.selectRange(node);
1047 }
1048 else if (ctrlCmdMask) {
1049 this.model.toggleNode(node);
1050 }
1051 }
1052 else {
1053 this.tapNode(node);
1054 }
1055 }
1056 }
1057 /**
1058 * The effective handler of an unmodified single-click event.
1059 */
1060 tapNode(node) {
1061 if (tree_selection_1.SelectableTreeNode.is(node)) {
1062 this.model.selectNode(node);
1063 }
1064 if (node && !this.props.expandOnlyOnExpansionToggleClick && this.isExpandable(node)) {
1065 this.model.toggleNodeExpansion(node);
1066 }
1067 }
1068 /**
1069 * Handle the double-click mouse event.
1070 * @param node the tree node if available.
1071 * @param event the double-click mouse event.
1072 */
1073 handleDblClickEvent(node, event) {
1074 this.model.openNode(node);
1075 event.stopPropagation();
1076 }
1077 /**
1078 * Handle the middle-click mouse event.
1079 * @param node the tree node if available.
1080 * @param event the middle-click mouse event.
1081 */
1082 handleAuxClickEvent(node, event) {
1083 if (event.button === 1) {
1084 this.model.openNode(node);
1085 if (tree_selection_1.SelectableTreeNode.is(node)) {
1086 this.model.selectNode(node);
1087 }
1088 }
1089 event.stopPropagation();
1090 }
1091 /**
1092 * Handle the middle-click mouse event.
1093 * @param event the middle-click mouse event.
1094 */
1095 handleMiddleClickEvent(event) {
1096 // Prevents auto-scrolling behavior when middle-clicking.
1097 if (event.button === 1) {
1098 event.preventDefault();
1099 }
1100 }
1101 /**
1102 * Handle the context menu click event.
1103 * - The context menu click event is triggered by the right-click.
1104 * @param node the tree node if available.
1105 * @param event the right-click mouse event.
1106 */
1107 handleContextMenuEvent(node, event) {
1108 if (tree_selection_1.SelectableTreeNode.is(node)) {
1109 // Keep the selection for the context menu, if the widget support multi-selection and the right click happens on an already selected node.
1110 if (!this.props.multiSelect || !node.selected) {
1111 const type = !!this.props.multiSelect && this.hasCtrlCmdMask(event) ? tree_selection_1.TreeSelection.SelectionType.TOGGLE : tree_selection_1.TreeSelection.SelectionType.DEFAULT;
1112 this.model.addSelection({ node, type });
1113 }
1114 this.focusService.setFocus(node);
1115 const contextMenuPath = this.props.contextMenuPath;
1116 if (contextMenuPath) {
1117 const { x, y } = event.nativeEvent;
1118 const args = this.toContextMenuArgs(node);
1119 setTimeout(() => this.contextMenuRenderer.render({
1120 menuPath: contextMenuPath,
1121 anchor: { x, y },
1122 args
1123 }), 10);
1124 }
1125 }
1126 event.stopPropagation();
1127 event.preventDefault();
1128 }
1129 /**
1130 * Actually handle the double-click mouse event on the expansion toggle.
1131 * @param event the double-click mouse event.
1132 */
1133 doHandleExpansionToggleDblClickEvent(event) {
1134 if (this.props.expandOnlyOnExpansionToggleClick) {
1135 // Ignore the double-click event.
1136 event.stopPropagation();
1137 }
1138 }
1139 /**
1140 * Convert the tree node to context menu arguments.
1141 * @param node the selectable tree node.
1142 */
1143 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1144 toContextMenuArgs(node) {
1145 return undefined;
1146 }
1147 /**
1148 * Determine if the tree modifier aware event has a `ctrlcmd` mask.
1149 * @param event the tree modifier aware event.
1150 *
1151 * @returns `true` if the tree modifier aware event contains the `ctrlcmd` mask.
1152 */
1153 hasCtrlCmdMask(event) {
1154 return os_1.isOSX ? event.metaKey : event.ctrlKey;
1155 }
1156 /**
1157 * Determine if the tree modifier aware event has a `shift` mask.
1158 * @param event the tree modifier aware event.
1159 *
1160 * @returns `true` if the tree modifier aware event contains the `shift` mask.
1161 */
1162 hasShiftMask(event) {
1163 // Ctrl/Cmd mask overrules the Shift mask.
1164 if (this.hasCtrlCmdMask(event)) {
1165 return false;
1166 }
1167 return event.shiftKey;
1168 }
1169 /**
1170 * Deflate the tree node for storage.
1171 * @param node the tree node.
1172 */
1173 deflateForStorage(node) {
1174 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1175 const copy = Object.assign({}, node);
1176 if (copy.parent) {
1177 delete copy.parent;
1178 }
1179 if ('previousSibling' in copy) {
1180 delete copy.previousSibling;
1181 }
1182 if ('nextSibling' in copy) {
1183 delete copy.nextSibling;
1184 }
1185 if ('busy' in copy) {
1186 delete copy.busy;
1187 }
1188 if (tree_1.CompositeTreeNode.is(node)) {
1189 copy.children = [];
1190 for (const child of node.children) {
1191 copy.children.push(this.deflateForStorage(child));
1192 }
1193 }
1194 return copy;
1195 }
1196 /**
1197 * Inflate the tree node from storage.
1198 * @param node the tree node.
1199 * @param parent the optional tree node.
1200 */
1201 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1202 inflateFromStorage(node, parent) {
1203 if (node.selected) {
1204 node.selected = false;
1205 }
1206 if (parent) {
1207 node.parent = parent;
1208 }
1209 if (Array.isArray(node.children)) {
1210 for (const child of node.children) {
1211 this.inflateFromStorage(child, node);
1212 }
1213 }
1214 return node;
1215 }
1216 /**
1217 * Store the tree state.
1218 */
1219 storeState() {
1220 var _a;
1221 const decorations = this.decoratorService.deflateDecorators(this.decorations);
1222 let state = {
1223 decorations
1224 };
1225 if (this.model.root) {
1226 state = {
1227 ...state,
1228 root: this.deflateForStorage(this.model.root),
1229 model: this.model.storeState(),
1230 focusedNodeId: (_a = this.focusService.focusedNode) === null || _a === void 0 ? void 0 : _a.id
1231 };
1232 }
1233 return state;
1234 }
1235 /**
1236 * Restore the state.
1237 * @param oldState the old state object.
1238 */
1239 restoreState(oldState) {
1240 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1241 const { root, decorations, model, focusedNodeId } = oldState;
1242 if (root) {
1243 this.model.root = this.inflateFromStorage(root);
1244 }
1245 if (decorations) {
1246 this.decorations = this.decoratorService.inflateDecorators(decorations);
1247 }
1248 if (model) {
1249 this.model.restoreState(model);
1250 }
1251 if (focusedNodeId) {
1252 const candidate = this.model.getNode(focusedNodeId);
1253 if (tree_selection_1.SelectableTreeNode.is(candidate)) {
1254 this.focusService.setFocus(candidate);
1255 }
1256 }
1257 }
1258 toNodeIcon(node) {
1259 return this.labelProvider.getIcon(node);
1260 }
1261 toNodeName(node) {
1262 return this.labelProvider.getName(node);
1263 }
1264 toNodeDescription(node) {
1265 return this.labelProvider.getLongName(node);
1266 }
1267 getDepthPadding(depth) {
1268 return depth * this.props.leftPadding;
1269 }
1270};
1271__decorate([
1272 (0, inversify_1.inject)(tree_decorator_1.TreeDecoratorService),
1273 __metadata("design:type", Object)
1274], TreeWidget.prototype, "decoratorService", void 0);
1275__decorate([
1276 (0, inversify_1.inject)(tree_search_1.TreeSearch),
1277 __metadata("design:type", tree_search_1.TreeSearch)
1278], TreeWidget.prototype, "treeSearch", void 0);
1279__decorate([
1280 (0, inversify_1.inject)(search_box_1.SearchBoxFactory),
1281 __metadata("design:type", Function)
1282], TreeWidget.prototype, "searchBoxFactory", void 0);
1283__decorate([
1284 (0, inversify_1.inject)(tree_focus_service_1.TreeFocusService),
1285 __metadata("design:type", Object)
1286], TreeWidget.prototype, "focusService", void 0);
1287__decorate([
1288 (0, inversify_1.inject)(common_1.SelectionService),
1289 __metadata("design:type", common_1.SelectionService)
1290], TreeWidget.prototype, "selectionService", void 0);
1291__decorate([
1292 (0, inversify_1.inject)(label_provider_1.LabelProvider),
1293 __metadata("design:type", label_provider_1.LabelProvider)
1294], TreeWidget.prototype, "labelProvider", void 0);
1295__decorate([
1296 (0, inversify_1.inject)(core_preferences_1.CorePreferences),
1297 __metadata("design:type", Object)
1298], TreeWidget.prototype, "corePreferences", void 0);
1299__decorate([
1300 (0, inversify_1.postConstruct)(),
1301 __metadata("design:type", Function),
1302 __metadata("design:paramtypes", []),
1303 __metadata("design:returntype", void 0)
1304], TreeWidget.prototype, "init", null);
1305TreeWidget = TreeWidget_1 = __decorate([
1306 (0, inversify_1.injectable)(),
1307 __param(0, (0, inversify_1.inject)(exports.TreeProps)),
1308 __param(1, (0, inversify_1.inject)(tree_model_1.TreeModel)),
1309 __param(2, (0, inversify_1.inject)(context_menu_renderer_1.ContextMenuRenderer)),
1310 __metadata("design:paramtypes", [Object, Object, context_menu_renderer_1.ContextMenuRenderer])
1311], TreeWidget);
1312exports.TreeWidget = TreeWidget;
1313(function (TreeWidget) {
1314 class View extends React.Component {
1315 render() {
1316 const { rows, width, height, scrollToRow } = this.props;
1317 return React.createElement(react_virtuoso_1.Virtuoso, { ref: list => {
1318 this.list = (list || undefined);
1319 if (this.list && scrollToRow !== undefined) {
1320 this.list.scrollIntoView({
1321 index: scrollToRow,
1322 align: 'center'
1323 });
1324 }
1325 }, totalCount: rows.length, itemContent: index => this.props.renderNodeRow(rows[index]), width: width, height: height,
1326 // This is a pixel value, it will scan 200px to the top and bottom of the current view
1327 overscan: 500 });
1328 }
1329 }
1330 TreeWidget.View = View;
1331})(TreeWidget = exports.TreeWidget || (exports.TreeWidget = {}));
1332exports.TreeWidget = TreeWidget;
1333//# sourceMappingURL=tree-widget.js.map
\No newline at end of file