@uswds/uswds
Version:
Open source UI components and visual style guide for U.S. government websites
101 lines (91 loc) • 3.37 kB
JavaScript
const selectOrMatches = require("../../uswds-core/src/js/utils/select-or-matches");
const behavior = require("../../uswds-core/src/js/utils/behavior");
const { prefix: PREFIX } = require("../../uswds-core/src/js/config");
const RANGE_CLASSNAME = `${PREFIX}-range`;
const RANGE = `.${RANGE_CLASSNAME}`;
const RANGE_WRAPPER_CLASS = `${RANGE}__wrapper`;
const VALUE_WRAPPER_CLASS = `${RANGE}__value`;
const RANGE_WRAPPER = `${RANGE_CLASSNAME}__wrapper`;
const VALUE_WRAPPER = `${RANGE_CLASSNAME}__value`;
/**
* Dynamically create a span field with the range slider's initial value.
*
* @comment This is the visual representation of the value of the range slider
* and appears in an adjacent span. This runs on init.
*/
const createEnhancedRangeSlider = (el) => {
const rangeSliderEl = el;
const rangeSliderVal = document.createElement("span");
rangeSliderVal.setAttribute("aria-hidden", "true");
rangeSliderVal.className = VALUE_WRAPPER;
rangeSliderVal.textContent = rangeSliderEl.value;
const rangeWrapper = document.createElement("div");
rangeWrapper.className = RANGE_WRAPPER;
rangeSliderEl.parentNode.insertBefore(rangeWrapper, rangeSliderEl);
rangeWrapper.appendChild(rangeSliderEl);
rangeWrapper.appendChild(rangeSliderVal);
};
/**
* Update range callout for screen readers using the optional data attributes.
*
* Get optional data attributes, construct and appends aria-valuetext attribute.
*
* @example
*
* <input id="usa-range" class="usa-range" type="range" min="0" max="100" step="10" value="20" data-text-unit="degrees">
*
* Callout returns "20 degrees of 100."
*
* <input id="usa-range" class="usa-range" type="range" min="0" max="100" step="10" value="20" data-text-preposition="de">
*
* Callout returns "20 de 100."
*
* @param {HTMLInputElement} targetRange - The range slider input element
*/
const updateCallout = (targetRange) => {
const rangeSlider = targetRange;
const defaultPrep = "of";
const optionalPrep = rangeSlider.dataset.textPreposition;
const prep = optionalPrep || defaultPrep;
const unit = rangeSlider.dataset.textUnit;
const val = rangeSlider.value;
// Note: 100 is the max attribute's native default value on range inputs
// Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/range#validation
const max = rangeSlider.getAttribute("max") || 100;
let callout;
if (unit) {
callout = `${val} ${unit} ${prep} ${max}`;
} else {
callout = `${val} ${prep} ${max}`;
}
rangeSlider.setAttribute("aria-valuetext", callout);
};
/**
* Update range <span> value (the range slider value for sighted users).
* @param {HTMLInputElement} targetRange - The range slider input element
*/
const updateVisualCallout = (el) => {
const rangeSliderEl = el;
const parentDiv = rangeSliderEl.closest(RANGE_WRAPPER_CLASS);
const rangeSliderVal = parentDiv.querySelector(VALUE_WRAPPER_CLASS);
rangeSliderVal.textContent = rangeSliderEl.value;
};
const rangeEvents = {
change: {
[RANGE]() {
updateCallout(this);
updateVisualCallout(this);
},
},
};
const range = behavior(rangeEvents, {
init(root) {
selectOrMatches(RANGE, root).forEach((rangeSlider) => {
createEnhancedRangeSlider(rangeSlider);
updateCallout(rangeSlider);
});
},
updateCallout,
updateVisualCallout,
});
module.exports = range;