Skip to content

Commit 0e113bd

Browse files
committed
support type to search select
1 parent ee92a83 commit 0e113bd

File tree

8 files changed

+211
-12
lines changed

8 files changed

+211
-12
lines changed

app/components/component-tree-item.hbs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{{!-- template-lint-disable no-invalid-interactive --}}
22
<div
33
style={{@item.style}}
4+
data-test-id={{@item.instance}}
45
class="
56
component-tree-item relative flex items-center mx-1 rounded
67
{{if @item.hasInstance "cursor-pointer" "cursor-default"}}
@@ -40,7 +41,7 @@
4041
{{#if @item.isComponent}}
4142
{{#if @item.isCurlyInvocation}}
4243
<span class="component-name">
43-
{{@item.name}}
44+
{{mark-match @item.name @searchTerm}}
4445
</span>
4546

4647
{{#each @item.args.positional as |value|}}
@@ -63,7 +64,7 @@
6364
{{/each-in}}
6465
{{else}}
6566
<span class="component-name">
66-
{{classify @item.name}}
67+
{{mark-match (classify @item.name) @searchTerm}}
6768
</span>
6869

6970
{{#each-in @item.args.named as |name value|}}
@@ -78,11 +79,11 @@
7879
{{/each-in}}
7980
{{/if}}
8081
{{else if @item.isOutlet}}
81-
\{{outlet "{{@item.name}}"}}
82+
\{{outlet "{{mark-match @item.name @searchTerm}}"}}
8283
{{else if @item.isEngine}}
83-
\{{mount "{{@item.name}}"}}
84+
\{{mount "{{mark-match @item.name @searchTerm}}"}}
8485
{{else if @item.isRouteTemplate}}
85-
{{@item.name}} route
86+
{{mark-match @item.name @searchTerm}} route
8687
{{/if}}
8788
</code>
8889
</div>

app/controllers/component-tree.js

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,26 @@ export default class ComponentTreeController extends Controller {
1919

2020
@tracked query = '';
2121
@tracked isInspecting = false;
22+
/**
23+
*
24+
* @type {RenderItem[]}
25+
*/
2226
@tracked renderItems = [];
2327

2428
@tracked _pinned = undefined;
2529
@tracked _previewing = undefined;
30+
@tracked searchSelect = undefined;
2631

2732
_store = Object.create(null);
2833

2934
set renderTree(renderTree) {
3035
let { _store } = this;
3136

3237
let store = Object.create(null);
38+
/**
39+
*
40+
* @type {RenderItem[]}
41+
*/
3342
let renderItems = [];
3443

3544
let flatten = (parent, renderNode) => {
@@ -80,6 +89,14 @@ export default class ComponentTreeController extends Controller {
8089
}
8190

8291
get nextItem() {
92+
if (this.searchSelect) {
93+
const items = this.matchingItems;
94+
const index = items.indexOf(this.findItem(this.pinned)) + 1;
95+
return (
96+
items.slice(index).find((i) => searchMatch(i.name, this.searchSelect)) ||
97+
this.currentItem
98+
);
99+
}
83100
const items = this.visibleItems;
84101
return (
85102
items[items.indexOf(this.findItem(this.pinned)) + 1] ||
@@ -88,6 +105,16 @@ export default class ComponentTreeController extends Controller {
88105
}
89106

90107
get previousItem() {
108+
if (this.searchSelect) {
109+
const items = this.matchingItems;
110+
const index = items.indexOf(this.findItem(this.pinned));
111+
return (
112+
items
113+
.slice(0, index)
114+
.reverse()
115+
.find((i) => searchMatch(i.name, this.searchSelect)) || this.currentItem
116+
);
117+
}
91118
const items = this.visibleItems;
92119
return items[items.indexOf(this.findItem(this.pinned)) - 1] || items[0];
93120
}
@@ -193,6 +220,10 @@ export default class ComponentTreeController extends Controller {
193220
}
194221
}
195222

223+
@action handleClick() {
224+
this.searchSelect = false;
225+
}
226+
196227
@action handleKeyDown(event) {
197228
if (focusedInInput()) {
198229
return;
@@ -202,25 +233,33 @@ export default class ComponentTreeController extends Controller {
202233
event.preventDefault();
203234
}
204235

236+
console.log('this._searching', this.searchSelect);
237+
205238
switch (event.keyCode) {
206239
case KEYS.up:
207-
this.pinned = this.previousItem.id;
240+
this.pinned = this.previousItem?.id;
208241
break;
209242
case KEYS.right: {
243+
if (this.searchSelect) {
244+
break;
245+
}
210246
const pinnedItem = this.findItem(this.pinned);
211247

212248
if (pinnedItem.isExpanded) {
213-
this.pinned = this.nextItem.id;
249+
this.pinned = this.nextItem?.id;
214250
} else {
215251
pinnedItem.expand();
216252
}
217253

218254
break;
219255
}
220256
case KEYS.down:
221-
this.pinned = this.nextItem.id;
257+
this.pinned = this.nextItem?.id;
222258
break;
223259
case KEYS.left: {
260+
if (this.searchSelect) {
261+
break;
262+
}
224263
const pinnedItem = this.findItem(this.pinned);
225264

226265
if (pinnedItem.isExpanded) {
@@ -231,6 +270,22 @@ export default class ComponentTreeController extends Controller {
231270

232271
break;
233272
}
273+
case KEYS.escape:
274+
this.searchSelect = undefined;
275+
break;
276+
case KEYS.backspace:
277+
this.searchSelect = (this.searchSelect || '').slice(0, -1);
278+
break;
279+
default:
280+
if (event.key.length === 1) {
281+
this.searchSelect = (this.searchSelect || '') + event.key;
282+
if (
283+
!this.currentItem ||
284+
!searchMatch(this.currentItem.name, this.searchSelect)
285+
) {
286+
this.pinned = this.nextItem?.id;
287+
}
288+
}
234289
}
235290
}
236291

@@ -248,10 +303,12 @@ export default class ComponentTreeController extends Controller {
248303

249304
@action arrowKeysSetup() {
250305
document.addEventListener('keydown', this.handleKeyDown);
306+
document.addEventListener('click', this.handleClick);
251307
}
252308

253309
@action arrowKeysTeardown() {
254310
document.removeEventListener('keydown', this.handleKeyDown);
311+
document.removeEventListener('click', this.handleClick);
255312
}
256313
}
257314

app/helpers/mark-match.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { helper } from '@ember/component/helper';
2+
import { htmlSafe } from '@ember/template';
3+
import searchMatch from 'ember-inspector/utils/search-match';
4+
5+
6+
function replaceRange(s, start, end, substitute) {
7+
return s.substring(0, start) + substitute + s.substring(end);
8+
}
9+
/**
10+
*
11+
* @param str {string}
12+
* @param regex {RegExp}
13+
* @return {SafeString}
14+
*/
15+
export function markMatch([str, regex]) {
16+
if (!regex) {
17+
return str;
18+
}
19+
const match = searchMatch(str, regex);
20+
if (!match) {
21+
return str;
22+
}
23+
const matchedText = str.slice(match.index, match.index + match[0].length);
24+
str = replaceRange(
25+
str,
26+
match.index,
27+
match.index + match[0].length,
28+
`<mark>${matchedText}</mark>`
29+
);
30+
return htmlSafe(str);
31+
}
32+
33+
export default helper(markMatch);

app/styles/component_tree.scss

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,3 +210,14 @@
210210
.component-tree-item--component {
211211
color: var(--base09);
212212
}
213+
214+
.component-tree-search {
215+
background-color: var(--base00);
216+
border: 1px solid var(--base10);
217+
box-shadow: 0 0 3px var(--base15);
218+
padding: 3px;
219+
position: absolute;
220+
right: 20px;
221+
top: 3px;
222+
z-index: 1;
223+
}

app/templates/component-tree.hbs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@
1010
{{/in-element}}
1111
{{/if}}
1212

13+
{{#if this.searchSelect}}
14+
<div class='component-tree-search'> selecting: <strong>{{this.searchSelect}}</strong></div>
15+
{{/if}}
16+
1317
<ScrollContainer
1418
@collection={{this.visibleItems}}
1519
@currentItem={{this.currentItem}}
@@ -24,6 +28,6 @@
2428
@estimateHeight={{this.itemHeight}}
2529
@items={{this.visibleItems}}
2630
@key="id" as |item|>
27-
<ComponentTreeItem @item={{item}} />
31+
<ComponentTreeItem @item={{item}} @searchTerm={{this.searchSelect}}/>
2832
</VerticalCollection>
2933
</ScrollContainer>

app/utils/key-codes.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const KEYS = {
55
right: 39,
66
down: 40,
77
left: 37,
8+
backspace: 8,
89
};
910

1011
export { KEYS };

app/utils/search-match.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ export default function (text, searchQuery) {
1010
return true;
1111
}
1212
let regExp = new RegExp(escapeRegExp(sanitize(searchQuery)));
13-
return !!sanitize(text).match(regExp);
13+
return sanitize(text).match(regExp);
1414
}

tests/acceptance/component-tree-test.js

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
import { module, test } from 'qunit';
1414
import { setupApplicationTest } from 'ember-qunit';
1515
import { setupTestAdapter, respondWith, sendMessage } from '../test-adapter';
16+
import { KEYS } from 'ember-inspector/utils/key-codes';
1617

1718
function textFor(selector, context) {
1819
return context.querySelector(selector).textContent.trim();
@@ -108,8 +109,8 @@ function getRenderTree({ withChildren, withManyChildren } = {}) {
108109
const children = [];
109110
if (withChildren) {
110111
children.push(
111-
Component({ id: 5, name: 'sub-task' }),
112-
Component({ id: 6, name: 'sub-task' })
112+
Component({ id: 5, name: 'sub-task-' + 5 }),
113+
Component({ id: 6, name: 'sub-task-' + 6 })
113114
);
114115
}
115116
if (withManyChildren) {
@@ -354,6 +355,97 @@ module('Component Tab', function (hooks) {
354355
assert.strictEqual(treeNodes.length, 4, 'expected all tree nodes');
355356
});
356357

358+
test('It should search select when typing characters', async function (assert) {
359+
assert.expect(26);
360+
await visit('/component-tree');
361+
362+
respondWith('view:showInspection', false, { count: 12 });
363+
364+
await sendMessage({
365+
type: 'view:renderTree',
366+
tree: getRenderTree({ withManyChildren: true }),
367+
});
368+
369+
let expanders = findAll('.component-tree-item__expand');
370+
let expanderEl = expanders[expanders.length - 1];
371+
await click(expanderEl);
372+
373+
async function triggerCharCodes(text) {
374+
for (let t of text) {
375+
await triggerKeyEvent(document, 'keydown', t.toUpperCase());
376+
}
377+
}
378+
379+
let treeNodes = findAll('.component-tree-item');
380+
assert.strictEqual(treeNodes.length, 32, 'expected all tree nodes');
381+
382+
respondWith('view:showInspection', false);
383+
respondWith('objectInspector:inspectById', ({ objectId }) => {
384+
const result = findAll(
385+
`[data-test-id=${objectId}] .component-tree-item__tag`
386+
)[0];
387+
// matching initial character s
388+
assert.strictEqual(
389+
result.textContent.trim(),
390+
'todos route',
391+
'should first select todos route'
392+
);
393+
return false;
394+
});
395+
396+
await triggerCharCodes('sub-task-1');
397+
await rerender();
398+
399+
treeNodes = findAll('mark');
400+
treeNodes.forEach((node) => {
401+
assert.strictEqual(
402+
node.textContent.trim(),
403+
'SubTask1',
404+
'SubTask1 text part should be marked'
405+
);
406+
});
407+
assert.strictEqual(
408+
treeNodes.length,
409+
10,
410+
'expected nodes with sub-task-1 name to be marked'
411+
);
412+
413+
treeNodes = findAll('.component-tree-item');
414+
assert.strictEqual(treeNodes.length, 32, 'expected all tree nodes');
415+
416+
respondWith('view:showInspection', false);
417+
418+
let subTaskNumber = 11;
419+
for (let i = 0; i < 10; i++) {
420+
await triggerKeyEvent(document, 'keydown', KEYS.down);
421+
const node = findAll(
422+
'.component-tree-item--pinned .component-tree-item__tag'
423+
)[0];
424+
assert.strictEqual(
425+
node.textContent.trim(),
426+
'SubTask' + subTaskNumber,
427+
'should include SubTask1'
428+
);
429+
subTaskNumber++;
430+
if (subTaskNumber === 20) {
431+
subTaskNumber = 100;
432+
}
433+
}
434+
435+
await triggerKeyEvent(document, 'keydown', KEYS.up);
436+
const node = findAll(
437+
'.component-tree-item--pinned .component-tree-item__tag'
438+
)[0];
439+
assert.strictEqual(
440+
node.textContent.trim(),
441+
'SubTask19',
442+
'should include SubTask1'
443+
);
444+
445+
treeNodes = findAll('.component-tree-item');
446+
assert.strictEqual(treeNodes.length, 33, 'expected all tree nodes');
447+
});
448+
357449
test('It should update the view tree when the port triggers a change, preserving the expanded state of existing nodes', async function (assert) {
358450
await visit('/component-tree');
359451

0 commit comments

Comments
 (0)