Skip to content

Commit f00e0d6

Browse files
committed
Change search default
- clicking a suggestion triggers a search, navigation - panel stays open until user dismisses it - panel now requires 2 Esc / clicks to close it - update broken tests
1 parent f81e4c2 commit f00e0d6

4 files changed

Lines changed: 112 additions & 32 deletions

File tree

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/mapml/control/SearchButton.js

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -68,14 +68,23 @@ export var SearchButton = Control.extend({
6868
function (e) {
6969
if (e.key === 'Escape') {
7070
DomEvent.stop(e);
71-
this._closePanel();
71+
if (this._input.value) {
72+
this._input.value = '';
73+
this._results.innerHTML = '';
74+
} else {
75+
this._closePanel();
76+
}
7277
} else if (e.key === 'Enter') {
7378
DomEvent.stop(e);
7479
if (this._debounceTimer) {
7580
clearTimeout(this._debounceTimer);
7681
this._debounceTimer = null;
7782
}
7883
this._doSearch(this._input.value.trim());
84+
} else if (e.key === 'ArrowDown' || e.key === 'Down') {
85+
DomEvent.stop(e);
86+
let first = this._results.querySelector('.mapml-search-result');
87+
if (first) first.focus();
7988
}
8089
},
8190
this
@@ -381,6 +390,44 @@ export var SearchButton = Control.extend({
381390

382391
_defaultSearchHandler: function ({ query, responses }) {
383392
this._renderResults(responses);
393+
// Navigate to the first result
394+
for (let { data } of responses) {
395+
if (!data || !data.features) continue;
396+
for (let feature of data.features) {
397+
this._navigateToFeature(feature);
398+
break;
399+
}
400+
break;
401+
}
402+
// Keep focus on the search input so the user can refine or review
403+
this._input.focus();
404+
},
405+
406+
_navigateToFeature: function (feature) {
407+
let map = this._map;
408+
// Standard GeoJSON bbox
409+
let bbox = feature.bbox;
410+
// Photon stores extent in properties.extent [west, south, east, north]
411+
if (
412+
(!bbox || bbox.length !== 4) &&
413+
feature.properties &&
414+
feature.properties.extent &&
415+
feature.properties.extent.length === 4
416+
) {
417+
bbox = feature.properties.extent;
418+
}
419+
if (bbox && bbox.length === 4) {
420+
let [west, south, east, north] = bbox;
421+
map.fitBounds(latLngBounds([south, west], [north, east]));
422+
} else if (
423+
feature.geometry &&
424+
feature.geometry.coordinates &&
425+
feature.geometry.coordinates.length >= 2
426+
) {
427+
let [lon, lat] = feature.geometry.coordinates;
428+
let zoom = (feature.properties && feature.properties.zoom) || 14;
429+
map.setView([lat, lon], zoom);
430+
}
384431
},
385432

386433
_renderResults: function (responses) {
@@ -399,11 +446,44 @@ export var SearchButton = Control.extend({
399446
this._selectResult(f, l)
400447
)(feature, layer)
401448
);
449+
btn.addEventListener('keydown', this._resultKeydown.bind(this));
402450
this._results.appendChild(btn);
403451
}
404452
}
405453
},
406454

455+
_resultKeydown: function (e) {
456+
let btn = e.target;
457+
let prev = btn.previousElementSibling;
458+
let next = btn.nextElementSibling;
459+
if (
460+
e.key === 'ArrowDown' ||
461+
e.key === 'ArrowRight' ||
462+
e.key === 'Down' ||
463+
e.key === 'Right'
464+
) {
465+
DomEvent.stop(e);
466+
if (next && next.classList.contains('mapml-search-result')) {
467+
next.focus();
468+
}
469+
} else if (
470+
e.key === 'ArrowUp' ||
471+
e.key === 'ArrowLeft' ||
472+
e.key === 'Up' ||
473+
e.key === 'Left'
474+
) {
475+
DomEvent.stop(e);
476+
if (prev && prev.classList.contains('mapml-search-result')) {
477+
prev.focus();
478+
} else {
479+
this._input.focus();
480+
}
481+
} else if (e.key === 'Escape') {
482+
DomEvent.stop(e);
483+
this._input.focus();
484+
}
485+
},
486+
407487
_formatResultName: function (props) {
408488
if (!props) return this._locale?.searchResultWithNoName || 'Unnamed';
409489
if (props.display_name) return props.display_name;
@@ -420,31 +500,10 @@ export var SearchButton = Control.extend({
420500
},
421501

422502
_selectResult: function (feature, layer) {
423-
let map = this._map;
424-
// Standard GeoJSON bbox
425-
let bbox = feature.bbox;
426-
// Photon stores extent in properties.extent [west, south, east, north]
427-
if (
428-
(!bbox || bbox.length !== 4) &&
429-
feature.properties &&
430-
feature.properties.extent &&
431-
feature.properties.extent.length === 4
432-
) {
433-
bbox = feature.properties.extent;
434-
}
435-
if (bbox && bbox.length === 4) {
436-
let [west, south, east, north] = bbox;
437-
map.fitBounds(latLngBounds([south, west], [north, east]));
438-
} else if (
439-
feature.geometry &&
440-
feature.geometry.coordinates &&
441-
feature.geometry.coordinates.length >= 2
442-
) {
443-
let [lon, lat] = feature.geometry.coordinates;
444-
let zoom = (feature.properties && feature.properties.zoom) || 14;
445-
map.setView([lat, lon], zoom);
446-
}
447-
this._closePanel();
503+
// Use the suggestion's display name as a refined search query
504+
let name = this._formatResultName(feature.properties);
505+
this._input.value = name;
506+
this._doSearch(name);
448507
}
449508
});
450509

test/e2e/core/searchDefault.test.js

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,39 @@ test.describe('Search default handler tests', () => {
4646
expect(texts[2]).toBe('Gatineau, Quebec, Canada');
4747
});
4848

49-
test('Clicking a result with bbox fits bounds and closes panel', async () => {
50-
// Click the first result (Ottawa — has bbox)
49+
test('Clicking a suggestion result triggers search and navigates', async () => {
50+
// Click the first suggestion (Ottawa — has bbox)
5151
await page.click('.mapml-search-result:first-child');
52+
// Wait for the search triggered by _selectResult to complete
5253
await page.waitForTimeout(500);
53-
// Panel should be closed
54+
// Panel should remain open (suggestion selection now triggers a search)
5455
let hasOpenClass = await page.$eval('.mapml-search-panel', (panel) =>
5556
panel.classList.contains('mapml-search-panel-open')
5657
);
57-
expect(hasOpenClass).toBe(false);
58+
expect(hasOpenClass).toBe(true);
59+
// Input should contain the suggestion's display name
60+
let inputValue = await page.$eval(
61+
'.mapml-search-input',
62+
(input) => input.value
63+
);
64+
expect(inputValue).toBe('Ottawa, Ontario, Canada');
65+
// Search results should be rendered (search endpoint returns 1 result)
66+
let count = await page.$$eval(
67+
'.mapml-search-result',
68+
(btns) => btns.length
69+
);
70+
expect(count).toBe(1);
5871
// Check the map center moved toward Ottawa (lat ~45.4, lon ~-75.65)
5972
let center = await page.$eval('[data-testid=viewer]', (viewer) => {
6073
let map = viewer._map;
6174
return { lat: map.getCenter().lat, lng: map.getCenter().lng };
6275
});
6376
expect(center.lat).toBeCloseTo(45.4, 0);
6477
expect(center.lng).toBeCloseTo(-75.65, 0);
78+
// Close panel to restore clean state for subsequent tests
79+
await page.press('.mapml-search-input', 'Escape'); // clear input
80+
await page.press('.mapml-search-input', 'Escape'); // close panel
81+
await page.waitForTimeout(400);
6582
});
6683

6784
test('Pressing Enter in input performs search', async () => {

test/e2e/core/searchI18n.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ test.describe('Search i18n tests (Japanese)', () => {
5151
// Tokyo bbox center is roughly (35.7, 139.4)
5252
expect(center.lat).toBeCloseTo(35.7, 0);
5353
expect(center.lng).toBeCloseTo(139.4, 0);
54+
// Close panel to restore clean state for subsequent tests
55+
await page.press('.mapml-search-input', 'Escape'); // clear input
56+
await page.press('.mapml-search-input', 'Escape'); // close panel
57+
await page.waitForTimeout(400);
5458
});
5559

5660
test('Search query is properly URL-encoded for non-Latin characters', async () => {

0 commit comments

Comments
 (0)