fix(web): keep Chrome's menu from grabbing focus after Alt+wheel zoom
46136b1 parent: 50c9d0c modified
web/src/lib/composables.ts +16 -0 | @@ -25,3 +25,19 @@ export function usePolling(tick: () => Promise<number>) { | ||
| 25 | 25 | onMounted(loop); |
| 26 | 26 | onUnmounted(() => { stopped = true; clearTimeout(timer); }); |
| 27 | 27 | } |
| 28 | + | |
| 29 | +/** | |
| 30 | + * Sous Windows, relâcher Alt donne le focus au menu de Chrome (et Espace l'ouvre ensuite). Après un | |
| 31 | + * Alt+molette, on annule ce relâchement : appeler la fonction renvoyée à chaque molette avec Alt. | |
| 32 | + */ | |
| 33 | +export function useAltWheel(): () => void { | |
| 34 | + let used = false; | |
| 35 | + const onKeyUp = (e: KeyboardEvent) => { | |
| 36 | + if (e.key !== "Alt") return; | |
| 37 | + if (used) e.preventDefault(); | |
| 38 | + used = false; | |
| 39 | + }; | |
| 40 | + onMounted(() => window.addEventListener("keyup", onKeyUp)); | |
| 41 | + onUnmounted(() => window.removeEventListener("keyup", onKeyUp)); | |
| 42 | + return () => { used = true; }; | |
| 43 | +} | |
| @@ -25,3 +25,19 @@ export function usePolling(tick: () => Promise<number>) { | |||
| 25 | onMounted(loop); | 25 | onMounted(loop); |
| 26 | onUnmounted(() => { stopped = true; clearTimeout(timer); }); | 26 | onUnmounted(() => { stopped = true; clearTimeout(timer); }); |
| 27 | } | 27 | } |
| 28 | + | ||
| 29 | +/** | ||
| 30 | + * Sous Windows, relâcher Alt donne le focus au menu de Chrome (et Espace l'ouvre ensuite). Après un | ||
| 31 | + * Alt+molette, on annule ce relâchement : appeler la fonction renvoyée à chaque molette avec Alt. | ||
| 32 | + */ | ||
| 33 | +export function useAltWheel(): () => void { | ||
| 34 | + let used = false; | ||
| 35 | + const onKeyUp = (e: KeyboardEvent) => { | ||
| 36 | + if (e.key !== "Alt") return; | ||
| 37 | + if (used) e.preventDefault(); | ||
| 38 | + used = false; | ||
| 39 | + }; | ||
| 40 | + onMounted(() => window.addEventListener("keyup", onKeyUp)); | ||
| 41 | + onUnmounted(() => window.removeEventListener("keyup", onKeyUp)); | ||
| 42 | + return () => { used = true; }; | ||
| 43 | +} | ||
modified
web/src/project/steps/derush/Timeline.vue +3 -0 | @@ -11,6 +11,7 @@ | ||
| 11 | 11 | */ |
| 12 | 12 | import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue"; |
| 13 | 13 | import { useI18n } from "vue-i18n"; |
| 14 | +import { useAltWheel } from "../../../lib/composables"; | |
| 14 | 15 | import { mmss } from "../../../lib/format"; |
| 15 | 16 | import type { DerushClip, DerushSeg, TimelineClip } from "../../types"; |
| 16 | 17 | import { clipLen, offsets, type Selection } from "./clips"; |
| @@ -71,9 +72,11 @@ function fit() { | ||
| 71 | 72 | } |
| 72 | 73 | defineExpose({ zoomBy, fit }); |
| 73 | 74 | |
| 75 | +const altWheel = useAltWheel(); | |
| 74 | 76 | function onWheel(e: WheelEvent) { |
| 75 | 77 | // la molette seule fait défiler la page |
| 76 | 78 | if (e.altKey) { |
| 79 | + altWheel(); | |
| 77 | 80 | e.preventDefault(); |
| 78 | 81 | zoomBy(Math.pow(1.0015, -e.deltaY)); |
| 79 | 82 | } else if ((e.ctrlKey || e.metaKey) && Math.abs(e.deltaY) > Math.abs(e.deltaX)) { |
| @@ -11,6 +11,7 @@ | |||
| 11 | */ | 11 | */ |
| 12 | import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue"; | 12 | import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue"; |
| 13 | import { useI18n } from "vue-i18n"; | 13 | import { useI18n } from "vue-i18n"; |
| 14 | +import { useAltWheel } from "../../../lib/composables"; | ||
| 14 | import { mmss } from "../../../lib/format"; | 15 | import { mmss } from "../../../lib/format"; |
| 15 | import type { DerushClip, DerushSeg, TimelineClip } from "../../types"; | 16 | import type { DerushClip, DerushSeg, TimelineClip } from "../../types"; |
| 16 | import { clipLen, offsets, type Selection } from "./clips"; | 17 | import { clipLen, offsets, type Selection } from "./clips"; |
| @@ -71,9 +72,11 @@ function fit() { | |||
| 71 | } | 72 | } |
| 72 | defineExpose({ zoomBy, fit }); | 73 | defineExpose({ zoomBy, fit }); |
| 73 | 74 | ||
| 75 | +const altWheel = useAltWheel(); | ||
| 74 | function onWheel(e: WheelEvent) { | 76 | function onWheel(e: WheelEvent) { |
| 75 | // la molette seule fait défiler la page | 77 | // la molette seule fait défiler la page |
| 76 | if (e.altKey) { | 78 | if (e.altKey) { |
| 79 | + altWheel(); | ||
| 77 | e.preventDefault(); | 80 | e.preventDefault(); |
| 78 | zoomBy(Math.pow(1.0015, -e.deltaY)); | 81 | zoomBy(Math.pow(1.0015, -e.deltaY)); |
| 79 | } else if ((e.ctrlKey || e.metaKey) && Math.abs(e.deltaY) > Math.abs(e.deltaX)) { | 82 | } else if ((e.ctrlKey || e.metaKey) && Math.abs(e.deltaY) > Math.abs(e.deltaX)) { |
modified
web/src/project/steps/montage/Timeline.vue +3 -0 | @@ -12,6 +12,7 @@ | ||
| 12 | 12 | */ |
| 13 | 13 | import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue"; |
| 14 | 14 | import { useI18n } from "vue-i18n"; |
| 15 | +import { useAltWheel } from "../../../lib/composables"; | |
| 15 | 16 | import { fxName } from "../../../lib/transitions"; |
| 16 | 17 | import { MEDIA_DRAG, trimAroll } from "./ops"; |
| 17 | 18 | import { |
| @@ -106,9 +107,11 @@ function fit() { | ||
| 106 | 107 | } |
| 107 | 108 | defineExpose({ zoomBy, fit }); |
| 108 | 109 | |
| 110 | +const altWheel = useAltWheel(); | |
| 109 | 111 | function onWheel(e: WheelEvent) { |
| 110 | 112 | // la molette seule fait défiler la page (pour atteindre les pistes du bas) |
| 111 | 113 | if (e.altKey) { |
| 114 | + altWheel(); | |
| 112 | 115 | e.preventDefault(); |
| 113 | 116 | zoomBy(Math.pow(1.0015, -e.deltaY)); |
| 114 | 117 | } else if ((e.ctrlKey || e.metaKey) && Math.abs(e.deltaY) > Math.abs(e.deltaX)) { |
| @@ -12,6 +12,7 @@ | |||
| 12 | */ | 12 | */ |
| 13 | import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue"; | 13 | import { computed, nextTick, onMounted, onUnmounted, ref, watch } from "vue"; |
| 14 | import { useI18n } from "vue-i18n"; | 14 | import { useI18n } from "vue-i18n"; |
| 15 | +import { useAltWheel } from "../../../lib/composables"; | ||
| 15 | import { fxName } from "../../../lib/transitions"; | 16 | import { fxName } from "../../../lib/transitions"; |
| 16 | import { MEDIA_DRAG, trimAroll } from "./ops"; | 17 | import { MEDIA_DRAG, trimAroll } from "./ops"; |
| 17 | import { | 18 | import { |
| @@ -106,9 +107,11 @@ function fit() { | |||
| 106 | } | 107 | } |
| 107 | defineExpose({ zoomBy, fit }); | 108 | defineExpose({ zoomBy, fit }); |
| 108 | 109 | ||
| 110 | +const altWheel = useAltWheel(); | ||
| 109 | function onWheel(e: WheelEvent) { | 111 | function onWheel(e: WheelEvent) { |
| 110 | // la molette seule fait défiler la page (pour atteindre les pistes du bas) | 112 | // la molette seule fait défiler la page (pour atteindre les pistes du bas) |
| 111 | if (e.altKey) { | 113 | if (e.altKey) { |
| 114 | + altWheel(); | ||
| 112 | e.preventDefault(); | 115 | e.preventDefault(); |
| 113 | zoomBy(Math.pow(1.0015, -e.deltaY)); | 116 | zoomBy(Math.pow(1.0015, -e.deltaY)); |
| 114 | } else if ((e.ctrlKey || e.metaKey) && Math.abs(e.deltaY) > Math.abs(e.deltaX)) { | 117 | } else if ((e.ctrlKey || e.metaKey) && Math.abs(e.deltaY) > Math.abs(e.deltaX)) { |