shepherd icon indicating copy to clipboard operation
shepherd copied to clipboard

Keyboard navigation triggers next rather than action button

Open trish1400 opened this issue 2 years ago • 5 comments

This is something we intended to look at as part of our project but we've run out of time / budget - so I'm raising it, in case anyone else is able to.

I've set up a number of tour steps, most of which have Back and Next buttons but for some steps I've replaced the 'Next' button with a custom action (mostly basic stuff - e.g. click a menu link and then call next).

This works fine when navigating the tour using the mouse but, if you use the keyboard navigation - the right arrow key will always just move the user to the next tour step and never trigger the custom action.

Could there be a way to specify which button should be triggered by the left and right arrow keys? Or, as a bit of a fudge, could the right arrow key trigger the first button on that step which has an action with a class of 'next-button' (and vice versa for the back button)?

trish1400 avatar Apr 06 '22 10:04 trish1400

@trish1400 this is definitely a known issue. I think the problem is buttons can do anything you want, as you mentioned, so we cannot assume which ones will be next/back etc. I would love to support this, but I don't know of a good way.

RobbieTheWagner avatar Apr 07 '22 16:04 RobbieTheWagner

Easy to implement.

  1. In Tour options set keyboardNavigation: false to deactivate default navigation.
  2. Add classes shepherd-left-arrow and shepherd-right-arrow to buttons by setting e.g.  
{
action() {return yourFunction()},
classes: 'shepherd-right-arrow',
text: 'Next'
}
  1. Add custom key listeners.
document.onkeydown = function (e) {
  if (Shepherd.activeTour != null){
    switch (e.key) {
        case 'ArrowUp':
            break;
        case 'ArrowDown':
            break;
        case 'ArrowLeft':
            $(".shepherd-left-arrow").eq(-1).click();
            break;
        case 'ArrowRight':
            $(".shepherd-right-arrow").eq(-1).click();
            break
    }
  }
};

Note that the function executes only when a tour is active. I added .eq(-1) as there appears to be some kind of bug or logic of a doubled invisible div. The last one however is always the one you would actually click with your mouse. Tested and works well for my use case.

Personally I use this workaround in combination with #1906.

do-me avatar May 06 '22 09:05 do-me

Thanks for sharing @do-me 👍

trish1400 avatar May 06 '22 09:05 trish1400

We should probably make next and previous buttons special cases, instead of allowing custom logic for all buttons. Then we could handle this in the library.

RobbieTheWagner avatar May 10 '22 14:05 RobbieTheWagner

classes: 'shepherd-right-arrow',

Here trying to respond a already good answer in typescript, I'm checking if the element is visible for better handling (using vue but you'll understand no worries)

const shepherdKeyEvents = (e) => {
          const clickOnElement = (className: string) => {
            const elements = document.querySelectorAll<HTMLElement>(className);
            if (
              elements.length > 0 &&
              elements[elements.length - 1].offsetParent != null
            ) {
              elements[elements.length - 1].click();
            }
          };

          if (this.tour.isActive()) {
            switch (e.key) {
              case 'ArrowUp':
                break;
              case 'ArrowDown':
                break;
              case 'ArrowLeft':
                clickOnElement('.shepherd-left-arrow');
                break;
              case 'ArrowRight':
                clickOnElement('.shepherd-right-arrow');
                break;
            }
          }
        };

        onMounted(() =>
          document.addEventListener('keydown', shepherdKeyEvents)
        );
        onUnmounted(() =>
          document.removeEventListener('keydown', shepherdKeyEvents)
        );

strife-cloud avatar May 19 '24 15:05 strife-cloud