diff --git a/packages/main/src/RangeSlider.hbs b/packages/main/src/RangeSlider.hbs index 6ebefdbec6c1..152061156ba8 100644 --- a/packages/main/src/RangeSlider.hbs +++ b/packages/main/src/RangeSlider.hbs @@ -1,14 +1,60 @@ {{>include "./SliderBase.hbs"}} +{{_ariaHandlesText.startHandleText}} +{{_ariaHandlesText.endHandleText}} + +{{#*inline "progressBar"}} +
+
+
+{{/inline}} + {{#*inline "handles"}} -
+
{{#if showTooltip}}
{{tooltipStartValue}}
{{/if}}
-
+ +
{{#if showTooltip}}
{{tooltipEndValue}} diff --git a/packages/main/src/RangeSlider.js b/packages/main/src/RangeSlider.js index 886e3b00aa13..daecc657c42c 100644 --- a/packages/main/src/RangeSlider.js +++ b/packages/main/src/RangeSlider.js @@ -8,6 +8,13 @@ import { import SliderBase from "./SliderBase.js"; import RangeSliderTemplate from "./generated/templates/RangeSliderTemplate.lit.js"; +// Texts +import { + RANGE_SLIDER_ARIA_DESCRIPTION, + RANGE_SLIDER_START_HANDLE_DESCRIPTION, + RANGE_SLIDER_END_HANDLE_DESCRIPTION, +} from "./generated/i18n/i18n-defaults.js"; + /** * @public */ @@ -119,6 +126,30 @@ class RangeSlider extends SliderBase { return this.endValue.toFixed(stepPrecision); } + get _ariaDisabled() { + return this.disabled || undefined; + } + + get _ariaLabelledByText() { + return this.i18nBundle.getText(RANGE_SLIDER_ARIA_DESCRIPTION); + } + + get _ariaHandlesText() { + const isRTL = this.effectiveDir === "rtl"; + const isReversed = this._areValuesReversed(); + const ariaHandlesText = {}; + + if ((isRTL && !isReversed) || (!isRTL && isReversed)) { + ariaHandlesText.startHandleText = this.i18nBundle.getText(RANGE_SLIDER_END_HANDLE_DESCRIPTION); + ariaHandlesText.endHandleText = this.i18nBundle.getText(RANGE_SLIDER_START_HANDLE_DESCRIPTION); + } else { + ariaHandlesText.startHandleText = this.i18nBundle.getText(RANGE_SLIDER_START_HANDLE_DESCRIPTION); + ariaHandlesText.endHandleText = this.i18nBundle.getText(RANGE_SLIDER_END_HANDLE_DESCRIPTION); + } + + return ariaHandlesText; + } + /** * Check if the previously saved state is outdated. That would mean * either it is the initial rendering or that a property has been changed @@ -667,19 +698,15 @@ class RangeSlider extends SliderBase { } get _startHandle() { - return this.getDomRef().querySelector(".ui5-slider-handle--start"); + return this.shadowRoot.querySelector(".ui5-slider-handle--start"); } get _endHandle() { - return this.getDomRef().querySelector(".ui5-slider-handle--end"); + return this.shadowRoot.querySelector(".ui5-slider-handle--end"); } get _progressBar() { - return this.getDomRef().querySelector(".ui5-slider-progress"); - } - - get tabIndexProgress() { - return this.tabIndex; + return this.shadowRoot.querySelector(".ui5-slider-progress"); } get styles() { diff --git a/packages/main/src/Slider.hbs b/packages/main/src/Slider.hbs index e2d9e62937ad..79413a5a93e9 100644 --- a/packages/main/src/Slider.hbs +++ b/packages/main/src/Slider.hbs @@ -1,11 +1,29 @@ {{>include "./SliderBase.hbs"}} +{{#*inline "progressBar"}} + +{{/inline}} + {{#*inline "handles"}}
{{#if showTooltip}} diff --git a/packages/main/src/Slider.js b/packages/main/src/Slider.js index 57b725cd4806..ff8fa1500e8a 100644 --- a/packages/main/src/Slider.js +++ b/packages/main/src/Slider.js @@ -6,6 +6,11 @@ import SliderBase from "./SliderBase.js"; // Template import SliderTemplate from "./generated/templates/SliderTemplate.lit.js"; +// Texts +import { + SLIDER_ARIA_DESCRIPTION, +} from "./generated/i18n/i18n-defaults.js"; + /** * @public */ @@ -265,8 +270,12 @@ class Slider extends SliderBase { return this.value.toFixed(stepPrecision); } - get tabIndexProgress() { - return "-1"; + get _ariaDisabled() { + return this.disabled || undefined; + } + + get _ariaLabelledByText() { + return this.i18nBundle.getText(SLIDER_ARIA_DESCRIPTION); } static async onDefine() { diff --git a/packages/main/src/SliderBase.hbs b/packages/main/src/SliderBase.hbs index fa6f00e89de5..dae8c9758dba 100644 --- a/packages/main/src/SliderBase.hbs +++ b/packages/main/src/SliderBase.hbs @@ -22,11 +22,13 @@ {{/if}} {{/if}} -
-
-
+ {{> progressBar}} + {{> handles}}
+{{_ariaLabelledByText}} + +{{#*inline "progressBar"}}{{/inline}} {{#*inline "handles"}}{{/inline}} diff --git a/packages/main/src/SliderBase.js b/packages/main/src/SliderBase.js index 7b7a4869c26e..907936c1c4ad 100644 --- a/packages/main/src/SliderBase.js +++ b/packages/main/src/SliderBase.js @@ -114,11 +114,6 @@ const metadata = { _hiddenTickmarks: { type: Boolean, }, - _tabIndex: { - type: String, - defaultValue: "0", - noAttribute: true, - }, }, events: /** @lends sap.ui.webcomponents.main.SliderBase.prototype */ { /** @@ -839,7 +834,7 @@ class SliderBase extends UI5Element { } get tabIndex() { - return this.disabled ? "-1" : this._tabIndex; + return this.disabled ? "-1" : "0"; } } diff --git a/packages/main/src/i18n/messagebundle.properties b/packages/main/src/i18n/messagebundle.properties index ec3b84191425..7649480d6a8a 100644 --- a/packages/main/src/i18n/messagebundle.properties +++ b/packages/main/src/i18n/messagebundle.properties @@ -283,6 +283,15 @@ MULTIINPUT_SHOW_MORE_TOKENS={0} More #XTOL: Tooltip for panel expand title PANEL_ICON=Expand/Collapse +#XACT: ARIA description for range slider progress +RANGE_SLIDER_ARIA_DESCRIPTION=Range + +#XACT: ARIA description for range slider start handle +RANGE_SLIDER_START_HANDLE_DESCRIPTION=Left handle + +#XACT: ARIA description for range slider end handle +RANGE_SLIDER_END_HANDLE_DESCRIPTION=Right handle + #XBUT: Rating indicator tooltip text RATING_INDICATOR_TOOLTIP_TEXT=Rating @@ -292,6 +301,9 @@ RATING_INDICATOR_TEXT=Rating Indicator #XACT: ARIA description for the segmented button SEGMENTEDBUTTON_ARIA_DESCRIPTION=Segmented button +#XACT: ARIA description for slider handle +SLIDER_ARIA_DESCRIPTION=Slider handle + #XACT: ARIA announcement for the switch on SWITCH_ON=On diff --git a/packages/main/src/themes/SliderBase.css b/packages/main/src/themes/SliderBase.css index 7a4d4d641b68..76b584e24f1e 100644 --- a/packages/main/src/themes/SliderBase.css +++ b/packages/main/src/themes/SliderBase.css @@ -1,3 +1,5 @@ +@import "./InvisibleTextStyles.css"; + :host([disabled]) { opacity: var(--_ui5_slider_disabled_opacity); cursor: default; diff --git a/packages/main/test/specs/RangeSlider.spec.js b/packages/main/test/specs/RangeSlider.spec.js index 2361a7d4624c..e6b0903c3c0d 100644 --- a/packages/main/test/specs/RangeSlider.spec.js +++ b/packages/main/test/specs/RangeSlider.spec.js @@ -97,7 +97,7 @@ describe("Testing Range Slider interactions", () => { rangeSlider.setProperty("endValue", 30); rangeSlider.dragAndDrop({ x: -500, y: 1 }); - + assert.strictEqual(rangeSlider.getProperty("startValue"), 0, "startValue should be 0 as the selected range has reached the start of the Range Slider"); assert.strictEqual(rangeSlider.getProperty("endValue"), 21, "endValue should be 21 and no less, the initially selected range should be preserved"); @@ -203,7 +203,7 @@ describe("Properties synchronization and normalization", () => { rangeSlider.setProperty("endValue", 300); assert.strictEqual(rangeSlider.getProperty("endValue"), 200, "value prop should always be lower than the max value"); - + rangeSlider.setProperty("startValue", 99); assert.strictEqual(rangeSlider.getProperty("startValue"), 100, "value prop should always be greater than the min value"); @@ -220,7 +220,7 @@ describe("Properties synchronization and normalization", () => { assert.strictEqual(rangeSlider.getProperty("startValue"), 14, "startValue should not be stepped to the next step (15)"); assert.strictEqual(rangeSlider.getProperty("endValue"), 24, "endValue should not be stepped to the next step (25)"); - }); + }); it("If the step property or the labelInterval are changed, the tickmarks and labels must be updated also", () => { const rangeSlider = browser.$("#range-slider-tickmarks-labels"); @@ -234,7 +234,7 @@ describe("Properties synchronization and normalization", () => { rangeSlider.setProperty("step", 2); assert.strictEqual(rangeSlider.getProperty("_labels").length, 11, "Labels must be 12 - 1 for every 2 tickmarks (and 4 current value points)"); - + rangeSlider.setProperty("labelInterval", 4); assert.strictEqual(rangeSlider.getProperty("_labels").length, 6, "Labels must be 6 - 1 for every 4 tickmarks (and 8 current value points)"); @@ -263,7 +263,66 @@ describe("Testing events", () => { }); -describe("Accessibility: Testing focus", () => { +describe("Accessibility", () => { + it("Aria attributes of the progress bar are set correctly", () => { + const rangeSlider = browser.$("#range-slider-tickmarks"); + const rangeSliderProgressBar = rangeSlider.shadow$(".ui5-slider-progress"); + const rangeSliderId = rangeSlider.getProperty("_id"); + + assert.strictEqual(rangeSliderProgressBar.getAttribute("aria-labelledby"), + `${rangeSliderId}-sliderDesc`, "aria-labelledby is set correctly"); + assert.strictEqual(rangeSliderProgressBar.getAttribute("aria-valuemin"), + `${rangeSlider.getProperty("min")}`, "aria-valuemin is set correctly"); + assert.strictEqual(rangeSliderProgressBar.getAttribute("aria-valuemax"), + `${rangeSlider.getProperty("max")}`, "aria-valuemax is set correctly"); + assert.strictEqual(rangeSliderProgressBar.getAttribute("aria-valuetext"), + `From ${rangeSlider.getProperty("startValue")} to ${rangeSlider.getProperty("endValue")}`, "aria-valuetext is set correctly"); + }); + + it("Aria attributes of the start handle are set correctly", () => { + const rangeSlider = browser.$("#range-slider-tickmarks"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle--start"); + const rangeSliderId = rangeSlider.getProperty("_id"); + + assert.strictEqual(startHandle.getAttribute("aria-labelledby"), + `${rangeSliderId}-startHandleDesc`, "aria-labelledby is set correctly"); + assert.strictEqual(startHandle.getAttribute("aria-valuemin"), + `${rangeSlider.getProperty("min")}`, "aria-valuemin is set correctly"); + assert.strictEqual(startHandle.getAttribute("aria-valuemax"), + `${rangeSlider.getProperty("max")}`, "aria-valuemax is set correctly"); + assert.strictEqual(startHandle.getAttribute("aria-valuenow"), + `${rangeSlider.getProperty("startValue")}`, "aria-valuenow is set correctly"); + }); + + it("Aria attributes of the end handle are set correctly", () => { + const rangeSlider = browser.$("#range-slider-tickmarks"); + const endHandle = rangeSlider.shadow$(".ui5-slider-handle--end"); + const rangeSliderId = rangeSlider.getProperty("_id"); + + assert.strictEqual(endHandle.getAttribute("aria-labelledby"), + `${rangeSliderId}-endHandleDesc`, "aria-labelledby is set correctly"); + assert.strictEqual(endHandle.getAttribute("aria-valuemin"), + `${rangeSlider.getProperty("min")}`, "aria-valuemin is set correctly"); + assert.strictEqual(endHandle.getAttribute("aria-valuemax"), + `${rangeSlider.getProperty("max")}`, "aria-valuemax is set correctly"); + assert.strictEqual(endHandle.getAttribute("aria-valuenow"), + `${rangeSlider.getProperty("endValue")}`, "aria-valuenow is set correctly"); + }); + + it("Aria-labelledby text is mapped correctly when values are swapped", () => { + const rangeSlider = browser.$("#range-slider-tickmarks"); + const rangeSliderId = rangeSlider.getProperty("_id"); + const startHandle = rangeSlider.shadow$(".ui5-slider-handle--start"); + const rangeSliderStartHandleSpan = rangeSlider.shadow$(`#${rangeSliderId}-startHandleDesc`); + const rangeSliderEndHandleSpan = rangeSlider.shadow$(`#${rangeSliderId}-endHandleDesc`); + + rangeSlider.setProperty("endValue", 9); + startHandle.dragAndDrop({ x: 100, y: 1 }); + + assert.strictEqual(rangeSliderStartHandleSpan.getText(), "Left handle", "Start Handle text is correct after swap"); + assert.strictEqual(rangeSliderEndHandleSpan.getText(), "Right handle", "End Handle text is correct after swap"); + }); + it("Click anywhere in the Range Slider should focus the closest handle", () => { browser.url("http://localhost:8080/test-resources/pages/RangeSlider.html"); @@ -321,7 +380,7 @@ describe("Accessibility: Testing focus", () => { assert.strictEqual(rangeSlider.isFocused(), true, "Range Slider component is focused"); assert.strictEqual($(innerFocusedElement).getAttribute("class"), rangeSliderSelection.getAttribute("class"), "Range Slider progress tracker has the shadowDom focus"); }); - + it("When progress bar has the focus, 'Tab' should move the focus to the first handle", () => { const rangeSlider = browser.$("#basic-range-slider"); const rangeSliderStartHandle = rangeSlider.shadow$(".ui5-slider-handle--start"); diff --git a/packages/main/test/specs/Slider.spec.js b/packages/main/test/specs/Slider.spec.js index c4dcdaa7f010..59bda5e6cf2a 100644 --- a/packages/main/test/specs/Slider.spec.js +++ b/packages/main/test/specs/Slider.spec.js @@ -96,7 +96,7 @@ describe("Properties synchronization and normalization", () => { slider.setProperty("step", 2); assert.strictEqual(slider.getProperty("_labels").length, 11, "Labels must be 12 - 1 for every 2 tickmarks (and 4 current value points)"); - + slider.setProperty("labelInterval", 4); assert.strictEqual(slider.getProperty("_labels").length, 6, "Labels must be 6 - 1 for every 4 tickmarks (and 8 current value points)"); @@ -185,7 +185,22 @@ describe("Testing events", () => { }); }); -describe("Accessibility: Testing focus", () => { +describe("Accessibility", () => { + it("Aria attributes are set correctly", () => { + const slider = browser.$("#basic-slider"); + const sliderHandle = slider.shadow$(".ui5-slider-handle"); + const sliderId = slider.getProperty("_id"); + + assert.strictEqual(sliderHandle.getAttribute("aria-labelledby"), + `${sliderId}-sliderDesc`, "aria-labelledby is set correctly"); + assert.strictEqual(sliderHandle.getAttribute("aria-valuemin"), + `${slider.getProperty("min")}`, "aria-valuemin is set correctly"); + assert.strictEqual(sliderHandle.getAttribute("aria-valuemax"), + `${slider.getProperty("max")}`, "aria-valuemax is set correctly"); + assert.strictEqual(sliderHandle.getAttribute("aria-valuenow"), + `${slider.getProperty("value")}`, "aria-valuenow is set correctly"); + }); + it("Click anywhere in the Slider should focus the Slider's handle", () => { browser.url("http://localhost:8080/test-resources/pages/Slider.html"); @@ -335,7 +350,7 @@ describe("Accessibility: Testing keyboard handling", () => { browser.keys(numpadSubtract); assert.strictEqual(slider.getProperty("value"), 0, "Value is decreased"); - }); + }); it("An 'End' key press should increase the value of the slider to its max", () => { const slider = browser.$("#basic-slider-with-tooltip");