From 0949f6a77652c1d5c47323b8d47b183ab9b670ec Mon Sep 17 00:00:00 2001 From: Sameh Khamis Date: Sun, 1 Nov 2026 13:53:31 -0800 Subject: [PATCH] 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. --- CHANGELOG.md | 11 +++- src/cameraHandoff.test.mjs | 2 +- src/panelEscape.test.mjs | 15 ++---- src/ui/applicationShell.js | 8 +-- src/ui/commandDock.test.mjs | 35 ++++++++++++- src/ui/displayBindings.js | 8 +-- src/ui/locationControls.js | 53 +++++++++++++++++--- src/ui/locationControls.test.mjs | 69 +++++++++++++++++++++++++- src/ui/locationNavigation.js | 29 ++++++++--- src/ui/locationSearch.js | 8 +-- src/ui/locationSearchRecovery.test.mjs | 52 ++++++++++++------- src/ui/navigationController.js | 14 ++---- src/ui/panelChrome.js | 5 +- src/ui/placeNames.test.mjs | 1 - src/ui/shellElements.js | 1 - src/ui/stateOwners.test.mjs | 1 - src/ui/stores/location.js | 5 +- src/ui/views/CommandDockView.vue | 4 +- 18 files changed, 238 insertions(+), 83 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f5f0e6d4..17abe6f3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). diff --git a/src/cameraHandoff.test.mjs b/src/cameraHandoff.test.mjs index 15a316366..2c0d356b2 100644 --- a/src/cameraHandoff.test.mjs +++ b/src/cameraHandoff.test.mjs @@ -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'); diff --git a/src/panelEscape.test.mjs b/src/panelEscape.test.mjs index 75eba753d..1177804d0 100644 --- a/src/panelEscape.test.mjs +++ b/src/panelEscape.test.mjs @@ -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); }); diff --git a/src/ui/applicationShell.js b/src/ui/applicationShell.js index eed23487c..1fc2c1f05 100644 --- a/src/ui/applicationShell.js +++ b/src/ui/applicationShell.js @@ -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), diff --git a/src/ui/commandDock.test.mjs b/src/ui/commandDock.test.mjs index 742cea1e6..c70007a17 100644 --- a/src/ui/commandDock.test.mjs +++ b/src/ui/commandDock.test.mjs @@ -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); +}); diff --git a/src/ui/displayBindings.js b/src/ui/displayBindings.js index 7c67f65c4..73c4fbee6 100644 --- a/src/ui/displayBindings.js +++ b/src/ui/displayBindings.js @@ -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(); diff --git a/src/ui/locationControls.js b/src/ui/locationControls.js index 8977c6fc2..5b5f06d5e 100644 --- a/src/ui/locationControls.js +++ b/src/ui/locationControls.js @@ -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; diff --git a/src/ui/locationControls.test.mjs b/src/ui/locationControls.test.mjs index b62478fc8..ccae6454b 100644 --- a/src/ui/locationControls.test.mjs +++ b/src/ui/locationControls.test.mjs @@ -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); +}); diff --git a/src/ui/locationNavigation.js b/src/ui/locationNavigation.js index f06515c6e..6f9ae4158 100644 --- a/src/ui/locationNavigation.js +++ b/src/ui/locationNavigation.js @@ -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; diff --git a/src/ui/locationSearch.js b/src/ui/locationSearch.js index 09669ca56..aeed9320e 100644 --- a/src/ui/locationSearch.js +++ b/src/ui/locationSearch.js @@ -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, { diff --git a/src/ui/locationSearchRecovery.test.mjs b/src/ui/locationSearchRecovery.test.mjs index 7372db8eb..0be6e7602 100644 --- a/src/ui/locationSearchRecovery.test.mjs +++ b/src/ui/locationSearchRecovery.test.mjs @@ -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); +}); diff --git a/src/ui/navigationController.js b/src/ui/navigationController.js index 3c0bc7684..3ea54948b 100644 --- a/src/ui/navigationController.js +++ b/src/ui/navigationController.js @@ -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({ diff --git a/src/ui/panelChrome.js b/src/ui/panelChrome.js index f389ff97c..97dcaa737 100644 --- a/src/ui/panelChrome.js +++ b/src/ui/panelChrome.js @@ -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. diff --git a/src/ui/placeNames.test.mjs b/src/ui/placeNames.test.mjs index 0e1536ffa..5ddf3152c 100644 --- a/src/ui/placeNames.test.mjs +++ b/src/ui/placeNames.test.mjs @@ -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 }; diff --git a/src/ui/shellElements.js b/src/ui/shellElements.js index fa24026aa..c512532b2 100644 --- a/src/ui/shellElements.js +++ b/src/ui/shellElements.js @@ -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'), diff --git a/src/ui/stateOwners.test.mjs b/src/ui/stateOwners.test.mjs index b3708ea73..dd9339721 100644 --- a/src/ui/stateOwners.test.mjs +++ b/src/ui/stateOwners.test.mjs @@ -17,7 +17,6 @@ function navigation() { return new NavigationController({ viewer: { camera: { cancelFlight() {}, lookAtTransform() {} } }, tracking, - searchInput: null, interruptCameraMotion() {}, isCockpitActive: () => false, clearLocation() {}, diff --git a/src/ui/stores/location.js b/src/ui/stores/location.js index 3ef58aa9a..1ef2f51b3 100644 --- a/src/ui/stores/location.js +++ b/src/ui/stores/location.js @@ -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. */ diff --git a/src/ui/views/CommandDockView.vue b/src/ui/views/CommandDockView.vue index 552d0e739..10f3463ca 100644 --- a/src/ui/views/CommandDockView.vue +++ b/src/ui/views/CommandDockView.vue @@ -150,10 +150,10 @@ function selectMapSource(chip) {
{{ city.label }}
- - +