feat(dees-geo-map): Highlight current navigation step with progress, mark completed steps, auto-scroll turn-by-turn list, expose guidance state for synchronization, and refine instruction/voice wording

This commit is contained in:
2026-02-05 22:51:41 +00:00
parent 0617822116
commit 89670ecad3
7 changed files with 118 additions and 9 deletions

View File

@@ -3,6 +3,7 @@ import maplibregl from 'maplibre-gl';
import { renderIcon } from './geo-map.icons.js';
import { type INominatimResult } from './geo-map.search.js';
import type { ITrafficAwareRoute } from './geo-map.traffic.providers.js';
import type { INavigationGuideState } from './geo-map.navigation-guide.js';
// ─── Navigation/Routing Types ────────────────────────────────────────────────
@@ -29,6 +30,7 @@ export interface IOSRMStep {
location: [number, number];
};
name: string; // Street name
ref?: string; // Road reference (A1, M25, etc.)
distance: number;
duration: number;
driving_side: string;
@@ -69,6 +71,8 @@ export interface INavigationControllerCallbacks {
onTrafficToggle?: (enabled: boolean) => void;
/** Optional callback to get current traffic state */
getTrafficEnabled?: () => boolean;
/** Optional callback to get current guidance state for turn-by-turn synchronization */
getGuidanceState?: () => INavigationGuideState | null;
}
/**
@@ -742,7 +746,7 @@ export class NavigationController {
*/
public formatStepInstruction(step: IOSRMStep): string {
const { type, modifier } = step.maneuver;
const name = step.name || 'unnamed road';
const name = step.name || step.ref || 'unnamed road';
switch (type) {
case 'depart':
@@ -767,7 +771,7 @@ export class NavigationController {
case 'end of road':
return `At the end of the road, turn ${modifier || ''} onto ${name}`;
case 'new name':
return `Continue onto ${name}`;
return `Continue on ${name}`;
default:
return `${type} ${modifier || ''} on ${name}`.trim();
}
@@ -1044,20 +1048,44 @@ export class NavigationController {
}
const steps = route.legs.flatMap(leg => leg.steps);
const guidanceState = this.callbacks.getGuidanceState?.();
const isNavigating = guidanceState?.isNavigating ?? false;
const currentStepIndex = guidanceState?.currentStepIndex ?? -1;
if (steps.length === 0) {
return html`<div class="nav-empty-steps">No turn-by-turn directions available</div>`;
}
return html`
${steps.map(step => {
${steps.map((step, index) => {
const icon = this.getManeuverIcon(step.maneuver.type, step.maneuver.modifier);
const instruction = this.formatStepInstruction(step);
const distance = this.formatDistance(step.distance);
// currentStepIndex points to the maneuver we're approaching,
// but we're traveling on the PREVIOUS step's road
const isCurrent = isNavigating && index === currentStepIndex - 1;
const isCompleted = isNavigating && index < currentStepIndex - 1;
// Calculate progress percentage for current step
let progressPercent = 0;
if (isCurrent && step.distance > 0) {
const distanceRemaining = guidanceState?.distanceToNextManeuver ?? step.distance;
progressPercent = Math.max(0, Math.min(100,
((step.distance - distanceRemaining) / step.distance) * 100
));
}
return html`
<div class="nav-step" @click=${() => this.flyToStep(step)}>
<div class="nav-step-icon">${icon}</div>
<div
class="nav-step ${isCurrent ? 'current' : ''} ${isCompleted ? 'completed' : ''}"
@click=${() => this.flyToStep(step)}
data-step-index="${index}"
>
${isCurrent ? html`
<div class="nav-step-progress-bar" style="width: ${progressPercent}%"></div>
` : ''}
<div class="nav-step-icon">${isCompleted ? '✓' : icon}</div>
<div class="nav-step-content">
<div class="nav-step-instruction">${instruction}</div>
<div class="nav-step-distance">${distance}</div>
@@ -1067,4 +1095,26 @@ export class NavigationController {
})}
`;
}
/**
* Scroll the turn-by-turn list to show the current step
* Called externally when guidance state changes
*/
public scrollToCurrentStep(stepIndex: number): void {
// Use requestAnimationFrame to ensure DOM is updated
requestAnimationFrame(() => {
// Find elements in document - they may be in shadow DOM
const stepsContainer = document.querySelector('.nav-steps')
?? document.querySelector('dees-geo-map')?.shadowRoot?.querySelector('.nav-steps');
const currentStep = document.querySelector(`.nav-step[data-step-index="${stepIndex}"]`)
?? document.querySelector('dees-geo-map')?.shadowRoot?.querySelector(`.nav-step[data-step-index="${stepIndex}"]`);
if (stepsContainer && currentStep) {
(currentStep as HTMLElement).scrollIntoView({
behavior: 'smooth',
block: 'nearest',
});
}
});
}
}