Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keyboard support for nouislider #724

Closed
chabbrik opened this issue Nov 18, 2016 · 16 comments
Closed

Keyboard support for nouislider #724

chabbrik opened this issue Nov 18, 2016 · 16 comments
Labels
Feature Feature requests/suggestions
Milestone

Comments

@chabbrik
Copy link

Hello,

As part of accessibility work, we implemented support for keyboard to manipulate slider handles. I would love to see it integrated into the core code base, if it seems useful.

Let me know, if that is something you want me to work on.

@leongersen
Copy link
Owner

If it is a testable solution, I'd absolutely be willing to work on integrating it with you!

My main concerns would be:

  • Focus-able handles (and prevent focus stealing)
  • Accessibility (there's another issue about ARIA integration, I'd think these features have some overlap)

@chabbrik
Copy link
Author

Focus-able handles (and prevent focus stealing)
Yes, it will only work, when user has focus on a handle. It will intercept arrow keys and page up/down, but otherwise does not interfere with normal operations.

Accessibility (there's another issue about ARIA integration, I'd think these features have some overlap)

According to our accessibility guys, it is workable solution.

I will work on the reference implementation in my forked repo and then share it. It is my first time contributing to OS project, so it is all new to me.

@bryanwaddington
Copy link

I'd love to see keyboard access, and good screenreader access, for this excellent slider and was going to add it myself when I saw this issue. Has there been any further progress on this? chabbrik, is your forked version usable as it is for keyboard access as it stands now?

I'd be happy to contribute where I can if that helped.

@leongersen
Copy link
Owner

I'd be very happy to see this added. Here's how you can help:

  • there's another issue about implementing aria roles. This should be easy to implement (using the 'update' event), but I'd need someone with some experience in testing (screenreader-) accessibility. If that's you, please let me know and we'll push that feature through very quickly.

  • Keyboard access has two issues: it has to be either universal or configurable. I imagine keystrokes mostly have to obey 'step' but some of the other behaviors might be trickier. I'd like someone to help brainstorm how this has to work, and how to test it. I've already got a library ('simulant') in the tests that another pull request added to ease event testing. We could use that to test keyboard entry as well.

@leongersen leongersen modified the milestone: 9.3.0 Feb 4, 2017
@leongersen leongersen removed this from the 9.3.0 milestone May 28, 2017
@leongersen
Copy link
Owner

I just shipped Aria support in #685. Keyboard support is a little more complicated, and I haven't gotten around to it yet. A pull request would be very welcome. There already is an API for determining the previous and next steps for a handle, see this example.

@MartinMuzatko
Copy link

Hey there. I'll be glad to see this implemented. Yes keyboard should be configurable. Agreed. Eventlisteners for keys have to be registered and unregistered with focus/blur. But it looks like this should be doable.

@leongersen
Copy link
Owner

Adding keyboard support for a linear slider is trivial, see this example. For non-linear sliders there'd a least need to be a setting for what step to use in sub-ranges that do not have a step defined, e.g. defaultStep.

@leongersen leongersen added this to the 12.2.0 milestone Nov 30, 2018
@leongersen
Copy link
Owner

It took a (long!) time, but noUiSlider 13.0.0 now comes with keyboard support build in. Thanks, everybody, for contributing!

@michaelvogt33
Copy link
Contributor

As an other nouislider user wrote in an other forum, I also can work smoothly with nouisliders with screenreader (thank you for your work). As suggested by the user, it would be great if beneath the arrow up / down keys for stepwiseslider value changes, it would be great if it is possible to slide with pageup / pagedown in larger steps and also with home / end to move slider to top / bottom or left / right end of sliders. since many years this is established in mac osx and windows range / slider controls and widely well implemented in nearly any application ( sadly not in libraries like jquery, ...). So: I am working as blind screenreader user by defelop a audio mixer app by using nouisliders as faders. Nouislider is a perfect possibility also because when entering with keyboard focus, your slider stay at current value, most other evaluated sliders and range controls jump to bottom or left end while entering. Now, I have successfully edited the relevant function in your nouislider library source code. with this additional lines of code, it is possible to navigate with pageup / pagedown keys in a screenreader / with keyboard:

    function eventKeydown(event, handleNumber) {
        if (isSliderDisabled() || isHandleDisabled(handleNumber)) {
            return false;
        }

        var largeStepKeys = [ "PageUp", "PageDown"];
        var horizontalKeys = ["Left", "Right"];
        var verticalKeys = ["Down", "Up"];

        if (options.dir && !options.ort) {
            // On an right-to-left slider, the left and right keys act inverted
            horizontalKeys.reverse();
        } else if (options.ort && !options.dir) {
            // On a top-to-bottom slider, the up and down keys act inverted
            verticalKeys.reverse();
        }

        // Strip "Arrow" for IE compatibility. https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
        var key = event.key.replace("Arrow", "");
        var isDown = key === verticalKeys[0] || key === horizontalKeys[0];
        var isUp = key === verticalKeys[1] || key === horizontalKeys[1];
        var isPageDown = key === largeStepKeys[1];
        var isPageUp = key === largeStepKeys[0];
        if (!isDown && !isUp && !isPageDown && !isPageUp) {
            return true;
        }

        event.preventDefault();

        var multiplier = 5;
        var direction = (isDown || isPageDown)? 0 : 1;
        var steps = getNextStepsForHandle(handleNumber);
        var step = steps[direction];

        // At the edge of a slider, do nothing
        if (step === null) {
            return false;
        }

        // No step set, use the default of 10% of the sub-range
        if (step === false) {
            step = scope_Spectrum.getDefaultStep(scope_Locations[handleNumber], isDown || isPageDown, 10);
        }

        if( isPageUp || isPageDown) {
        	step *= multiplier;
        }
        // Step over zero-length ranges (#948);
        step = Math.max(step, 0.0000001);

        // Decrement for down steps
        step = ((isDown || isPageDown)? -1 : 1) * step;
        valueSetHandle(handleNumber, scope_Values[handleNumber] + step, true);

        return false;
    }

@michaelvogt33
Copy link
Contributor

Addendum:
With this changes:

  • each slider can have a default value between the min and max value of the range (optional)
  • added keyboard shortcuts [home] and [end] to move slider to min, default or max value

Also, I renamed within the eventKeydown function the variable names in context of the keyboard shortcuts [pageUp] and [pageDown]. So it is now more clear what the code does and is same logical concept as up/down and left/right.

// you can add simply a default value to the NoUiSlider.create() option parameter list like this:// ...
start: 13.7,
default: 15.0,
faderRange: { 'min': [0.0, 0.25], 'max': 30.0}
// ...

// To do once: within nouislider.js library file:
// add default attribute to the test routine
function testDefault( parsed, entry) {
if (!isNumeric(entry)) {
throw new Error("noUiSlider (" + VERSION + "): 'default' is not numeric.");
}
parsed.default = entry;
}

// and within the test object structure

    var tests = {

// declare the option name, set default as optional and declare the test function name:
default: { r: false, t: testDefault },
// ...
};

// and here, the whole updated eventKeydown function:
function eventKeydown(event, handleNumber) {
if (isSliderDisabled() || isHandleDisabled(handleNumber)) {
return false;
}

        var horizontalKeys = ["Left", "Right"];
        var verticalKeys = ["Down", "Up"];
        var largeStepKeys = [ "PageDown", "PageUp"];
        var edgeKeys = [ "Home", "End"];
        
        if( !options.dir && !options.ort) {
        	// left to right:
        	verticalKeys.reverse();
        	largeStepKeys.reverse();
        } else if( options.dir && !options.ort) {
        	// right to left slider: 
            horizontalKeys.reverse();
        	verticalKeys.reverse();
        	largeStepKeys.reverse();
        } else if( !options.dir && options.ort) {
        	// top down:
        	verticalKeys.reverse();
        	largeStepKeys.reverse();
        } else if( options.dir && options.ort) {
        	// bottom up: 
        	edgeKeys.reverse();
        }

        // Strip "Arrow" for IE compatibility. https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key
        var key = event.key.replace("Arrow", "");
        
        var isDown = key === verticalKeys[0] || key === horizontalKeys[0];
        var isUp = key === verticalKeys[1] || key === horizontalKeys[1];
        var isLargeDown = key === largeStepKeys[0];
        var isLargeUp = key === largeStepKeys[1];
        var isMin = key === edgeKeys[0];
        var isMax = key === edgeKeys[1];
        if (!isDown && !isUp && !isLargeDown && !isLargeUp && !isMin && !isMax) {
            return true;
        }

        event.preventDefault();

        if( isUp || isDown || isLargeUp || isLargeDown) {
        var multiplier = 5;
        var direction = (isDown || isLargeDown)? 0 : 1;
        var steps = getNextStepsForHandle(handleNumber);
        var step = steps[direction];

        // At the edge of a slider, do nothing
        if (step === null) {
            return false;
        }

        // No step set, use the default of 10% of the sub-range
        if (step === false) {
            step = scope_Spectrum.getDefaultStep(scope_Locations[handleNumber], isDown || isLargeDown, 10);
        }

        if( isLargeUp || isLargeDown) {
        	step *= multiplier;
        }
        // Step over zero-length ranges (#948);
        step = Math.max(step, 0.0000001);

        // Decrement for down steps
        step = ((isDown || isLargeDown)? -1 : 1) * step;
        valueSetHandle(handleNumber, scope_Values[handleNumber] + step, true);
        } else {
        	// home or end key were pressed
            var minVal = options.spectrum.xVal[0];
            var maxVal = options.spectrum.xVal[ options.spectrum.xVal.length -1];
            var defaultVal = (options.default === undefined) ? minVal : options.default;
            
            var currentVal = scope_Values[handleNumber];
//console.log( "within key down handler:\nmin: " + minVal + ", default: " + defaultVal + ", max: " + maxVal + ", currentVal: " + currentVal);

var targetVal = currentVal;
if( defaultVal === minVal || defaultVal === maxVal) {
	// ignore defaultVal
	if( isMin) {
		targetVal = minVal;
	} else {
		targetVal = maxVal;
	}
} else {
	// check wether set to default value nor to min / max value
if( isMax) {
	if( currentVal < defaultVal) {
		targetVal = defaultVal;
	} else {
		targetVal = maxVal;
	}
} else {
	if( currentVal > defaultVal) {
		targetVal = defaultVal;
	} else {
		targetVal = minVal;
	}
}
}
valueSetHandle(handleNumber, targetVal, true);
        }
        return false;
    }

@leongersen
Copy link
Owner

@michaelvogt33 Would you consider opening a PR with your suggested changes?

@leongersen leongersen added the Feature Feature requests/suggestions label Dec 2, 2019
@leongersen
Copy link
Owner

Support for PageUp/PageDown and Home/End is now in noUiSlider 14.1.0. Thanks, @michaelvogt33!

@hyvyys
Copy link

hyvyys commented Oct 21, 2021

Is there keyboard support for two-ended range sliders? It's not working for me out-of-the-box.

   const sliderEl = document.querySelector('#slider');
   const options = {
        behaviour: 'drag-tap',
        connect: true,
        range: { min: 0, max: 299 },
        step: 1,
        start: [0]
   };
   const sliderInstance = noUiSlider.create(sliderEl, options);

I'm using "nouislider": "^15.5.0".

@leongersen
Copy link
Owner

@hyvyys Yes, keyboard support works for sliders with any number of handles. What do you mean by two-ended?

@CucumberKid
Copy link

It took a (long!) time, but noUiSlider 13.0.0 now comes with keyboard support build in. Thanks, everybody, for contributing!

Hi I have version 10.0.0 of nouislider and would like to upgrade to have keyboard and aria support. Are there any people in the community that could give me some paid for support to do the upgrade? I tried swapping out the js and lost the handles so I figure I could use some expert support.

@github-actions
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 16, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Feature Feature requests/suggestions
Projects
None yet
Development

No branches or pull requests

7 participants