Skip to content

Commit

Permalink
Navigate component tree with arrow keys
Browse files Browse the repository at this point in the history
  • Loading branch information
nummi committed Mar 7, 2020
1 parent b7a7470 commit 6338007
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 8 deletions.
60 changes: 58 additions & 2 deletions app/controllers/component-tree.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { htmlSafe } from '@ember/string';
import { tracked } from '@glimmer/tracking';

import searchMatch from 'ember-inspector/utils/search-match';
import { KEYS } from 'ember-inspector/utils/key-codes';

export default class ComponentTreeController extends Controller {
queryParams = ['pinned', 'previewing', 'query'];
Expand Down Expand Up @@ -72,6 +73,16 @@ export default class ComponentTreeController extends Controller {
}
}

get nextItem() {
const items = this.visibleItems;
return items[items.indexOf(this.findItem(this.pinned)) + 1] || items[items.length - 1];
}

get previousItem() {
const items = this.visibleItems;
return items[items.indexOf(this.findItem(this.pinned)) - 1] || items[0];
}

get matchingItems() {
let { renderItems, query } = this;

Expand Down Expand Up @@ -167,6 +178,31 @@ export default class ComponentTreeController extends Controller {
}
}

@action handleKeyDown(event) {
if(focusedInInput()) {
return;
}

if(arrowKeyPressed(event.keyCode)) {
event.preventDefault();
}

switch (event.keyCode) {
case KEYS.up:
this.pinned = this.previousItem.id;
break;
case KEYS.right:
this.findItem(this.pinned).expand();
break;
case KEYS.down:
this.pinned = this.nextItem.id;
break;
case KEYS.left:
this.findItem(this.pinned).collapse();
break;
}
}

@action toggleInspect() {
this.port.send('view:inspectViews', { inspect: !this.isInspecting });
}
Expand All @@ -178,13 +214,33 @@ export default class ComponentTreeController extends Controller {
@action collapseAll() {
this.renderItems.forEach(item => item.collapse());
}

@action arrowKeysSetup() {
document.addEventListener('keydown', this.handleKeyDown);
}

@action arrowKeysTeardown() {
document.removeEventListener('keydown', this.handleKeyDown);
}
}

function isInternalRenderNode(renderNode) {
return renderNode.type === 'outlet' && renderNode.name === 'main' ||
renderNode.type === 'route-template' && renderNode.name === '-top-level';
}

function focusedInInput() {
return ['input', 'textarea'].includes(
document.activeElement.tagName.toLowerCase()
);
}

function arrowKeyPressed(keyCode) {
if([KEYS.up, KEYS.right, KEYS.down, KEYS.left].includes(keyCode)) {
event.preventDefault();
}
}

class RenderItem {
@tracked isExpanded = true;

Expand Down Expand Up @@ -284,7 +340,7 @@ class RenderItem {
}
}

get isSelected() {
get isPinned() {
return this.id === this.controller.pinned;
}

Expand Down Expand Up @@ -316,7 +372,7 @@ class RenderItem {
}

@action toggleInspection() {
if (this.isSelected) {
if (this.isPinned) {
this.controller.pinned = undefined;
} else {
this.controller.pinned = this.id;
Expand Down
4 changes: 2 additions & 2 deletions app/styles/component_tree.scss
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
border-radius: 0;
}

.component-tree-item--selected .component-tree-item__tag {
.component-tree-item--pinned .component-tree-item__tag {
.component-name,
.bracket-token,
.key-token,
Expand Down Expand Up @@ -154,7 +154,7 @@
* selected - user clicked on component
*/

.component-tree-item.component-tree-item--selected {
.component-tree-item.component-tree-item--pinned {
background: var(--focus);
color: var(--focus-text);

Expand Down
10 changes: 9 additions & 1 deletion app/templates/component-tree.hbs
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
<ScrollContainer @collection={{this.visibleItems}} @currentItem={{this.currentItem}} @itemHeight={{this.itemHeight}} class="list__content js-component-tree">
<ScrollContainer
{{did-insert this.arrowKeysSetup}}
{{will-destroy this.arrowKeysTeardown}}
@collection={{this.visibleItems}}
@currentItem={{this.currentItem}}
@itemHeight={{this.itemHeight}}
class="list__content js-component-tree"
tabindex="-1"
>
{{#vertical-collection this.visibleItems estimateHeight=this.itemHeight key="id" as |item|}}
<ComponentTreeItem @item={{item}} />
{{/vertical-collection}}
Expand Down
2 changes: 1 addition & 1 deletion app/templates/components/component-tree-item.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
component-tree-item relative flex items-center mx-1 rounded
{{if @item.hasInstance "cursor-pointer" "cursor-default"}}
{{if @item.isComponent "component-tree-item--component"}}
{{if @item.isSelected "component-tree-item--selected"}}
{{if @item.isPinned "component-tree-item--pinned"}}
{{if @item.isHighlighted "component-tree-item--highlighted"}}"
{{on "click" @item.toggleInspection}}
{{on "mouseenter" @item.showPreview}}
Expand Down
1 change: 0 additions & 1 deletion lib/ui/addon/components/toolbar-search-field.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,3 @@
</button>
{{/if}}
</div>

53 changes: 52 additions & 1 deletion tests/acceptance/component-tree-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
fillIn,
findAll,
triggerEvent,
triggerKeyEvent,
visit
} from '@ember/test-helpers';
import { module, test } from 'qunit';
Expand Down Expand Up @@ -142,6 +143,56 @@ module('Component Tab', function (hooks) {
assert.equal(treeNodes.length, 3, 'the last node should be hidden');
});

test('It allows users to expand and collapse nodes with arrow keys', async function (assert) {
await visit('/component-tree');

// handle messages
respondWith('view:showInspection', () => { return false; });
respondWith('objectInspector:inspectById', () => { return false; });

let treeNodes = findAll('.component-tree-item');
assert.equal(treeNodes.length, 4, 'expected some tree nodes');

// select first component node and collapse with left arrow
await click(treeNodes[2]);
await triggerKeyEvent(document, 'keydown', 37);

treeNodes = findAll('.component-tree-item');
assert.equal(treeNodes.length, 3, 'child nodes should be hidden');

// press right arrow key
await triggerKeyEvent(document, 'keydown', 39);

treeNodes = findAll('.component-tree-item');
assert.equal(treeNodes.length, 4, 'child nodes should be visible');
});

test('It allows users to navigate nodes with arrow keys', async function (assert) {
await visit('/component-tree');

// select first node with down arrow key
respondWith('view:showInspection', false);
respondWith('objectInspector:inspectById', ({ objectId }) => {
assert.equal(objectId, 'ember123');
return false;
});
await triggerKeyEvent(document, 'keydown', 40);

// select next node with down arrow key
respondWith('objectInspector:inspectById', ({ objectId }) => {
assert.equal(objectId, 'ember1');
return false;
});
await triggerKeyEvent(document, 'keydown', 40);

// select previous node with up arrow key
respondWith('objectInspector:inspectById', ({ objectId }) => {
assert.equal(objectId, 'ember123');
return false;
});
await triggerKeyEvent(document, 'keydown', 38);
});

test('It allows users to expand and collapse children with alt key', async function (assert) {
await visit('/component-tree');

Expand Down Expand Up @@ -298,6 +349,6 @@ module('Component Tab', function (hooks) {
});

assert.equal(currentURL(), '/component-tree?pinned=render-node%3A3', 'It pins the element id as a query param');
assert.dom('.component-tree-item--selected').hasText('TodoList', 'It selects the item in the tree corresponding to the element');
assert.dom('.component-tree-item--pinned').hasText('TodoList', 'It selects the item in the tree corresponding to the element');
});
});

0 comments on commit 6338007

Please sign in to comment.