feat(radio): hand the tuner slider's events to RadioControls through the store

RadioTuner binds the slider's pointer, key and value events in its
template and calls RadioControls through the radio store; the drag,
its slot and the pointer capture stay with RadioControls, unchanged.
This commit is contained in:
Sameh Khamis committed 2026-11-01 11:06:54 -08:00
1 parent 7f7e66e793
commit fa2dc9fc87
4 files changed
+72 -113

No files matched your search

+52 -101
View File
@@ -382,7 +382,47 @@ export function bindRadioControls() {
},
tunerListenerOptions,
);
// The Radio panel's and Cockpit's controls call these through the store.
const updateTunerFromPointer = (event) => {
const rect = this._radioTunerSlider?.getBoundingClientRect();
if (!rect || !this._radioTunerStations.length) return false;
const position = radioTunerPointerPosition(
event.clientX,
rect.left,
rect.width,
this._radioTunerStations.length,
);
if (position.stationIndex > this._radioTunerLastSlot)
this._radioTunerDragDirection = 1;
else if (position.stationIndex < this._radioTunerLastSlot)
this._radioTunerDragDirection = -1;
this._radioTunerLastSlot = position.stationIndex;
this._radioTunerCoordinate = position.coordinate;
showRadioTuner.call(this, { slot: position.stationIndex });
tunerPreview({ coordinate: position.coordinate });
return true;
};
const releaseNavigationBand = () => {
this._radioTunerBandPinnedForNavigation = false;
};
this.listen(
this.canvas,
'pointerdown',
releaseNavigationBand,
tunerListenerOptions,
);
this.listen(
this.canvas,
'wheel',
releaseNavigationBand,
tunerListenerOptions,
);
// The directory order is catalog/filter authority, not camera authority.
// Globe motion therefore never rebuilds or re-ranks the frequency band.
this._radioSelectedHandler = () =>
this.actions.setPanelCollapsed('radio-panel', false);
this.listen(document, 'gev:radio-selected', this._radioSelectedHandler);
// The Radio panel's and Cockpit's controls, and the tuner slider's events,
// call these through the store.
// Cockpit owns the Cesium camera even though it intentionally clears
// viewer.trackedEntity, so its station steps pass `rotate: false`: they
// must never start the map-view rotation/fallback flights that would
@@ -421,30 +461,7 @@ export function bindRadioControls() {
showRadioVolume.call(this, value);
this.actions.setParams({ volume: value / 100 }, { origin: 'user' });
},
});
const updateTunerFromPointer = (event) => {
const rect = this._radioTunerSlider?.getBoundingClientRect();
if (!rect || !this._radioTunerStations.length) return false;
const position = radioTunerPointerPosition(
event.clientX,
rect.left,
rect.width,
this._radioTunerStations.length,
);
if (position.stationIndex > this._radioTunerLastSlot)
this._radioTunerDragDirection = 1;
else if (position.stationIndex < this._radioTunerLastSlot)
this._radioTunerDragDirection = -1;
this._radioTunerLastSlot = position.stationIndex;
this._radioTunerCoordinate = position.coordinate;
showRadioTuner.call(this, { slot: position.stationIndex });
tunerPreview({ coordinate: position.coordinate });
return true;
};
this.listen(
this._radioTunerSlider,
'pointerdown',
(event) => {
tunerPointerDown: (event) => {
if (!beginTuner()) return;
this._radioTunerPointerId = event.pointerId;
this._radioTunerSlider.focus({ preventScroll: true });
@@ -456,12 +473,7 @@ export function bindRadioControls() {
updateTunerFromPointer(event);
event.preventDefault();
},
tunerListenerOptions,
);
this.listen(
this._radioTunerSlider,
'pointermove',
(event) => {
tunerPointerMove: (event) => {
if (
!this._radioTunerDragging ||
this._radioTunerPointerId !== event.pointerId
@@ -470,12 +482,7 @@ export function bindRadioControls() {
updateTunerFromPointer(event);
event.preventDefault();
},
tunerListenerOptions,
);
this.listen(
this._radioTunerSlider,
'input',
() => {
tunerInput: () => {
if (this._radioTunerPointerId !== null || this._radioTunerKeyboardKey)
return;
// A drag the input starts begins from the selected station's slot.
@@ -496,21 +503,11 @@ export function bindRadioControls() {
this._radioTunerCoordinate = inputSlot.stationIndex;
tunerPreview({ coordinate: this._radioTunerCoordinate });
},
tunerListenerOptions,
);
this.listen(
this._radioTunerSlider,
'change',
() => {
tunerChange: () => {
if (this._radioTunerPointerId === null && !this._radioTunerKeyboardKey)
finishTuner(true);
},
tunerListenerOptions,
);
this.listen(
this._radioTunerSlider,
'pointerup',
(event) => {
tunerPointerUp: (event) => {
if (this._radioTunerPointerId !== event.pointerId) return;
updateTunerFromPointer(event);
finishTuner(true);
@@ -521,12 +518,7 @@ export function bindRadioControls() {
}
event.preventDefault();
},
tunerListenerOptions,
);
this.listen(
this._radioTunerSlider,
'pointercancel',
(event) => {
tunerPointerCancel: (event) => {
if (
this._radioTunerPointerId !== null &&
this._radioTunerPointerId !== event.pointerId
@@ -539,24 +531,14 @@ export function bindRadioControls() {
}
finishTuner(false);
},
tunerListenerOptions,
);
this.listen(
this._radioTunerSlider,
'lostpointercapture',
(event) => {
tunerLostPointerCapture: (event) => {
if (
this._radioTunerDragging &&
this._radioTunerPointerId === event.pointerId
)
finishTuner(false);
},
tunerListenerOptions,
);
this.listen(
this._radioTunerSlider,
'keydown',
(event) => {
tunerKeyDown: (event) => {
if (event.key === 'Escape' && this._radioTunerDragging) {
event.preventDefault();
finishTuner(false);
@@ -602,12 +584,7 @@ export function bindRadioControls() {
showRadioTuner.call(this, { slot: slot.slot });
tunerPreview({ coordinate: this._radioTunerCoordinate });
},
tunerListenerOptions,
);
this.listen(
this._radioTunerSlider,
'keyup',
(event) => {
tunerKeyUp: (event) => {
if (
!this._radioTunerKeyboardKey ||
event.key !== this._radioTunerKeyboardKey
@@ -616,32 +593,6 @@ export function bindRadioControls() {
event.preventDefault();
finishTuner(true);
},
tunerListenerOptions,
);
this.listen(
this._radioTunerSlider,
'blur',
() => finishTuner(true),
tunerListenerOptions,
);
const releaseNavigationBand = () => {
this._radioTunerBandPinnedForNavigation = false;
};
this.listen(
this.canvas,
'pointerdown',
releaseNavigationBand,
tunerListenerOptions,
);
this.listen(
this.canvas,
'wheel',
releaseNavigationBand,
tunerListenerOptions,
);
// The directory order is catalog/filter authority, not camera authority.
// Globe motion therefore never rebuilds or re-ranks the frequency band.
this._radioSelectedHandler = () =>
this.actions.setPanelCollapsed('radio-panel', false);
this.listen(document, 'gev:radio-selected', this._radioSelectedHandler);
tunerBlur: () => finishTuner(true),
});
}
+11 -5
View File
@@ -15,11 +15,15 @@ import { useRadioStore } from '../../stores/radio.js';
// The Radio panel's directory tuner. RadioControls owns the drag and
// publishes the band, the readouts, the slider and where the needle sits;
// until it does, the tuner shows its initial markup. The tick tape is laid
// out here against the dial's width. A hidden dial measures 0 wide, so the
// last real width is kept and no ticks are drawn before there is one: ticks
// laid out for no width would slide into place when the tuner appears.
const { tuner } = storeToRefs(useRadioStore());
// until it does, the tuner shows its initial markup. The slider hands its
// pointer, key and value events to RadioControls through the store. The
// tick tape is laid out here against the dial's width. A hidden dial
// measures 0 wide, so the last real width is kept and no ticks are drawn
// before there is one: ticks laid out for no width would slide into place
// when the tuner appears.
const store = useRadioStore();
const { act } = store;
const { tuner } = storeToRefs(store);
const dial = useTemplateRef('dial');
const dialWidth = shallowRef(0);
let resizeObserver = null;
@@ -68,6 +72,8 @@ const vTunerDragging = {
<div class="radio-tuner-scale" aria-hidden="true" :style="tape && { '--radio-tuner-tick-pitch': `${tape.pitchPx}px` }"><span v-for="(tick, index) in tape?.ticks ?? []" :key="index" class="radio-tuner-tick" :class="{ 'is-current': tick.current }" :style="{ left: `${tick.xPx}px` }" :data-station-index="tick.stationIndex">{{ tick.label }}</span></div>
<div id="radio-tuner-needle" class="radio-tuner-needle" aria-hidden="true"></div>
<input id="radio-tuner-slider" type="range" min="0" :max="tuner ? tuner.max : 0" step="1" value="0" v-range-value="tuner ? tuner.slot : 0"
@pointerdown="act('tunerPointerDown', $event)" @pointermove="act('tunerPointerMove', $event)" @pointerup="act('tunerPointerUp', $event)" @pointercancel="act('tunerPointerCancel', $event)" @lostpointercapture="act('tunerLostPointerCapture', $event)"
@keydown="act('tunerKeyDown', $event)" @keyup="act('tunerKeyUp', $event)" @input="act('tunerInput')" @change="act('tunerChange')" @blur="act('tunerBlur')"
aria-label="Tune available internet radio stations" :aria-valuetext="tuner?.valuetext ?? t('No station available')" :disabled="tuner ? !tuner.available : true" data-i18n-aria-label />
</div>
<div class="radio-tuner-readout">