mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-04 10:24:02 +00:00
feat(location): render the search field's open and searching state from the tray
LocationControls presents whether the search field is open and whether a lookup runs, and the dock renders its expanded and searching classes and calls toggleSearch(). The lookup, the navigation's settle, Escape and the location tray's collapse reach the field through the controls (setSearching, settleSearch, closeSearch) instead of writing its classes. LocationControls no longer takes searchToggle, LocationSearch takes setSearching and NavigationController takes settleSearch in place of searchInput.
This commit is contained in:
1 parent
b4ee2321cf
commit
0949f6a776
18 files changed
+238
-83
No files matched your search
+9
-2
@@ -113,8 +113,15 @@
|
||||
landmark pills or writes the mini status: it presents the tray, including
|
||||
the search error (`setSearchError()`), and hands it to its `publish`
|
||||
option, and Core's dock renders it and calls `chooseCity()` and
|
||||
`choosePoi()`. Its `elements` are now only `search` and `searchToggle`:
|
||||
`resetButtons` is gone, and the reset buttons call its new `reset()`.
|
||||
`choosePoi()`. Its `elements` are now only `search`: `resetButtons` and
|
||||
`searchToggle` are gone, and the reset buttons and the search toggle call
|
||||
its new `reset()` and `toggleSearch()`. It presents the search field's
|
||||
open and searching state too (`searchExpanded`, `searching`; a
|
||||
`searchExpanded` option starts it open), set through `setSearching()`,
|
||||
`settleSearch()` and `closeSearch()`, and a host renders the field's
|
||||
`expanded` and `searching` classes from it; `LocationSearch`
|
||||
takes a `setSearching` option in place of writing the field's class, and
|
||||
`NavigationController` a `settleSearch` option in place of `searchInput`.
|
||||
`createOrbitIndicator()` is replaced by
|
||||
`showOrbit(active)`, which adds the ORBIT indicator to the presented tray
|
||||
as `orbit: { active }` (null until offered) (samehkhamis).
|
||||
|
||||
@@ -353,7 +353,7 @@ test('teardown refuses deferred location work before geocoding begins', () => {
|
||||
'if (!query || this.destroyed) return;',
|
||||
'const authority = this.begin();',
|
||||
'if (authority === false)',
|
||||
'this.input.blur();',
|
||||
'this.input?.blur();',
|
||||
'this.onStart?.(authority);',
|
||||
'await this.search(query',
|
||||
], 'disposed search refusal');
|
||||
|
||||
@@ -151,17 +151,12 @@ test('a collapsed nested panel does not block the next Escape from closing its p
|
||||
|
||||
test('Location Escape clears a hidden draft search before restoring disclosure focus', () => {
|
||||
const fixture = panelFixture({ id: 'location-bar' });
|
||||
const removed = [];
|
||||
let blurred = false;
|
||||
fixture.manager._locationSearch = {
|
||||
classList: { remove: (...names) => removed.push(...names) },
|
||||
value: 'focus cleanup',
|
||||
blur: () => { blurred = true; },
|
||||
};
|
||||
// The location controls close the field and drop its draft
|
||||
// (src/ui/locationControls.test.mjs).
|
||||
let closed = 0;
|
||||
fixture.manager._closeLocationSearch = () => { closed += 1; };
|
||||
assert.equal(fixture.manager._collapsePanelOnEscape(fixture.event, 'location-bar'), true);
|
||||
assert.deepEqual(removed, ['expanded']);
|
||||
assert.equal(fixture.manager._locationSearch.value, '');
|
||||
assert.equal(blurred, true);
|
||||
assert.equal(closed, 1);
|
||||
assert.equal(fixture.focused(), true);
|
||||
});
|
||||
|
||||
|
||||
@@ -134,11 +134,11 @@ export class StyleManager extends ShellFacade {
|
||||
this._panelChrome = new PanelChrome({
|
||||
elements: {
|
||||
_leftPanelStack: this._leftPanelStack,
|
||||
_locationSearch: this._locationSearch,
|
||||
_ppToggles: this._ppToggles,
|
||||
_rightPanelStack: this._rightPanelStack,
|
||||
},
|
||||
operations: {
|
||||
_closeLocationSearch: () => this._locationNavigation?.closeSearch(),
|
||||
_syncCctvPanelViewport: (...args) =>
|
||||
this._syncCctvPanelViewport(...args),
|
||||
_showToast: (...args) => this._showToast(...args),
|
||||
@@ -171,7 +171,8 @@ export class StyleManager extends ShellFacade {
|
||||
militaryAwarenessLayer,
|
||||
rocketLaunchesLayer: services.rocketLaunchesLayer,
|
||||
},
|
||||
searchInput: this._locationSearch,
|
||||
settleSearch: (options) =>
|
||||
this._locationNavigation?.settleSearch(options),
|
||||
interruptCameraMotion: services.interruptCameraMotion,
|
||||
interruptGlobeRotation: services.interruptGlobeRotation,
|
||||
isCockpitActive: () => !!this.cockpitView?.active,
|
||||
@@ -321,7 +322,6 @@ export class StyleManager extends ShellFacade {
|
||||
},
|
||||
elements: {
|
||||
_locationSearch: this._locationSearch,
|
||||
_searchToggle: this._searchToggle,
|
||||
},
|
||||
operations: {
|
||||
_publishLocation: (view) =>
|
||||
@@ -528,6 +528,8 @@ export class StyleManager extends ShellFacade {
|
||||
_locationSearch: this._locationSearch,
|
||||
},
|
||||
operations: {
|
||||
_closeLocationSearch: (options) =>
|
||||
this._locationNavigation?.closeSearch(options),
|
||||
setStyle: (...args) => this.setStyle(...args),
|
||||
_updateHudButtonState: (...args) => this._updateHudButtonState(...args),
|
||||
_syncShareState: (...args) => this._syncShareState(...args),
|
||||
|
||||
@@ -168,7 +168,6 @@ test('the ORBIT indicator sits on the body while offered and lights while orbiti
|
||||
const controls = new LocationControls({
|
||||
elements: {
|
||||
search: $('#location-search'),
|
||||
searchToggle: $('#search-toggle'),
|
||||
},
|
||||
cities: {},
|
||||
getExpandedCity: () => null,
|
||||
@@ -209,7 +208,6 @@ test('the location tray renders what LocationControls publishes', async (t) => {
|
||||
const controls = new LocationControls({
|
||||
elements: {
|
||||
search: $('#location-search'),
|
||||
searchToggle: $('#search-toggle'),
|
||||
},
|
||||
cities: {
|
||||
paris: { name: 'Paris', pois: [{ name: 'Louvre' }, { name: 'Eiffel' }] },
|
||||
@@ -455,3 +453,36 @@ test('closing a sheet returns focus to its tab once the tab bar shows it closed'
|
||||
);
|
||||
assert.equal($('#mobile-sheet-backdrop').hidden, false);
|
||||
});
|
||||
|
||||
test('the search toggle opens the field, and a lookup marks it', async (t) => {
|
||||
const { $, pinia } = fixture(t);
|
||||
const store = useLocationStore(pinia);
|
||||
const controls = new LocationControls({
|
||||
elements: { search: $('#location-search') },
|
||||
cities: {},
|
||||
getExpandedCity: () => null,
|
||||
onCity() {},
|
||||
onPoi() {},
|
||||
onSearch() {},
|
||||
onReset() {},
|
||||
publish: (view) => store.publish(view),
|
||||
requestFrame() {},
|
||||
cancelFrame() {},
|
||||
});
|
||||
t.after(() => controls.destroy());
|
||||
store.connect(controls);
|
||||
await nextTick();
|
||||
const field = $('#location-search');
|
||||
assert.equal(field.classList.contains('expanded'), false);
|
||||
$('#search-toggle').click();
|
||||
await nextTick();
|
||||
assert.equal(field.classList.contains('expanded'), true);
|
||||
assert.equal(document.activeElement, field);
|
||||
controls.setSearching(true);
|
||||
await nextTick();
|
||||
assert.equal(field.classList.contains('searching'), true);
|
||||
controls.settleSearch();
|
||||
await nextTick();
|
||||
assert.equal(field.classList.contains('searching'), false);
|
||||
assert.equal(field.classList.contains('expanded'), false);
|
||||
});
|
||||
@@ -55,13 +55,7 @@ export class DisplayBindings {
|
||||
searchInput: this._locationSearch,
|
||||
actions: {
|
||||
setStyle: (style) => this.setStyle(style, { userInitiated: true }),
|
||||
dismissSearch: () => {
|
||||
if (this._locationSearch.classList.contains('expanded')) {
|
||||
this._locationSearch.classList.remove('expanded');
|
||||
this._locationSearch.value = '';
|
||||
this._locationSearch.blur();
|
||||
}
|
||||
},
|
||||
dismissSearch: () => this._closeLocationSearch?.({ ifOpen: true }),
|
||||
toggleHud: () => {
|
||||
this.shareLinkManager?.claimRestoreLane?.('visual');
|
||||
this.hud.toggle();
|
||||
|
||||
@@ -19,8 +19,9 @@ const LOCATION_MINI_SEARCHED_PLACEHOLDER = Object.freeze(
|
||||
* mini status, the search error and the ORBIT indicator are presented as a
|
||||
* plain view and handed to `publish`; Core's dock renders them and calls
|
||||
* `chooseCity()` and `choosePoi()`, and the scene chrome shows the indicator.
|
||||
* The globe actions and the cockpit call `reset()`. The search field keeps its
|
||||
* listeners.
|
||||
* The globe actions and the cockpit call `reset()`. The search field's open
|
||||
* and searching state is presented too, and the dock's toggle calls
|
||||
* `toggleSearch()`; the field keeps its own key, input and blur listeners.
|
||||
*/
|
||||
export class LocationControls {
|
||||
constructor({
|
||||
@@ -32,6 +33,7 @@ export class LocationControls {
|
||||
onSearch,
|
||||
onSearchEdit,
|
||||
onReset,
|
||||
searchExpanded = false,
|
||||
publish = () => {},
|
||||
doc = document,
|
||||
requestFrame = (callback) => requestAnimationFrame(callback),
|
||||
@@ -63,6 +65,9 @@ export class LocationControls {
|
||||
poi: t('Landmark: --'),
|
||||
};
|
||||
this.searchError = null;
|
||||
// The search field: whether it is open, and whether a lookup runs.
|
||||
this.searchExpanded = Boolean(searchExpanded);
|
||||
this.searching = false;
|
||||
// The ORBIT indicator: null until the shell offers it, then whether the
|
||||
// camera orbits.
|
||||
this.orbit = null;
|
||||
@@ -78,11 +83,6 @@ export class LocationControls {
|
||||
if (index !== -1 && index < (cities[cityId]?.pois.length || 0))
|
||||
onPoi(cityId, index);
|
||||
});
|
||||
this.bind(elements.searchToggle, 'click', () => {
|
||||
elements.search.classList.toggle('expanded');
|
||||
if (elements.search.classList.contains('expanded'))
|
||||
elements.search.focus();
|
||||
});
|
||||
this.bind(elements.search, 'keydown', (event) => {
|
||||
if (event.key === 'Enter') void onSearch(elements.search.value);
|
||||
});
|
||||
@@ -124,6 +124,8 @@ export class LocationControls {
|
||||
poisExpanded: this.poisExpanded,
|
||||
status: { ...this.status },
|
||||
searchError: this.searchError,
|
||||
searchExpanded: this.searchExpanded,
|
||||
searching: this.searching,
|
||||
orbit: this.orbit && { ...this.orbit },
|
||||
};
|
||||
}
|
||||
@@ -208,6 +210,43 @@ export class LocationControls {
|
||||
this.searchError = message || null;
|
||||
this.present();
|
||||
}
|
||||
/** The search toggle was clicked: open the field and focus it, or close it. */
|
||||
toggleSearch() {
|
||||
if (this.destroyed) return;
|
||||
this.searchExpanded = !this.searchExpanded;
|
||||
this.present();
|
||||
if (this.searchExpanded) this.elements.search?.focus();
|
||||
}
|
||||
/** Mark the search field while a lookup runs. */
|
||||
setSearching(active) {
|
||||
if (this.destroyed) return;
|
||||
this.searching = Boolean(active);
|
||||
this.present();
|
||||
}
|
||||
/**
|
||||
* A lookup settled. One that found nothing keeps its query, the open field
|
||||
* and focus so it can be corrected; any other clears and closes the field.
|
||||
*/
|
||||
settleSearch({ keepQuery = false } = {}) {
|
||||
if (this.destroyed) return;
|
||||
this.searching = false;
|
||||
if (!keepQuery) {
|
||||
this.closeSearch();
|
||||
return;
|
||||
}
|
||||
this.present();
|
||||
this.elements.search?.focus();
|
||||
this.elements.search?.select?.();
|
||||
}
|
||||
/** Close the search field and drop its query. */
|
||||
closeSearch() {
|
||||
if (this.destroyed) return;
|
||||
this.searchExpanded = false;
|
||||
this.present();
|
||||
const field = this.elements.search;
|
||||
if (field) field.value = '';
|
||||
field?.blur?.();
|
||||
}
|
||||
/** Offer the ORBIT indicator, lit while the camera orbits. */
|
||||
showOrbit(active = false) {
|
||||
if (this.destroyed) return;
|
||||
|
||||
@@ -57,10 +57,9 @@ function node() {
|
||||
},
|
||||
};
|
||||
}
|
||||
function fixture() {
|
||||
function fixture(options = {}) {
|
||||
const elements = {
|
||||
search: node(),
|
||||
searchToggle: node(),
|
||||
};
|
||||
const doc = node();
|
||||
doc.createElement = node;
|
||||
@@ -90,6 +89,7 @@ function fixture() {
|
||||
return id;
|
||||
},
|
||||
cancelFrame: (id) => cancelled.push(id),
|
||||
...options,
|
||||
});
|
||||
const view = () => views.at(-1);
|
||||
return { elements, doc, controls, calls, frames, cancelled, views, view };
|
||||
@@ -106,6 +106,8 @@ test('the tray presents its cities, empty status and no landmarks at first', ()
|
||||
poisExpanded: false,
|
||||
status: { city: '📍 Location: --', poi: 'Landmark: --' },
|
||||
searchError: null,
|
||||
searchExpanded: false,
|
||||
searching: false,
|
||||
orbit: null,
|
||||
});
|
||||
f.controls.highlightCity('b');
|
||||
@@ -199,3 +201,66 @@ test('typing clears a failed search error and closing the emptied field drops it
|
||||
f.elements.search.fire('blur');
|
||||
assert.deepEqual(f.calls, [['edit'], ['edit']]);
|
||||
});
|
||||
test('the search field opens and closes through the toggle and marks a lookup', () => {
|
||||
const f = fixture();
|
||||
const field = f.elements.search;
|
||||
f.controls.toggleSearch();
|
||||
assert.equal(f.view().searchExpanded, true);
|
||||
assert.equal(field.focused, true);
|
||||
f.controls.setSearching(true);
|
||||
assert.equal(f.view().searching, true);
|
||||
f.controls.toggleSearch();
|
||||
assert.equal(f.view().searchExpanded, false);
|
||||
f.controls.toggleSearch();
|
||||
field.value = 'Paris';
|
||||
f.controls.closeSearch();
|
||||
assert.equal(f.view().searchExpanded, false);
|
||||
assert.equal(field.value, '');
|
||||
});
|
||||
test('a lookup that found nothing keeps its query, the open field and focus', () => {
|
||||
const f = fixture();
|
||||
const field = f.elements.search;
|
||||
let selected = false;
|
||||
field.select = () => {
|
||||
selected = true;
|
||||
};
|
||||
f.controls.toggleSearch();
|
||||
f.controls.setSearching(true);
|
||||
field.value = 'Pariss';
|
||||
field.focused = false;
|
||||
f.controls.settleSearch({ keepQuery: true });
|
||||
assert.equal(f.view().searching, false);
|
||||
assert.equal(f.view().searchExpanded, true);
|
||||
assert.equal(field.value, 'Pariss');
|
||||
assert.equal(field.focused, true);
|
||||
assert.equal(selected, true);
|
||||
});
|
||||
test('a lookup that arrived clears and closes the field', () => {
|
||||
const f = fixture();
|
||||
const field = f.elements.search;
|
||||
let blurred = false;
|
||||
field.blur = () => {
|
||||
blurred = true;
|
||||
};
|
||||
f.controls.toggleSearch();
|
||||
f.controls.setSearching(true);
|
||||
field.value = 'Paris';
|
||||
f.controls.settleSearch();
|
||||
assert.equal(f.view().searching, false);
|
||||
assert.equal(f.view().searchExpanded, false);
|
||||
assert.equal(field.value, '');
|
||||
assert.equal(blurred, true);
|
||||
// Destroyed controls change nothing.
|
||||
f.controls.destroy();
|
||||
const after = f.views.length;
|
||||
f.controls.toggleSearch();
|
||||
f.controls.setSearching(true);
|
||||
f.controls.settleSearch();
|
||||
f.controls.closeSearch();
|
||||
assert.equal(f.views.length, after);
|
||||
});
|
||||
test('controls that replace others can start with the field open', () => {
|
||||
const f = fixture({ searchExpanded: true });
|
||||
assert.equal(f.controls.presentation().searchExpanded, true);
|
||||
assert.equal(f.elements.search.focused, undefined);
|
||||
});
|
||||
@@ -115,11 +115,14 @@ export class LocationNavigation {
|
||||
|
||||
_initLocationBar() {
|
||||
const { CITY_POIS, searchAndFlyTo, LocationSearch } = this.services;
|
||||
// An open field stays open in the new controls.
|
||||
const searchExpanded = Boolean(this._locationControls?.searchExpanded);
|
||||
this._locationControls?.destroy();
|
||||
this._locationLookupUnsubscribe?.();
|
||||
this._locationLookup?.destroy();
|
||||
this._locationLookup = new LocationSearch({
|
||||
input: this._locationSearch,
|
||||
setSearching: (active) => this._locationControls?.setSearching(active),
|
||||
begin: () => this._beginDeferredNavigation('location'),
|
||||
isCurrent: (generation) =>
|
||||
!this._disposed && generation === this._navigationGeneration,
|
||||
@@ -135,15 +138,9 @@ export class LocationNavigation {
|
||||
},
|
||||
onError: (error) => console.error('[Search] Geocoding failed:', error),
|
||||
});
|
||||
this._locationLookupUnsubscribe = this._locationLookup.subscribe(
|
||||
({ initial, change }) => {
|
||||
this._locationState.publish(initial ? { type: 'reset' } : change);
|
||||
},
|
||||
);
|
||||
this._locationControls = new LocationControls({
|
||||
elements: {
|
||||
search: this._locationSearch,
|
||||
searchToggle: this._searchToggle,
|
||||
},
|
||||
publish: (view) => this._publishLocation?.(view),
|
||||
cities: CITY_POIS,
|
||||
@@ -153,7 +150,15 @@ export class LocationNavigation {
|
||||
onSearch: (query) => this._locationLookup.run(query),
|
||||
onSearchEdit: () => this._setLocationSearchError(null),
|
||||
onReset: () => this.resetToGlobeView(),
|
||||
searchExpanded,
|
||||
});
|
||||
// The new lookup's reset settles a replaced lookup's search through the
|
||||
// new controls, so it subscribes once they are in place.
|
||||
this._locationLookupUnsubscribe = this._locationLookup.subscribe(
|
||||
({ initial, change }) => {
|
||||
this._locationState.publish(initial ? { type: 'reset' } : change);
|
||||
},
|
||||
);
|
||||
this._connectLocation?.(this._locationControls);
|
||||
this._connectGlobeReset?.(this._locationControls);
|
||||
}
|
||||
@@ -412,6 +417,18 @@ export class LocationNavigation {
|
||||
return resetPromise;
|
||||
}
|
||||
|
||||
/** Settle the search field once its lookup has (LocationControls.settleSearch). */
|
||||
settleSearch(options) {
|
||||
this._locationControls?.settleSearch(options);
|
||||
}
|
||||
|
||||
/** Close the search field; with `ifOpen`, only when it is open. */
|
||||
closeSearch({ ifOpen = false } = {}) {
|
||||
const controls = this._locationControls;
|
||||
if (ifOpen && !controls?.searchExpanded) return;
|
||||
controls?.closeSearch();
|
||||
}
|
||||
|
||||
/** Revoke callbacks and settle owned camera work before the viewer is released. */
|
||||
destroy() {
|
||||
if (this._disposed) return;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createStateChannel } from '../app/stateChannel.js';
|
||||
export class LocationSearch {
|
||||
constructor({
|
||||
input,
|
||||
setSearching = () => {},
|
||||
begin,
|
||||
isCurrent,
|
||||
beforeFly,
|
||||
@@ -16,6 +17,7 @@ export class LocationSearch {
|
||||
}) {
|
||||
Object.assign(this, {
|
||||
input,
|
||||
setSearching,
|
||||
begin,
|
||||
isCurrent,
|
||||
beforeFly,
|
||||
@@ -51,8 +53,8 @@ export class LocationSearch {
|
||||
if (!query || this.destroyed) return;
|
||||
const authority = this.begin();
|
||||
if (authority === false) {
|
||||
this.input.classList.remove('searching');
|
||||
this.input.blur();
|
||||
this.setSearching(false);
|
||||
this.input?.blur();
|
||||
return;
|
||||
}
|
||||
this.controller?.abort();
|
||||
@@ -81,7 +83,7 @@ export class LocationSearch {
|
||||
try {
|
||||
this.onStart?.(authority);
|
||||
if (!current() || controller.signal.aborted) return;
|
||||
this.input.classList.add('searching');
|
||||
this.setSearching(true);
|
||||
this.channel.publish(change('started'));
|
||||
if (!current() || controller.signal.aborted) return;
|
||||
const destination = await this.search(query, {
|
||||
|
||||
@@ -30,34 +30,32 @@ function input() {
|
||||
};
|
||||
}
|
||||
|
||||
function settle(searchInput, options) {
|
||||
const owner = { _activeLocationSearchGeneration: 4, searchInput };
|
||||
function settle(options) {
|
||||
const asked = [];
|
||||
const owner = {
|
||||
_activeLocationSearchGeneration: 4,
|
||||
settleSearch: (settled) => asked.push(settled),
|
||||
};
|
||||
NavigationController.prototype._settleLocationSearchUi.call(
|
||||
owner,
|
||||
4,
|
||||
options,
|
||||
);
|
||||
return owner;
|
||||
return { owner, asked };
|
||||
}
|
||||
|
||||
test('a search that found nothing keeps its query, the open field and focus', () => {
|
||||
const field = input();
|
||||
const owner = settle(field, { keepQuery: true });
|
||||
// The location controls keep or clear the field (src/ui/locationControls.test.mjs).
|
||||
test('a search that found nothing asks the field to keep its query', () => {
|
||||
const { owner, asked } = settle({ keepQuery: true });
|
||||
assert.equal(owner._activeLocationSearchGeneration, null);
|
||||
assert.equal(field.value, 'Pariss');
|
||||
assert.equal(field.classList.contains('expanded'), true);
|
||||
assert.equal(field.classList.contains('searching'), false);
|
||||
assert.equal(field.focused, true);
|
||||
assert.equal(field.selected, true);
|
||||
assert.deepEqual(asked, [{ keepQuery: true }]);
|
||||
});
|
||||
|
||||
test('a search that arrived still clears and closes the field', () => {
|
||||
const field = input();
|
||||
field.focused = true;
|
||||
settle(field);
|
||||
assert.equal(field.value, '');
|
||||
assert.equal(field.classList.contains('expanded'), false);
|
||||
assert.equal(field.focused, false);
|
||||
test('a search that arrived asks the field to clear, once', () => {
|
||||
const { owner, asked } = settle();
|
||||
assert.deepEqual(asked, [{ keepQuery: false }]);
|
||||
NavigationController.prototype._settleLocationSearchUi.call(owner, 4);
|
||||
assert.deepEqual(asked, [{ keepQuery: false }]);
|
||||
});
|
||||
|
||||
function navigation() {
|
||||
@@ -103,3 +101,21 @@ for (const [status, text] of [
|
||||
assert.equal(n.errors.at(-1), null);
|
||||
});
|
||||
}
|
||||
|
||||
test('Escape closes the search field only while it is open; panel collapse always', () => {
|
||||
let closed = 0;
|
||||
const owner = {
|
||||
_locationControls: {
|
||||
searchExpanded: false,
|
||||
closeSearch: () => (closed += 1),
|
||||
},
|
||||
};
|
||||
LocationNavigation.prototype.closeSearch.call(owner, { ifOpen: true });
|
||||
assert.equal(closed, 0);
|
||||
owner._locationControls.searchExpanded = true;
|
||||
LocationNavigation.prototype.closeSearch.call(owner, { ifOpen: true });
|
||||
assert.equal(closed, 1);
|
||||
owner._locationControls.searchExpanded = false;
|
||||
LocationNavigation.prototype.closeSearch.call(owner);
|
||||
assert.equal(closed, 2);
|
||||
});
|
||||
@@ -12,7 +12,7 @@ export class NavigationController {
|
||||
constructor({
|
||||
viewer,
|
||||
tracking,
|
||||
searchInput,
|
||||
settleSearch = () => {},
|
||||
interruptCameraMotion,
|
||||
interruptGlobeRotation = () => {},
|
||||
isCockpitActive,
|
||||
@@ -26,7 +26,7 @@ export class NavigationController {
|
||||
Object.assign(this, {
|
||||
viewer,
|
||||
tracking,
|
||||
searchInput,
|
||||
settleSearch,
|
||||
interruptCameraMotion,
|
||||
interruptGlobeRotation,
|
||||
isCockpitActive,
|
||||
@@ -123,15 +123,7 @@ export class NavigationController {
|
||||
_settleLocationSearchUi(generation, { keepQuery = false } = {}) {
|
||||
if (this._activeLocationSearchGeneration !== generation) return;
|
||||
this._activeLocationSearchGeneration = null;
|
||||
this.searchInput?.classList.remove('searching');
|
||||
if (keepQuery) {
|
||||
this.searchInput?.focus();
|
||||
this.searchInput?.select?.();
|
||||
return;
|
||||
}
|
||||
this.searchInput?.classList.remove('expanded');
|
||||
if (this.searchInput) this.searchInput.value = '';
|
||||
this.searchInput?.blur();
|
||||
this.settleSearch({ keepQuery });
|
||||
}
|
||||
|
||||
_releaseFollowCamera({
|
||||
|
||||
@@ -176,10 +176,7 @@ export class PanelChrome {
|
||||
onChange: (collapsed, options) =>
|
||||
this.setPanelCollapsed(panelId, collapsed, options),
|
||||
beforeCollapse: () => {
|
||||
if (panelId !== 'location-bar' || !this._locationSearch) return;
|
||||
this._locationSearch.classList.remove('expanded');
|
||||
this._locationSearch.value = '';
|
||||
this._locationSearch.blur();
|
||||
if (panelId === 'location-bar') this._closeLocationSearch?.();
|
||||
},
|
||||
// A desktop side panel hides entirely once collapsed; its rail item
|
||||
// takes the focus its own disclosure can no longer hold.
|
||||
|
||||
@@ -53,7 +53,6 @@ test('ru: location pills and the mini-status show the translated city', () => {
|
||||
setLocale('ru');
|
||||
const elements = {
|
||||
search: element(),
|
||||
searchToggle: element(),
|
||||
};
|
||||
const doc = { ...element(), createElement: element };
|
||||
const cities = { tokyo: CITY_POIS.tokyo };
|
||||
|
||||
@@ -64,7 +64,6 @@ export function readShellElements(document = globalThis.document) {
|
||||
_cctvMeta: document.getElementById('cctv-meta'),
|
||||
_cctvSummary: document.getElementById('cctv-summary'),
|
||||
_locationSearch: document.getElementById('location-search'),
|
||||
_searchToggle: document.getElementById('search-toggle'),
|
||||
_safeFrameOverlay: document.getElementById('safe-frame-overlay'),
|
||||
_safeFrameBox: document.getElementById('safe-frame-box'),
|
||||
_detectionBtn: document.getElementById('detection-toggle'),
|
||||
|
||||
@@ -17,7 +17,6 @@ function navigation() {
|
||||
return new NavigationController({
|
||||
viewer: { camera: { cancelFlight() {}, lookAtTransform() {} } },
|
||||
tracking,
|
||||
searchInput: null,
|
||||
interruptCameraMotion() {},
|
||||
isCockpitActive: () => false,
|
||||
clearLocation() {},
|
||||
|
||||
@@ -4,8 +4,9 @@ import { markRaw, shallowRef } from 'vue';
|
||||
/**
|
||||
* The dock's location tray as its view renders it. LocationControls
|
||||
* presents the city pills, the landmark row, the mini status, the search
|
||||
* error and the ORBIT indicator and publishes them here; it keeps the search
|
||||
* field's listeners.
|
||||
* error, the search field's open and searching state and the ORBIT indicator
|
||||
* and publishes them here; it keeps the search field's key, input and blur
|
||||
* listeners.
|
||||
*/
|
||||
export const useLocationStore = defineStore('gev-location', () => {
|
||||
/** The presented tray; null until the controls first render. */
|
||||
|
||||
@@ -150,10 +150,10 @@ function selectMapSource(chip) {
|
||||
<div class="location-city-row">
|
||||
<div id="location-pills" class="location-pills"><UiButton v-for="city in loc?.cities" :key="city.id" class="location-pill" :pressed="Boolean(city.active)" pressed-class="active" :data-location-id="city.id" @click="locationControls?.chooseCity(city.id)">{{ city.label }}</UiButton></div>
|
||||
<div class="location-search-wrap">
|
||||
<button id="search-toggle" class="search-toggle-btn" title="Search any location" data-i18n-title>
|
||||
<button id="search-toggle" class="search-toggle-btn" title="Search any location" data-i18n-title @click="locationControls?.toggleSearch()">
|
||||
<span>🔎</span>
|
||||
</button>
|
||||
<input type="text" id="location-search" placeholder="Search any location..." autocomplete="off" spellcheck="false" aria-label="Search location by name or coordinates" :aria-invalid="loc?.searchError ? 'true' : null" :aria-describedby="loc?.searchError ? 'location-search-error' : null" data-i18n-aria-label data-i18n-placeholder />
|
||||
<input type="text" id="location-search" v-class-toggle="{ expanded: !!loc?.searchExpanded, searching: !!loc?.searching }" placeholder="Search any location..." autocomplete="off" spellcheck="false" aria-label="Search location by name or coordinates" :aria-invalid="loc?.searchError ? 'true' : null" :aria-describedby="loc?.searchError ? 'location-search-error' : null" data-i18n-aria-label data-i18n-placeholder />
|
||||
</div>
|
||||
</div>
|
||||
<p id="location-search-error" class="location-search-error" role="alert">{{ loc?.searchError }}</p>
|
||||
|
||||
Reference in new issue
Block a user