diff --git a/js/ISLCA11yStrings.js b/js/ISLCA11yStrings.js index 7eabc79..99cd465 100644 --- a/js/ISLCA11yStrings.js +++ b/js/ISLCA11yStrings.js @@ -82,27 +82,36 @@ define( require => { distanceFromOtherObjectPattern: { value: '{{distance}} {{otherObjectLabel}}' }, - positionMarkPattern: { - value: '{{position}} {{unit}} mark' - }, positionDistanceFromOtherObjectPattern: { - value: '{{positionMark}}, {{distanceFromOtherObject}}.' + value: '{{positionLandmark}}, {{distanceFromOtherObject}}.' }, distanceFromOtherObjectSentencePattern: { value: '{{distanceFromOtherObject}}.' }, - // closer/further away - progressDistanceFromOtherObjectPattern: { - value: '{{progress}}, {{distanceFromOtherObject}}.' + + // position landmarks + leftSideOfTrack: { + value: 'Left side of track' + }, + rightSideOfTrack: { + value: 'Right side of track' }, - edgeDistancePattern: { - value: '{{distanceAndUnits}} away' + lastStopRight: { + value: 'Last stop right' }, - arrivedAtEdgePattern: { - value: 'At {{side}} edge, {{distanceClause}}.' + lastStopLeft: { + value: 'Last stop left' }, - closestToOtherObjectPattern: { - value: '{{positionMark}}, {{edgePhrase}}, {{distanceFromOtherObject}}.' + trackEndLeft: { + value: 'Track end left' + }, + trackEndRight: { + value: 'Track end right' + }, + + // closer/further away + progressDistanceFromOtherObjectPattern: { + value: '{{progress}}, {{distanceFromOtherObject}}.' }, sidePattern: { value: '{{side}} edge' diff --git a/js/view/ISLCObjectNode.js b/js/view/ISLCObjectNode.js index 4804def..bda95f5 100644 --- a/js/view/ISLCObjectNode.js +++ b/js/view/ISLCObjectNode.js @@ -345,6 +345,9 @@ define( function( require ) { accessibleSliderOptions ); + // a11y - keep value text in sync as the model changes + model.forceProperty.lazyLink( () => this.updateOnFocusAriaValueText() ); + // TODO: move to MassNode since ChargeNodes don't have a changing radiusProperty. this.objectModel.radiusProperty.link( function() { diff --git a/js/view/describers/PositionDescriber.js b/js/view/describers/PositionDescriber.js index 704958a..695029a 100644 --- a/js/view/describers/PositionDescriber.js +++ b/js/view/describers/PositionDescriber.js @@ -21,12 +21,8 @@ define( require => { const quantitativeAndQualitativePatternString = ISLCA11yStrings.quantitativeAndQualitativePattern.value; const centersOfObjectsDistancePatternString = ISLCA11yStrings.centersOfObjectsDistancePattern.value; - const positionMarkPatternString = ISLCA11yStrings.positionMarkPattern.value; const positionDistanceFromOtherObjectPatternString = ISLCA11yStrings.positionDistanceFromOtherObjectPattern.value; const progressDistanceFromOtherObjectPatternString = ISLCA11yStrings.progressDistanceFromOtherObjectPattern.value; - const arrivedAtEdgePatternString = ISLCA11yStrings.arrivedAtEdgePattern.value; - const edgeDistancePatternString = ISLCA11yStrings.edgeDistancePattern.value; - const closestToOtherObjectPatternString = ISLCA11yStrings.closestToOtherObjectPattern.value; const sidePatternString = ISLCA11yStrings.sidePattern.value; const distanceAndUnitsPatternString = ISLCA11yStrings.distanceAndUnitsPattern.value; const quantitativeDistancePatternString = ISLCA11yStrings.quantitativeDistancePattern.value; @@ -34,6 +30,14 @@ define( require => { const distanceFromOtherObjectSentencePatternString = ISLCA11yStrings.distanceFromOtherObjectSentencePattern.value; const lastStopString = ISLCA11yStrings.lastStop.value; + // track landmarks + const leftSideOfTrackString = ISLCA11yStrings.leftSideOfTrack.value; + const rightSideOfTrackString = ISLCA11yStrings.rightSideOfTrack.value; + const lastStopRightString = ISLCA11yStrings.lastStopRight.value; + const lastStopLeftString = ISLCA11yStrings.lastStopLeft.value; + const trackEndLeftString = ISLCA11yStrings.trackEndLeft.value; + const trackEndRightString = ISLCA11yStrings.trackEndRight.value; + const leftString = ISLCA11yStrings.left.value; const rightString = ISLCA11yStrings.right.value; const farthestFromString = ISLCA11yStrings.farthestFrom.value; @@ -91,7 +95,7 @@ define( require => { options = _.extend( { unit: unitsMeterString, units: unitsMetersString, - centerOffset: 0, + centerOffset: 0, // {number} the point considered the "center" of the track space // {number} => {number} convertDistanceMetric: distance => distance @@ -270,31 +274,35 @@ define( require => { } /** - * fillIn just the position mark clause of some sentences - * @param {ISLCObjectEnum} thisObjectEnum + * Map object positions to landmarks. This is not a traditional linear/numeric mapping + * but instead it is based on the two objects and if they are touching each other or the edges. + * @param {ISLCObjectEnum} objectEnum * @returns {string} */ - getPositionMark( thisObjectEnum ) { - const position = this.getConvertedPositionFromEnum( thisObjectEnum ); - const unit = this.unit; - return StringUtils.fillIn( positionMarkPatternString, { - position: position, unit: unit - } ); - } + getPositionLandmark( objectEnum ) { - /** - * Returns the string filled in string '{{position}} {{unit}} mark, {{distance}} {{units}} from {{otherObjectLabel}}.' - * - * @param {ISLCObjectEnum} thisObjectEnum - * @returns {string} - */ - getPositionAndDistanceFromOtherObjectText( thisObjectEnum ) { - const positionMark = this.getPositionMark( thisObjectEnum ); - return this.getSpherePositionAriaValueText( - thisObjectEnum, - positionDistanceFromOtherObjectPatternString, - { positionMark: positionMark } - ); + // object 1 touching left + if ( this.object1AtMin( objectEnum ) ) { + return trackEndLeftString; + } + + // object 2 touching right + else if ( this.object2AtMax( objectEnum ) ) { + return trackEndRightString; + } + + // objects touching each other + else if ( this.objectTouchingBoundary( objectEnum ) ) { + return this.isObject1( objectEnum ) ? lastStopRightString : lastStopLeftString; + } + + // objects not touching any boundary, based on the side relative to the center + else { + const object = this.getObjectFromEnum( objectEnum ); + + // TODO: why does centerOffset not work as expected here? + return object.positionProperty.get() < 0 ? leftSideOfTrackString : rightSideOfTrackString; + } } /** @@ -313,66 +321,21 @@ define( require => { } /** - * Special case string for when an object is at a boundary. This either means touching - * an edge, or touching the other object. This is because in both cases, this object cannot move further in - * that direction. + * The aria-value text when + * Returns the desired value for the ISLCObjectNodes' aria-valuetext attributes when they receive keyboard focus. * * @param {ISLCObjectEnum} thisObjectEnum * @returns {string} */ - getBoundaryTouchingValueText( thisObjectEnum ) { - const positionMark = this.getPositionMark( thisObjectEnum ); - const edgePhrase = this.getEdgePhrase( thisObjectEnum ); - + getFocusAriaValueText( thisObjectEnum ) { + const positionMark = this.getPositionLandmark( thisObjectEnum ); return this.getSpherePositionAriaValueText( thisObjectEnum, - closestToOtherObjectPatternString, - { positionMark: positionMark, edgePhrase: edgePhrase } + positionDistanceFromOtherObjectPatternString, + { positionLandmark: positionMark } ); } - /** - * Returns the filled in string 'At {{side}} edge, {{distance}} {{units}} away.' - * - * @param {ISLCObjectEnum} thisObjectEnum - * @returns {string} - */ - getArrivedAtEdgeText( thisObjectEnum ) { - assert && assert( this.objectTouchingBoundary( thisObjectEnum ) ); - - const distanceClause = this.useQuantitativeDistance ? - - // quantitative distance - StringUtils.fillIn( edgeDistancePatternString, { - distanceAndUnits: this.getDistanceAndUnits() - } ) : - - // qualitative distance - this.getDistanceClause( thisObjectEnum ); - - // partially fill in the string with the "side" template var - return StringUtils.fillIn( arrivedAtEdgePatternString, { - side: this.getEdgeFromObjectEnum( thisObjectEnum ), - distanceClause: distanceClause - } ); - } - - /** - * Returns the desired value for the ISLCObjectNodes' aria-valuetext attributes when they receive keyboard focus. - * - * @param {ISLCObjectEnum} thisObjectEnum - * @returns {string} - */ - getFocusAriaValueText( thisObjectEnum ) { - let text = this.getPositionAndDistanceFromOtherObjectText( thisObjectEnum ); - - // this covers when the object is at edges, and closest to the other mass - if ( this.objectTouchingBoundary( thisObjectEnum ) ) { - text = this.getBoundaryTouchingValueText( thisObjectEnum ); - } - return text; - } - /** * Returns a function used by AccessibleSlider to format its aria-valuetext attribute. Of note is that this function * is called AFTER the Slider's position Property has been set. Since, the PositionDescriber links to the PositionProperty @@ -393,24 +356,41 @@ define( require => { * @returns {string} - the string that will fill the aria-valuetext attribute */ return ( formattedValue, oldValue ) => { + + // "normally" should just be short distance let newAriaValueText = this.getDistanceFromOtherObjectText( objectEnum ); + // closer/farther text if ( this.lastMoveCloser !== this.movedCloser ) { newAriaValueText = this.getProgressPositionAndDistanceFromOtherObjectText( objectEnum ); } - if ( this.objectAtEdgeIgnoreOtherObject( objectEnum ) ) { - newAriaValueText = this.getArrivedAtEdgeText( objectEnum ); - } - - if ( this.objectsClosest() ) { - newAriaValueText = this.getBoundaryTouchingValueText( objectEnum ); + // border/edge cases use the same as on focus value text + if ( this.objectsClosest() || this.objectAtEdgeIgnoreOtherObject( objectEnum ) ) { + newAriaValueText = this.getFocusAriaValueText( objectEnum ); } return newAriaValueText; }; } + /** + * @param {ISLCObjectEnum} objectEnum + * @returns {boolean} + */ + object1AtMin( objectEnum ) { + return this.isObject1( objectEnum ) && this.objectAtTouchingMin( objectEnum ); + } + + /** + * @param {ISLCObjectEnum} objectEnum + * @returns {boolean} + */ + object2AtMax( objectEnum ) { + return this.isObject2( objectEnum ) && this.objectAtTouchingMax( objectEnum ); + } + + /** * Since ISLCObject.enabledRangeProperty is affected by the other object, this method determines if the object is * actually at the edge of the sliding area, not just if you are stuck next to the other mass @@ -419,8 +399,7 @@ define( require => { * @returns {boolean} */ objectAtEdgeIgnoreOtherObject( objectEnum ) { - return ( this.isObject1( objectEnum ) && this.objectAtTouchingMin( objectEnum ) ) || - ( this.isObject2( objectEnum ) && this.objectAtTouchingMax( objectEnum ) ); + return this.object1AtMin( objectEnum ) || this.object2AtMax( objectEnum ); }