mirror of
https://github.com/9x/sheetmusictrainer.git
synced 2026-11-03 22:03:54 +01:00
Fix Next/New no-op for seeded material + keyboard shortcuts after button clicks
- nextAction: giuliani and single-chord arpeggios now roll a new seed too (they consume melodySeed); previously the handler silently did nothing for them. Scale drills keep no Next (deterministic). - New/Next button visibility matches the same condition, and the label is 'Next' for fixed material only. - Keyboard shortcuts: HTMLButtonElement removed from the interactive gate — after any mouse click the button keeps focus and previously swallowed N/R/P/H/L/V/M. Space/Enter on a focused button still activate it natively and no longer double-fire the global pauseToggle/skip handlers.
This commit is contained in:
22
src/App.tsx
22
src/App.tsx
@@ -126,7 +126,6 @@ function App() {
|
|||||||
const onInteractive =
|
const onInteractive =
|
||||||
target instanceof HTMLInputElement ||
|
target instanceof HTMLInputElement ||
|
||||||
target instanceof HTMLTextAreaElement ||
|
target instanceof HTMLTextAreaElement ||
|
||||||
target instanceof HTMLButtonElement ||
|
|
||||||
target instanceof HTMLSelectElement ||
|
target instanceof HTMLSelectElement ||
|
||||||
!!target?.isContentEditable;
|
!!target?.isContentEditable;
|
||||||
|
|
||||||
@@ -135,12 +134,17 @@ function App() {
|
|||||||
else if (settings.zenMode) setSettings(s => ({ ...s, zenMode: false }));
|
else if (settings.zenMode) setSettings(s => ({ ...s, zenMode: false }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Focused buttons handle their own keys (native activation);
|
// Text inputs and selects must keep their native keys — shortcuts are
|
||||||
// global shortcuts must not double-fire or block them.
|
// for typing-free contexts. FOCUSED BUTTONS however stay eligible:
|
||||||
if (onInteractive) return;
|
// after a click the button keeps focus, and blocking global shortcuts
|
||||||
|
// there made N/R/P/L silently dead until the user clicked elsewhere.
|
||||||
|
// Space/Enter on a focused button activate it natively and return below,
|
||||||
|
// so no double-fire.
|
||||||
|
|
||||||
// Prevent default for space to stop scrolling
|
// Prevent default for space to stop scrolling (Space on a focused
|
||||||
if (e.code === 'Space') {
|
// button activates it natively — preventDefault only applies when the
|
||||||
|
// focus is on the body, where the shortcut below takes over).
|
||||||
|
if (e.code === 'Space' && !(target instanceof HTMLButtonElement)) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +175,9 @@ function App() {
|
|||||||
setListening(l => !l);
|
setListening(l => !l);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (e.code === 'Space' || e.code === 'Enter') {
|
// Space/Enter on a focused button natively activate that button
|
||||||
|
// (e.g. Start); only treat them as pause-toggle when NOT on a button.
|
||||||
|
if ((e.code === 'Space' || e.code === 'Enter') && !(e.target instanceof HTMLButtonElement)) {
|
||||||
phraseRef.current?.pauseToggle();
|
phraseRef.current?.pauseToggle();
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -221,7 +227,7 @@ function App() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.code === 'Space' || e.code === 'Enter') {
|
if ((e.code === 'Space' || e.code === 'Enter') && !(e.target instanceof HTMLButtonElement)) {
|
||||||
singleNoteRef.current?.skipNote();
|
singleNoteRef.current?.skipNote();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -307,9 +307,7 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
|||||||
}, [scoreOk, fixedMaterial, pool, fullScore]);
|
}, [scoreOk, fixedMaterial, pool, fullScore]);
|
||||||
|
|
||||||
const nextAction = useCallback(() => {
|
const nextAction = useCallback(() => {
|
||||||
if (phrase.material === 'melody' || (phrase.material === 'arpeggio' && phrase.arpeggioBars > 1)) {
|
if (fixedMaterial && scoreOk) {
|
||||||
setMelodySeed(s => (s + 1) % 999983);
|
|
||||||
} else if (fixedMaterial && scoreOk) {
|
|
||||||
const total = fullScore!.value.measures.length;
|
const total = fullScore!.value.measures.length;
|
||||||
const nextStart = phrase.startBar + phrase.barCount;
|
const nextStart = phrase.startBar + phrase.barCount;
|
||||||
if (nextStart > total) {
|
if (nextStart > total) {
|
||||||
@@ -317,6 +315,12 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
|||||||
} else {
|
} else {
|
||||||
setPhrase({ startBar: nextStart });
|
setPhrase({ startBar: nextStart });
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Seeded material: roll a new variation (giuliani and single-chord
|
||||||
|
// arpeggios included — they take melodySeed too).
|
||||||
|
if (phrase.material === 'melody' || phrase.material === 'arpeggio' || phrase.material === 'giuliani') {
|
||||||
|
setMelodySeed(s => (s + 1) % 999983);
|
||||||
}
|
}
|
||||||
}, [phrase.material, phrase.startBar, phrase.barCount, fixedMaterial, scoreOk, fullScore, setPhrase]);
|
}, [phrase.material, phrase.startBar, phrase.barCount, fixedMaterial, scoreOk, fullScore, setPhrase]);
|
||||||
|
|
||||||
@@ -766,10 +770,10 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
|||||||
<RotateCcw size={18} />
|
<RotateCcw size={18} />
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
{(phrase.material === 'melody' || phrase.material === 'arpeggio' || fixedMaterial) && (
|
{(phrase.material === 'melody' || phrase.material === 'arpeggio' || phrase.material === 'giuliani' || fixedMaterial) && (
|
||||||
<button className="hint-button" onClick={nextAction} title="New melody / next bars (N)">
|
<button className="hint-button" onClick={nextAction} title="New melody / next bars (N)">
|
||||||
<SkipForward size={18} />
|
<SkipForward size={18} />
|
||||||
{phrase.material === 'melody' ? 'New' : 'Next'}
|
{(fixedMaterial || phrase.material === 'scale') ? 'Next' : 'New'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{phrase.pace === 'step' && (
|
{phrase.pace === 'step' && (
|
||||||
@@ -787,10 +791,10 @@ export const PhraseTrainer = forwardRef<PhraseHandle, PhraseTrainerProps>(
|
|||||||
<RotateCcw size={18} />
|
<RotateCcw size={18} />
|
||||||
Retry
|
Retry
|
||||||
</button>
|
</button>
|
||||||
{(phrase.material === 'melody' || phrase.material === 'arpeggio' || fixedMaterial) && (
|
{(phrase.material === 'melody' || phrase.material === 'arpeggio' || phrase.material === 'giuliani' || fixedMaterial) && (
|
||||||
<button className="hint-button" onClick={nextAction} title="New / Next (N)">
|
<button className="hint-button" onClick={nextAction} title="New / Next (N)">
|
||||||
<SkipForward size={18} />
|
<SkipForward size={18} />
|
||||||
{phrase.material === 'melody' ? 'New' : 'Next'}
|
{(fixedMaterial || phrase.material === 'scale') ? 'Next' : 'New'}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Reference in New Issue
Block a user