@@ -26,6 +26,17 @@ class EscDshotDirectionComponent {
2626 this . _allMotorsAreSpinning = false ;
2727 this . _spinDirectionToggleIsActive = true ;
2828 this . _activationButtonTimeoutId = null ;
29+ this . _isKeyboardControlEnabled = false ;
30+ this . _spacebarPressed = false ;
31+ this . _keyboardEventHandlerBound = false ;
32+ this . _isWizardActive = false ;
33+ this . _globalKeyboardActive = false ;
34+
35+ // Bind methods to preserve 'this' context - CRITICAL for event handlers
36+ this . _handleWizardKeyDown = this . _handleWizardKeyDown . bind ( this ) ;
37+ this . _handleWizardKeyUp = this . _handleWizardKeyUp . bind ( this ) ;
38+ this . _handleGlobalKeyDown = this . _handleGlobalKeyDown . bind ( this ) ;
39+ this . _handleWindowBlur = this . _handleWindowBlur . bind ( this ) ;
2940
3041 this . _contentDiv . load ( "./components/EscDshotDirection/Body.html" , ( ) => {
3142 this . _initializeDialog ( ) ;
@@ -285,9 +296,196 @@ class EscDshotDirectionComponent {
285296 }
286297 }
287298
299+ _enableGlobalKeyboard ( ) {
300+ if ( this . _globalKeyboardActive ) return ;
301+
302+ document . addEventListener ( "keydown" , this . _handleGlobalKeyDown , true ) ;
303+ this . _globalKeyboardActive = true ;
304+ }
305+
306+ _disableGlobalKeyboard ( ) {
307+ document . removeEventListener ( "keydown" , this . _handleGlobalKeyDown , true ) ;
308+ this . _globalKeyboardActive = false ;
309+ }
310+
311+ _handleGlobalKeyDown ( event ) {
312+ // Only handle spacebar for wizard workflow progression
313+ if ( event . code !== "Space" || event . repeat ) {
314+ return ;
315+ }
316+
317+ // Only process keyboard input if the dialog is actually visible
318+ // Check if either the warning content OR main content is visible
319+ const dialogIsVisible =
320+ ( this . _domWarningContentBlock && this . _domWarningContentBlock . is ( ":visible" ) ) ||
321+ ( this . _domMainContentBlock && this . _domMainContentBlock . is ( ":visible" ) ) ;
322+
323+ if ( ! dialogIsVisible ) {
324+ return ;
325+ }
326+
327+ // Step 1: Check the safety checkbox if it's not checked and warning is visible
328+ if ( this . _domWarningContentBlock . is ( ":visible" ) && ! this . _domAgreeSafetyCheckBox . is ( ":checked" ) ) {
329+ event . preventDefault ( ) ;
330+ event . stopPropagation ( ) ;
331+ this . _domAgreeSafetyCheckBox . prop ( "checked" , true ) ;
332+ this . _domAgreeSafetyCheckBox . trigger ( "change" ) ;
333+ return ;
334+ }
335+
336+ // Step 2: Start wizard if checkbox is checked and wizard isn't open yet
337+ if ( this . _domWarningContentBlock . is ( ":visible" ) && this . _domAgreeSafetyCheckBox . is ( ":checked" ) ) {
338+ event . preventDefault ( ) ;
339+ event . stopPropagation ( ) ;
340+ this . _onStartWizardButtonClicked ( ) ;
341+ return ;
342+ }
343+
344+ // Step 3: Spin motors if wizard is open but not spinning yet
345+ if (
346+ this . _domMainContentBlock . is ( ":visible" ) &&
347+ this . _domSpinWizardButton . is ( ":visible" ) &&
348+ ! this . _isWizardActive
349+ ) {
350+ event . preventDefault ( ) ;
351+ event . stopPropagation ( ) ;
352+ // Mark spacebar as pressed since we're transitioning to wizard control while key is down
353+ this . _spacebarPressed = true ;
354+ this . _onSpinWizardButtonClicked ( ) ;
355+ return ;
356+ }
357+
358+ // Step 4: If wizard is active, let the wizard keyboard handler take over
359+ // (no action needed here, the _handleWizardKeyDown will handle it)
360+ }
361+
362+ _enableKeyboardControl ( ) {
363+ if ( this . _keyboardEventHandlerBound ) return ;
364+
365+ // CRITICAL: Use capture phase (third parameter = true) for reliable event handling
366+ // This prevents other elements from stopping propagation before we handle the event
367+ document . addEventListener ( "keydown" , this . _handleWizardKeyDown , true ) ;
368+ document . addEventListener ( "keyup" , this . _handleWizardKeyUp , true ) ;
369+
370+ // SAFETY FEATURE: Stop motors if user switches windows while holding spacebar
371+ window . addEventListener ( "blur" , this . _handleWindowBlur ) ;
372+
373+ this . _keyboardEventHandlerBound = true ;
374+ this . _isKeyboardControlEnabled = true ;
375+ }
376+
377+ _disableKeyboardControl ( ) {
378+ document . removeEventListener ( "keydown" , this . _handleWizardKeyDown , true ) ;
379+ document . removeEventListener ( "keyup" , this . _handleWizardKeyUp , true ) ;
380+ window . removeEventListener ( "blur" , this . _handleWindowBlur ) ;
381+ this . _keyboardEventHandlerBound = false ;
382+ this . _isKeyboardControlEnabled = false ;
383+ this . _spacebarPressed = false ;
384+ }
385+
386+ _handleWizardKeyDown ( event ) {
387+ // Only handle events when keyboard control is active
388+ if ( ! this . _isKeyboardControlEnabled || ! this . _isWizardActive ) {
389+ return ;
390+ }
391+
392+ // SPACEBAR: Spin all motors (hold to spin, release to stop)
393+ if ( event . code === "Space" ) {
394+ event . preventDefault ( ) ;
395+ event . stopPropagation ( ) ;
396+ // CRITICAL: Check !event.repeat to prevent multiple triggers when holding key
397+ if ( ! this . _spacebarPressed && ! event . repeat ) {
398+ this . _spacebarPressed = true ;
399+ this . _handleSpacebarPress ( ) ;
400+ }
401+ return ;
402+ }
403+
404+ // NUMBER KEYS 1-8: Toggle individual motor direction
405+ if ( event . key >= "1" && event . key <= "8" && ! event . repeat ) {
406+ event . preventDefault ( ) ;
407+ event . stopPropagation ( ) ;
408+ const motorIndex = parseInt ( event . key ) - 1 ;
409+
410+ if ( motorIndex < this . _numberOfMotors ) {
411+ this . _toggleMotorDirection ( motorIndex ) ;
412+ }
413+ return ;
414+ }
415+ }
416+
417+ _handleWizardKeyUp ( event ) {
418+ if ( ! this . _isKeyboardControlEnabled || ! this . _isWizardActive ) {
419+ return ;
420+ }
421+
422+ // SPACEBAR RELEASE: Stop motors immediately
423+ if ( event . code === "Space" ) {
424+ event . preventDefault ( ) ;
425+ event . stopPropagation ( ) ;
426+ if ( this . _spacebarPressed ) {
427+ this . _spacebarPressed = false ;
428+ this . _handleSpacebarRelease ( ) ;
429+ }
430+ }
431+ }
432+
433+ _handleSpacebarPress ( ) {
434+ this . _motorDriver . spinAllMotors ( ) ;
435+ }
436+
437+ _handleSpacebarRelease ( ) {
438+ this . _motorDriver . stopAllMotorsNow ( ) ;
439+ }
440+
441+ _handleWindowBlur ( ) {
442+ // SAFETY FEATURE: Stop motors if user switches windows while holding spacebar
443+ if ( this . _spacebarPressed ) {
444+ this . _spacebarPressed = false ;
445+ this . _handleSpacebarRelease ( ) ;
446+ }
447+ }
448+
449+ _toggleMotorDirection ( motorIndex ) {
450+ const button = this . _wizardMotorButtons [ motorIndex ] ;
451+ const currentlyReversed = button . hasClass ( EscDshotDirectionComponent . PUSHED_BUTTON_CLASS ) ;
452+
453+ if ( currentlyReversed ) {
454+ button . removeClass ( EscDshotDirectionComponent . PUSHED_BUTTON_CLASS ) ;
455+ this . _motorDriver . setEscSpinDirection ( motorIndex , DshotCommand . dshotCommands_e . DSHOT_CMD_SPIN_DIRECTION_1 ) ;
456+ } else {
457+ button . addClass ( EscDshotDirectionComponent . PUSHED_BUTTON_CLASS ) ;
458+ this . _motorDriver . setEscSpinDirection ( motorIndex , DshotCommand . dshotCommands_e . DSHOT_CMD_SPIN_DIRECTION_2 ) ;
459+ }
460+ }
461+
462+ open ( ) {
463+ // Enable global keyboard when dialog is opened
464+ this . _enableGlobalKeyboard ( ) ;
465+ }
466+
288467 close ( ) {
468+ // Disable keyboard handlers first to prevent any new input
469+ this . _disableKeyboardControl ( ) ;
470+ this . _disableGlobalKeyboard ( ) ;
471+
472+ // If wizard is active, deactivate buttons but DON'T clear the flag yet
473+ // This ensures pending motor direction commands complete
474+ if ( this . _isWizardActive ) {
475+ this . _deactivateWizardMotorButtons ( ) ;
476+ }
477+
478+ // Stop motors (this adds stop commands to the queue)
289479 this . _motorDriver . stopAllMotorsNow ( ) ;
480+
481+ // Deactivate motor driver - this tells queue to stop AFTER processing current commands
482+ // This is critical - it allows direction change + save commands to complete
290483 this . _motorDriver . deactivate ( ) ;
484+
485+ // Clear wizard flag after motor driver deactivation
486+ this . _isWizardActive = false ;
487+
488+ // Reset GUI last
291489 this . _resetGui ( ) ;
292490 }
293491
@@ -363,13 +561,21 @@ class EscDshotDirectionComponent {
363561 this . _motorDriver . spinAllMotors ( ) ;
364562
365563 this . _activateWizardMotorButtons ( 0 ) ;
564+
565+ // NEW: Enable keyboard shortcuts when wizard starts spinning
566+ this . _isWizardActive = true ;
567+ this . _enableKeyboardControl ( ) ;
366568 }
367569
368570 _onStopWizardButtonClicked ( ) {
369571 this . _domSpinWizardButton . toggle ( true ) ;
370572 this . _domSpinningWizard . toggle ( false ) ;
371573 this . _motorDriver . stopAllMotorsNow ( ) ;
372574 this . _deactivateWizardMotorButtons ( ) ;
575+
576+ // NEW: Disable keyboard shortcuts when wizard stops
577+ this . _disableKeyboardControl ( ) ;
578+ this . _isWizardActive = false ;
373579 }
374580
375581 _toggleMainContent ( value ) {
0 commit comments