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