Index: sun/js/Checkbox.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/sun/js/Checkbox.js b/sun/js/Checkbox.js --- a/sun/js/Checkbox.js (revision 0c107a2323019ce22ab6510d7f914e2acc5355f1) +++ b/sun/js/Checkbox.js (date 1643061986302) @@ -34,7 +34,7 @@ const uncheckedShape = checkEmptySolidShape.transformed( SHAPE_MATRIX ); const checkedShape = checkSquareOSolidShape.transformed( SHAPE_MATRIX ); -class Checkbox extends Node { +class Checkbox extends Voicing( Node ) { /** * @param {Node} content @@ -88,9 +88,6 @@ super(); - // voicing - initialize the Trait - this.initializeVoicing(); - // @private - sends out notifications when the checkbox is toggled. const toggleAction = new Action( () => { property.value = !property.value; @@ -240,7 +237,5 @@ get checkboxColor() { return this.getCheckboxColor(); } } -Voicing.compose( Checkbox ); - sun.register( 'Checkbox', Checkbox ); export default Checkbox; \ No newline at end of file Index: sun/js/accessibility/AccessibleValueHandler.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/sun/js/accessibility/AccessibleValueHandler.js b/sun/js/accessibility/AccessibleValueHandler.js --- a/sun/js/accessibility/AccessibleValueHandler.js (revision 0c107a2323019ce22ab6510d7f914e2acc5355f1) +++ b/sun/js/accessibility/AccessibleValueHandler.js (date 1643062538085) @@ -46,7 +46,8 @@ const proto = type.prototype; // compose with Interactive Highlights, all Nodes with Voicing features highlight as they are interactive - Voicing.compose( type ); + // TODO: handle this case, https://github.com/phetsims/scenery/issues/1340 + // Voicing.compose( type ); extend( proto, { @@ -216,7 +217,7 @@ optionsToMutate.inputType = 'range'; // Should occur before mutate to support mutating Voicing options. - this.initializeVoicing(); + // this.initializeVoicing(); this.mutate( optionsToMutate ); Index: ratio-and-proportion/js/common/view/RatioHandNode.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/ratio-and-proportion/js/common/view/RatioHandNode.ts b/ratio-and-proportion/js/common/view/RatioHandNode.ts --- a/ratio-and-proportion/js/common/view/RatioHandNode.ts (revision 4a0bd79b66243fc1158eaa39402432812938e738) +++ b/ratio-and-proportion/js/common/view/RatioHandNode.ts (date 1643064704511) @@ -16,7 +16,7 @@ import ArrowNode from '../../../../scenery-phet/js/ArrowNode.js'; import ArrowKeyNode from '../../../../scenery-phet/js/keyboard/ArrowKeyNode.js'; import LetterKeyNode from '../../../../scenery-phet/js/keyboard/LetterKeyNode.js'; -import { Color, FocusHighlightFromNode, Node, NodeOptions, Path, PathOptions } from '../../../../scenery/js/imports.js'; +import { Color, FocusHighlightFromNode, Node, NodeOptions, Path, PathOptions, Voicing } from '../../../../scenery/js/imports.js'; import AccessibleSlider from '../../../../sun/js/accessibility/AccessibleSlider.js'; import Tandem from '../../../../tandem/js/Tandem.js'; import ratioAndProportion from '../../ratioAndProportion.js'; @@ -36,7 +36,7 @@ handNodeOptions?: NodeOptions }; -class RatioHandNode extends Node { +class RatioHandNode extends Voicing( Node ) { private resetRatioHandNode: () => void; /** Index: sun/js/ToggleSwitch.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/sun/js/ToggleSwitch.js b/sun/js/ToggleSwitch.js --- a/sun/js/ToggleSwitch.js (revision 0c107a2323019ce22ab6510d7f914e2acc5355f1) +++ b/sun/js/ToggleSwitch.js (date 1643062136891) @@ -29,7 +29,7 @@ // constants const DEFAULT_SIZE = new Dimension2( 60, 30 ); -class ToggleSwitch extends Node { +class ToggleSwitch extends Voicing( Node ) { /** * @param {Property.<*>} property @@ -102,8 +102,6 @@ super(); - this.initializeVoicing(); - const cornerRadius = options.size.height / 2; // track that the thumb slides in @@ -271,7 +269,5 @@ } } -Voicing.compose( ToggleSwitch ); - sun.register( 'ToggleSwitch', ToggleSwitch ); export default ToggleSwitch; \ No newline at end of file Index: sun/js/AquaRadioButton.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/sun/js/AquaRadioButton.js b/sun/js/AquaRadioButton.js --- a/sun/js/AquaRadioButton.js (revision 0c107a2323019ce22ab6510d7f914e2acc5355f1) +++ b/sun/js/AquaRadioButton.js (date 1643061986244) @@ -21,7 +21,7 @@ // constants const DEFAULT_RADIUS = 7; -class AquaRadioButton extends Node { +class AquaRadioButton extends Voicing( Node ) { /** * @mixes {Voicing} @@ -72,9 +72,6 @@ super(); - // voicing - initialize the trait - this.initializeVoicing(); - // @public (read-only) this.value = value; @@ -183,7 +180,5 @@ AquaRadioButton.DEFAULT_RADIUS = DEFAULT_RADIUS; -Voicing.compose( AquaRadioButton ); - sun.register( 'AquaRadioButton', AquaRadioButton ); export default AquaRadioButton; \ No newline at end of file Index: sun/js/ComboBoxListItemNode.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/sun/js/ComboBoxListItemNode.js b/sun/js/ComboBoxListItemNode.js --- a/sun/js/ComboBoxListItemNode.js (revision 0c107a2323019ce22ab6510d7f914e2acc5355f1) +++ b/sun/js/ComboBoxListItemNode.js (date 1643062136870) @@ -18,7 +18,7 @@ import ComboBoxItem from './ComboBoxItem.js'; import sun from './sun.js'; -class ComboBoxListItemNode extends Node { +class ComboBoxListItemNode extends Voicing( Node ) { /** * @param {ComboBoxItem} item @@ -98,9 +98,6 @@ super(); - // voicing - initialize the Voicing trait - this.initializeVoicing(); - // @public (read-only) this.item = item; @@ -120,7 +117,5 @@ } } -Voicing.compose( ComboBoxListItemNode ); - sun.register( 'ComboBoxListItemNode', ComboBoxListItemNode ); export default ComboBoxListItemNode; \ No newline at end of file Index: scenery/js/accessibility/voicing/Voicing.ts IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/scenery/js/accessibility/voicing/Voicing.ts b/scenery/js/accessibility/voicing/Voicing.ts --- a/scenery/js/accessibility/voicing/Voicing.ts (revision faa4e9d6dbd874a6d3bd9a0b6dd4acabb54651b9) +++ b/scenery/js/accessibility/voicing/Voicing.ts (date 1643070692205) @@ -23,13 +23,14 @@ * @author Jesse Greenberg (PhET Interactive Simulations) */ -import extend from '../../../../phet-core/js/extend.js'; import inheritance from '../../../../phet-core/js/inheritance.js'; -import merge from '../../../../phet-core/js/merge.js'; import responseCollector from '../../../../utterance-queue/js/responseCollector.js'; -import ResponsePacket from '../../../../utterance-queue/js/ResponsePacket.js'; +import ResponsePacket, { ResponsePacketOptions } from '../../../../utterance-queue/js/ResponsePacket.js'; import ResponsePatternCollection from '../../../../utterance-queue/js/ResponsePatternCollection.js'; -import { InteractiveHighlighting, Node, scenery, voicingUtteranceQueue } from '../../imports.js'; +import Utterance from '../../../../utterance-queue/js/Utterance.js'; +import UtteranceQueue from '../../../../utterance-queue/js/UtteranceQueue.js'; +import { InteractiveHighlighting, Node, scenery, SceneryEvent, voicingUtteranceQueue } from '../../imports.js'; +import optionize from '../../../../phet-core/js/optionize.js'; // options that are supported by Voicing.js. Added to mutator keys so that Voicing properties can be set with mutate. const VOICING_OPTION_KEYS = [ @@ -43,462 +44,414 @@ 'voicingFocusListener' ]; -const Voicing = { +type Constructor = new ( ...args: any[] ) => T; - /** - * @public - * @trait {Node} - * @mixes {InteractiveHighlighting} - * @param {function(new:Node)} type - The type (constructor) whose prototype that is modified. Should be a Node class. - */ - compose( type ) { - assert && assert( _.includes( inheritance( type ), Node ), 'Only Node subtypes should compose Voicing' ); +type ResponseOptions = { + utterance?: Utterance | null; +} & ResponsePacketOptions; - const proto = type.prototype; - // compose with Interactive Highlights, all Nodes with Voicing features highlight as they are interactive - InteractiveHighlighting.compose( type ); - - extend( proto, { +type SceneryListener = ( event: SceneryEvent ) => void; - /** - * {Array.} - String keys for all of the allowed options that will be set by node.mutate( options ), in - * the order they will be evaluated. - * @protected - * - * NOTE: See Node's _mutatorKeys documentation for more information on how this operates, and potential special - * cases that may apply. - */ - _mutatorKeys: VOICING_OPTION_KEYS.concat( proto._mutatorKeys ), +/** + * @param {function(new:Node)} Type + * @returns {function(new:Node)} + */ +const Voicing = ( Type: Constructor ) => { + + assert && assert( _.includes( inheritance( Type ), Node ), 'Only Node subtypes should compose Voicing' ); + - /** - * Initialize in the type being composed with Voicing. Call this in the constructor. Note, this must be called before - * options are mutated, so most often you must call options via `mutate()` instead of passing directly to `super()`. - * @public - */ - initializeVoicing() { + /** + * @extends Node + * @mixes InteractiveHighlighting + */ + const X = class extends Type { + private voicingResponsePacket: ResponsePacket; + private _voicingUtteranceQueue: UtteranceQueue | null; + private _voicingFocusListener: SceneryListener; + private speakContentOnFocusListener: { focus: SceneryListener }; - // undefined OR support poolable with the value set by dispose - assert && assert( this.voicingInitialized === undefined || this.voicingInitialized === false, 'Voicing has already been initialized for this Node' ); + constructor( ...args: any[] ) { + super( ...args ); - // initialize "super" Trait to support highlights on mouse input - this.initializeInteractiveHighlighting(); + // initialize "super" Trait to support highlights on mouse input + // TODO: Fix this, https://github.com/phetsims/scenery/issues/1340 + // @ts-ignore + this.initializeInteractiveHighlighting(); - // @private {boolean} - to make sure that initializeVoicing is called before trying to use the mixin. - this.voicingInitialized = true; - - // @public {ResponsePacket} - ResponsePacket that holds all the supported responses to be Voiced - this.voicingResponsePacket = new ResponsePacket(); + // @public {ResponsePacket} - ResponsePacket that holds all the supported responses to be Voiced + this.voicingResponsePacket = new ResponsePacket(); - // @private {UtteranceQueue|null} - The utteranceQueue that responses for this Node will be spoken through. - // By default (null), it will go through the singleton voicingUtteranceQueue, but you may need separate - // UtteranceQueues for different areas of content in your application. For example, Voicing and - // the default voicingUtteranceQueue may be disabled, but you could still want some speech to come through - // while user is changing preferences or other settings. - this._voicingUtteranceQueue = null; + // @private {UtteranceQueue|null} - The utteranceQueue that responses for this Node will be spoken through. + // By default (null), it will go through the singleton voicingUtteranceQueue, but you may need separate + // UtteranceQueues for different areas of content in your application. For example, Voicing and + // the default voicingUtteranceQueue may be disabled, but you could still want some speech to come through + // while user is changing preferences or other settings. + this._voicingUtteranceQueue = null; - // @private {Function(event):} - called when this node is focused. - this._voicingFocusListener = this.defaultFocusListener; + // @private {Function(event):} - called when this node is focused. + this._voicingFocusListener = this.defaultFocusListener; - // @private {Object} - Input listener that speaks content on focus. This is the only input listener added - // by Voicing, but it is the one that is consistent for all Voicing nodes. On focus, speak the name, object - // response, and interaction hint. - this.speakContentOnFocusListener = { - focus: event => { - this._voicingFocusListener( event ); - } - }; - this.addInputListener( this.speakContentOnFocusListener ); - }, + // @private {Object} - Input listener that speaks content on focus. This is the only input listener added + // by Voicing, but it is the one that is consistent for all Voicing nodes. On focus, speak the name, object + // response, and interaction hint. + this.speakContentOnFocusListener = { + focus: event => { + this._voicingFocusListener( event ); + } + }; + this.addInputListener( this.speakContentOnFocusListener ); + } - /** - * Speak all responses assigned to this Node. Options allow you to override a responses for this particular - * speech request. Each response is only spoken if the associated Property of responseCollector is true. If - * all are Properties are false, nothing will be spoken. - * @public - * - * @param {Object} [options] - */ - voicingSpeakFullResponse( options ) { - assert && assert( this.voicingInitialized, 'voicing must be initialized to speak' ); + /** + * Speak all responses assigned to this Node. Options allow you to override a responses for this particular + * speech request. Each response is only spoken if the associated Property of responseCollector is true. If + * all are Properties are false, nothing will be spoken. + */ + voicingSpeakFullResponse( providedOptions?: ResponseOptions ): void { - // options are passed along to collectAndSpeakResponse, see that function for additional options - options = merge( { - nameResponse: this.voicingResponsePacket.nameResponse, - objectResponse: this.voicingResponsePacket.objectResponse, - contextResponse: this.voicingResponsePacket.contextResponse, - hintResponse: this.voicingResponsePacket.hintResponse - }, options ); + // options are passed along to collectAndSpeakResponse, see that function for additional options + const options = optionize( { + nameResponse: this.voicingResponsePacket.nameResponse, + objectResponse: this.voicingResponsePacket.objectResponse, + contextResponse: this.voicingResponsePacket.contextResponse, + hintResponse: this.voicingResponsePacket.hintResponse + }, providedOptions ); - this.collectAndSpeakResponse( options ); - }, + this.collectAndSpeakResponse( options ); + } - /** - * Speak ONLY the provided responses that you pass in with options. This will NOT speak the name, object, - * context, or hint responses assigned to this node by default. But it allows for clarity at usages so it is - * clear that you are only requesting certain responses. If you want to speak all of the responses assigned - * to this Node, use voicingSpeakFullResponse(). - * - * Each response will only be spoken if the Properties of responseCollector are true. If all of those are false, - * nothing will be spoken. - * @public - * - * @param {Object} [options] - */ - voicingSpeakResponse( options ) { - assert && assert( this.voicingInitialized, 'voicing must be initialized to speak' ); + /** + * Speak ONLY the provided responses that you pass in with options. This will NOT speak the name, object, + * context, or hint responses assigned to this node by default. But it allows for clarity at usages so it is + * clear that you are only requesting certain responses. If you want to speak all of the responses assigned + * to this Node, use voicingSpeakFullResponse(). + * + * Each response will only be spoken if the Properties of responseCollector are true. If all of those are false, + * nothing will be spoken. + */ + voicingSpeakResponse( providedOptions?: ResponseOptions ): void { - // options are passed along to collectAndSpeakResponse, see that function for additional options - options = merge( { - nameResponse: null, - objectResponse: null, - contextResponse: null, - hintResponse: null - }, options ); + // options are passed along to collectAndSpeakResponse, see that function for additional options + const options = optionize( { + nameResponse: null, + objectResponse: null, + contextResponse: null, + hintResponse: null + }, providedOptions ); - this.collectAndSpeakResponse( options ); - }, + this.collectAndSpeakResponse( options ); + } - /** - * By default, speak the name response. But accepts all other responses through options. Respects responseCollector - * Properties, so the name response may not be spoken if responseCollector.nameResponseEnabledProperty is false. - * @public - * - * @param {Object} [options] - */ - voicingSpeakNameResponse( options ) { - assert && assert( this.voicingInitialized, 'voicing must be initialized to speak' ); + /** + * By default, speak the name response. But accepts all other responses through options. Respects responseCollector + * Properties, so the name response may not be spoken if responseCollector.nameResponseEnabledProperty is false. + */ + voicingSpeakNameResponse( providedOptions?: ResponseOptions ): void { - // options are passed along to collectAndSpeakResponse, see that function for additional options - options = merge( { - nameResponse: this.voicingResponsePacket.nameResponse - }, options ); + // options are passed along to collectAndSpeakResponse, see that function for additional options + const options = optionize( { + nameResponse: this.voicingResponsePacket.nameResponse + }, providedOptions ); - this.collectAndSpeakResponse( options ); - }, + this.collectAndSpeakResponse( options ); + } - /** - * By default, speak the object response. But accepts all other responses through options. Respects responseCollector - * Properties, so the name response may not be spoken if responseCollector.objectResponseEnabledProperty is false. - * @public - * - * @param {Object} [options] - */ - voicingSpeakObjectResponse( options ) { - assert && assert( this.voicingInitialized, 'voicing must be initialized to speak' ); + /** + * By default, speak the object response. But accepts all other responses through options. Respects responseCollector + * Properties, so the name response may not be spoken if responseCollector.objectResponseEnabledProperty is false. + */ + voicingSpeakObjectResponse( providedOptions?: ResponseOptions ): void { - // options are passed along to collectAndSpeakResponse, see that function for additional options - options = merge( { - objectResponse: this.voicingResponsePacket.objectResponse - }, options ); + // options are passed along to collectAndSpeakResponse, see that function for additional options + const options = optionize( { + objectResponse: this.voicingResponsePacket.objectResponse + }, providedOptions ); - this.collectAndSpeakResponse( options ); - }, + this.collectAndSpeakResponse( options ); + } - /** - * By default, speak the context response. But accepts all other responses through options. Respects - * responseCollector Properties, so the name response may not be spoken if - * responseCollector.contextResponseEnabledProperty is false. - * @public - * - * @param {Object} [options] - */ - voicingSpeakContextResponse( options ) { - assert && assert( this.voicingInitialized, 'voicing must be initialized to speak' ); + /** + * By default, speak the context response. But accepts all other responses through options. Respects + * responseCollector Properties, so the name response may not be spoken if + * responseCollector.contextResponseEnabledProperty is false. + */ + voicingSpeakContextResponse( providedOptions?: ResponseOptions ): void { - // options are passed along to collectAndSpeakResponse, see that function for additional options - options = merge( { - contextResponse: this.voicingResponsePacket.contextResponse - }, options ); + // options are passed along to collectAndSpeakResponse, see that function for additional options + const options = optionize( { + contextResponse: this.voicingResponsePacket.contextResponse + }, providedOptions ); - this.collectAndSpeakResponse( options ); - }, + this.collectAndSpeakResponse( options ); + } - /** - * By default, speak the hint response. But accepts all other responses through options. Respects - * responseCollector Properties, so the hint response may not be spoken if - * responseCollector.hintResponseEnabledProperty is false. - * @public - * - * @param {Object} [options] - */ - voicingSpeakHintResponse( options ) { - assert && assert( this.voicingInitialized, 'voicing must be initialized to speak' ); + /** + * By default, speak the hint response. But accepts all other responses through options. Respects + * responseCollector Properties, so the hint response may not be spoken if + * responseCollector.hintResponseEnabledProperty is false. + */ + voicingSpeakHintResponse( providedOptions?: ResponseOptions ): void { - // options are passed along to collectAndSpeakResponse, see that function for additional options - options = merge( { - hintResponse: this.voicingResponsePacket.hintResponse - }, options ); + // options are passed along to collectAndSpeakResponse, see that function for additional options + const options = optionize( { + hintResponse: this.voicingResponsePacket.hintResponse + }, providedOptions ); - this.collectAndSpeakResponse( options ); - }, + this.collectAndSpeakResponse( options ); + } - /** - * Collect responses with the responseCollector and speak the output with an UtteranceQueue. - * @protected - * - * @param {Object} [options] - */ - collectAndSpeakResponse( options ) { - options = merge( { + /** + * Collect responses with the responseCollector and speak the output with an UtteranceQueue. + * + * TODO: we want this to be @protected, https://github.com/phetsims/scenery/issues/1340 + * @public + */ + collectAndSpeakResponse( providedOptions?: ResponseOptions ): void { + const options = optionize( { - // {boolean} - whether or not this response should ignore the Properties of responseCollector - ignoreProperties: this.voicingResponsePacket.ignoreProperties, + // {boolean} - whether or not this response should ignore the Properties of responseCollector + ignoreProperties: this.voicingResponsePacket.ignoreProperties, - // {Object} - collection of string patterns to use with responseCollector.collectResponses, see - // ResponsePatternCollection for more information. - responsePatternCollection: this.voicingResponsePacket.responsePatternCollection, + // {Object} - collection of string patterns to use with responseCollector.collectResponses, see + // ResponsePatternCollection for more information. + responsePatternCollection: this.voicingResponsePacket.responsePatternCollection, - // {Utterance|null} - The utterance to use if you want this response to be more controlled in the - // UtteranceQueue. - utterance: null - }, options ); + // {Utterance|null} - The utterance to use if you want this response to be more controlled in the + // UtteranceQueue. + utterance: null + }, providedOptions ); - let response = responseCollector.collectResponses( options ); + let response: AlertableDef = responseCollector.collectResponses( options ); - if ( options.utterance ) { - options.utterance.alert = response; - response = options.utterance; - } - this.speakContent( response ); - }, + if ( options.utterance ) { + options.utterance.alert = response; + response = options.utterance; + } + this.speakContent( response ); + } - /** - * Use the provided function to create content to speak in response to input. The content is then added to the - * back of the voicing UtteranceQueue. - * @protected - * - * @param {null|AlertableDef} content - */ - speakContent( content ) { + /** + * Use the provided function to create content to speak in response to input. The content is then added to the + * back of the voicing UtteranceQueue. + * + * TODO: we want this to be @protected, https://github.com/phetsims/scenery/issues/1340 + * @public + * + */ + speakContent( content: AlertableDef | null ): void { - // don't send to utteranceQueue if response is empty - if ( content ) { - const utteranceQueue = this.voicingUtteranceQueue || voicingUtteranceQueue; - utteranceQueue.addToBack( content ); - } - }, + // don't send to utteranceQueue if response is empty + if ( content ) { + const utteranceQueue = this.voicingUtteranceQueue || voicingUtteranceQueue; + utteranceQueue.addToBack( content ); + } + } - /** - * Sets the voicingNameResponse for this Node. This is usually the label of the element and is spoken - * when the object receives input. When requesting speech, this will only be spoken if - * responseCollector.nameResponsesEnabledProperty is set to true. - * - * @public - * - * @param {string|null} response - */ - setVoicingNameResponse( response ) { - this.voicingResponsePacket.nameResponse = response; - }, - set voicingNameResponse( response ) { this.setVoicingNameResponse( response ); }, + /** + * Sets the voicingNameResponse for this Node. This is usually the label of the element and is spoken + * when the object receives input. When requesting speech, this will only be spoken if + * responseCollector.nameResponsesEnabledProperty is set to true. + */ + setVoicingNameResponse( response: string | null ): void { + this.voicingResponsePacket.nameResponse = response; + } + + set voicingNameResponse( response ) { this.setVoicingNameResponse( response ); } - /** - * Get the voicingNameResponse for this Node. - * @public - * - * @returns {string|null} - */ - getVoicingNameResponse() { - return this.voicingResponsePacket.nameResponse; - }, - get voicingNameResponse() { return this.getVoicingNameResponse(); }, + /** + * Get the voicingNameResponse for this Node. + */ + getVoicingNameResponse() { + return this.voicingResponsePacket.nameResponse; + } + + get voicingNameResponse() { return this.getVoicingNameResponse(); } - /** - * Set the object response for this Node. This is usually the state information associated with this Node, such - * as its current input value. When requesting speech, this will only be heard when - * responseCollector.objectResponsesEnabledProperty is set to true. - * @public - * - * @param {string|null} response - */ - setVoicingObjectResponse( response ) { - this.voicingResponsePacket.objectResponse = response; - }, - set voicingObjectResponse( response ) { this.setVoicingObjectResponse( response ); }, + /** + * Set the object response for this Node. This is usually the state information associated with this Node, such + * as its current input value. When requesting speech, this will only be heard when + * responseCollector.objectResponsesEnabledProperty is set to true. + */ + setVoicingObjectResponse( response: string | null ) { + this.voicingResponsePacket.objectResponse = response; + } + + set voicingObjectResponse( response ) { this.setVoicingObjectResponse( response ); } - /** - * Gets the object response for this Node. - * @public - * - * @returns {string|null} - */ - getVoicingObjectResponse() { - return this.voicingResponsePacket.objectResponse; - }, - get voicingObjectResponse() { return this.getVoicingObjectResponse(); }, + /** + * Gets the object response for this Node. + */ + getVoicingObjectResponse() { + return this.voicingResponsePacket.objectResponse; + } + + get voicingObjectResponse() { return this.getVoicingObjectResponse(); } - /** - * Set the context response for this Node. This is usually the content that describes what has happened in - * the surrounding application in response to interaction with this Node. When requesting speech, this will - * only be heard if responseCollector.contextResponsesEnabledProperty is set to true. - * @public - * - * @param {string|null} response - */ - setVoicingContextResponse( response ) { - this.voicingResponsePacket.contextResponse = response; - }, - set voicingContextResponse( response ) { this.setVoicingContextResponse( response ); }, + /** + * Set the context response for this Node. This is usually the content that describes what has happened in + * the surrounding application in response to interaction with this Node. When requesting speech, this will + * only be heard if responseCollector.contextResponsesEnabledProperty is set to true. + */ + setVoicingContextResponse( response: string | null ) { + this.voicingResponsePacket.contextResponse = response; + } + + set voicingContextResponse( response ) { this.setVoicingContextResponse( response ); } - /** - * Gets the context response for this Node. - * @public - * - * @returns {string|null} - */ - getVoicingContextResponse() { - return this.voicingResponsePacket.contextResponse; - }, - get voicingContextResponse() { return this.getVoicingContextResponse(); }, + /** + * Gets the context response for this Node. + */ + getVoicingContextResponse() { + return this.voicingResponsePacket.contextResponse; + } + + get voicingContextResponse() { return this.getVoicingContextResponse(); } - /** - * Sets the hint response for this Node. This is usually a response that describes how to interact with this Node. - * When requesting speech, this will only be spoken when responseCollector.hintResponsesEnabledProperty is set to - * true. - * @public - * - * @param {string|null} response - */ - setVoicingHintResponse( response ) { - this.voicingResponsePacket.hintResponse = response; - }, - set voicingHintResponse( response ) { this.setVoicingHintResponse( response ); }, + /** + * Sets the hint response for this Node. This is usually a response that describes how to interact with this Node. + * When requesting speech, this will only be spoken when responseCollector.hintResponsesEnabledProperty is set to + * true. + */ + setVoicingHintResponse( response: string | null ) { + this.voicingResponsePacket.hintResponse = response; + } + + set voicingHintResponse( response ) { this.setVoicingHintResponse( response ); } - /** - * Gets the hint response for this Node. - * @public - * - * @returns {string|null} - */ - getVoicingHintResponse() { - return this.voicingResponsePacket.hintResponse; - }, - get voicingHintResponse() { return this.getVoicingHintResponse(); }, + /** + * Gets the hint response for this Node. + */ + getVoicingHintResponse() { + return this.voicingResponsePacket.hintResponse; + } + + get voicingHintResponse() { return this.getVoicingHintResponse(); } - /** - * Set whether or not all responses for this Node will ignore the Properties of responseCollector. If false, - * all responses will be spoken regardless of responseCollector Properties, which are generally set in user - * preferences. - * @public - */ - setVoicingIgnoreVoicingManagerProperties( ignoreProperties ) { - this.voicingResponsePacket.ignoreProperties = ignoreProperties; - }, - set voicingIgnoreVoicingManagerProperties( ignoreProperties ) { this.setVoicingIgnoreVoicingManagerProperties( ignoreProperties ); }, + /** + * Set whether or not all responses for this Node will ignore the Properties of responseCollector. If false, + * all responses will be spoken regardless of responseCollector Properties, which are generally set in user + * preferences. + */ + setVoicingIgnoreVoicingManagerProperties( ignoreProperties: boolean ) { + this.voicingResponsePacket.ignoreProperties = ignoreProperties; + } + + set voicingIgnoreVoicingManagerProperties( ignoreProperties ) { this.setVoicingIgnoreVoicingManagerProperties( ignoreProperties ); } - /** - * Get whether or not responses are ignoring responseCollector Properties. - */ - getVoicingIgnoreVoicingManagerProperties() { - return this.voicingResponsePacket.ignoreProperties; - }, - get voicingIgnoreVoicingManagerProperties() { return this.getVoicingIgnoreVoicingManagerProperties(); }, + /** + * Get whether or not responses are ignoring responseCollector Properties. + */ + getVoicingIgnoreVoicingManagerProperties() { + return this.voicingResponsePacket.ignoreProperties; + } + + get voicingIgnoreVoicingManagerProperties() { return this.getVoicingIgnoreVoicingManagerProperties(); } - /** - * Sets the collection of patterns to use for voicing responses, controlling the order, punctuation, and - * additional content for each combination of response. See ResponsePatternCollection.js if you wish to use - * a collection of string patterns that are not the default. - * @public - * - * @param {ResponsePatternCollection} patterns - see ResponsePatternCollection - */ - setVoicingResponsePatternCollection( patterns ) { - assert && assert( patterns instanceof ResponsePatternCollection ); - this.voicingResponsePacket.responsePatternCollection = patterns; - }, - set voicingResponsePatternCollection( patterns ) { this.setVoicingResponsePatternCollection( patterns ); }, + /** + * Sets the collection of patterns to use for voicing responses, controlling the order, punctuation, and + * additional content for each combination of response. See ResponsePatternCollection.js if you wish to use + * a collection of string patterns that are not the default. + */ + setVoicingResponsePatternCollection( patterns: ResponsePatternCollection ) { + assert && assert( patterns instanceof ResponsePatternCollection ); + this.voicingResponsePacket.responsePatternCollection = patterns; + } + + set voicingResponsePatternCollection( patterns ) { this.setVoicingResponsePatternCollection( patterns ); } - /** - * Get the ResponsePatternCollection object that this Voicing Node is using to collect responses. - * @public - * - * @returns {ResponsePatternCollection} - */ - getVoicingResponsePatternCollection() { - return this.voicingResponsePacket.responsePatternCollection; - }, - get voicingResponsePatternCollection() { return this.getVoicingResponsePatternCollection(); }, + /** + * Get the ResponsePatternCollection object that this Voicing Node is using to collect responses. + */ + getVoicingResponsePatternCollection() { + return this.voicingResponsePacket.responsePatternCollection; + } + + get voicingResponsePatternCollection() { return this.getVoicingResponsePatternCollection(); } - /** - * Sets the utteranceQueue through which voicing associated with this Node will be spoken. By default, - * the Display's voicingUtteranceQueue is used. But you can specify a different one if more complicated - * management of voicing is necessary. - * @public - * - * @param {UtteranceQueue} utteranceQueue - */ - setVoicingUtteranceQueue( utteranceQueue ) { - this._voicingUtteranceQueue = utteranceQueue; - }, + /** + * Sets the utteranceQueue through which voicing associated with this Node will be spoken. By default, + * the Display's voicingUtteranceQueue is used. But you can specify a different one if more complicated + * management of voicing is necessary. + */ + setVoicingUtteranceQueue( utteranceQueue: UtteranceQueue | null ) { + this._voicingUtteranceQueue = utteranceQueue; + } - set voicingUtteranceQueue( utteranceQueue ) { this.setVoicingUtteranceQueue( utteranceQueue ); }, + set voicingUtteranceQueue( utteranceQueue: UtteranceQueue | null ) { this.setVoicingUtteranceQueue( utteranceQueue ); } - /** - * Gets the utteranceQueue through which voicing associated with this Node will be spoken. - * @public - * - * @returns {UtteranceQueue} - */ - getVoicingUtteranceQueue() { - return this._voicingUtteranceQueue; - }, - get voicingUtteranceQueue() { return this.getVoicingUtteranceQueue(); }, + /** + * Gets the utteranceQueue through which voicing associated with this Node will be spoken. + */ + getVoicingUtteranceQueue() { + return this._voicingUtteranceQueue; + } + + get voicingUtteranceQueue() { return this.getVoicingUtteranceQueue(); } - /** - * Called whenever this Node is focused. - * @public - * - * @param {function(SceneryEvent):} focusListener - */ - setVoicingFocusListener( focusListener ) { - this._voicingFocusListener = focusListener; - }, + /** + * Called whenever this Node is focused. + */ + setVoicingFocusListener( focusListener: SceneryListener ) { + this._voicingFocusListener = focusListener; + } - set voicingFocusListener( utteranceQueue ) { this.setVoicingFocusListener( utteranceQueue ); }, + set voicingFocusListener( focusListener: SceneryListener ) { this.setVoicingFocusListener( focusListener ); } - /** - * Gets the utteranceQueue through which voicing associated with this Node will be spoken. - * @public - * - * @returns {UtteranceQueue} - */ - getVoicingFocusListener() { - return this._voicingFocusListener; - }, - get voicingFocusListener() { return this.getVoicingFocusListener(); }, + /** + * Gets the utteranceQueue through which voicing associated with this Node will be spoken. + */ + getVoicingFocusListener(): SceneryListener { + return this._voicingFocusListener; + } + + get voicingFocusListener() { return this.getVoicingFocusListener(); } - /** - * The default focus listener attached to this Node during initialization. - * @public - */ - defaultFocusListener() { - this.voicingSpeakFullResponse( { - contextResponse: null - } ); - }, + /** + * The default focus listener attached to this Node during initialization. + */ + defaultFocusListener(): void { + this.voicingSpeakFullResponse( { + contextResponse: null + } ); + } - /** - * Whether or not a Node composes Voicing. - * @public - * @returns {boolean} - */ - get isVoicing() { - return true; - }, + /** + * Whether or not a Node composes Voicing. + */ + get isVoicing() { + return true; + } - /** - * Detaches references that ensure this components of this Trait are eligible for garbage collection. - * @public - */ - disposeVoicing() { - this.voicingInitialized = false; - this.removeInputListener( this.speakContentOnFocusListener ); - this.disposeInteractiveHighlighting(); - } - } ); - } + /** + * Detaches references that ensure this components of this Trait are eligible for garbage collection. + * @public + */ + disposeVoicing() { + this.removeInputListener( this.speakContentOnFocusListener ); + + // @ts-ignore + this.disposeInteractiveHighlighting(); + } + }; + + // compose with Interactive Highlights, all Nodes with Voicing features highlight as they are interactive + InteractiveHighlighting.compose( X ); + + /** + * {Array.} - String keys for all of the allowed options that will be set by node.mutate( options ), in + * the order they will be evaluated. + * + * TODO: we want this to be @protected, https://github.com/phetsims/scenery/issues/1340 + * @public + * + * NOTE: See Node's _mutatorKeys documentation for more information on how this operates, and potential special + * cases that may apply. + */ + X.prototype._mutatorKeys = _.uniq( ( X.prototype._mutatorKeys ? X.prototype._mutatorKeys : [] ).concat( VOICING_OPTION_KEYS ) ); + return X; }; // @pulic Index: scenery/js/accessibility/voicing/ReadingBlock.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/scenery/js/accessibility/voicing/ReadingBlock.js b/scenery/js/accessibility/voicing/ReadingBlock.js --- a/scenery/js/accessibility/voicing/ReadingBlock.js (revision faa4e9d6dbd874a6d3bd9a0b6dd4acabb54651b9) +++ b/scenery/js/accessibility/voicing/ReadingBlock.js (date 1643066948389) @@ -19,7 +19,6 @@ import TinyEmitter from '../../../../axon/js/TinyEmitter.js'; import Shape from '../../../../kite/js/Shape.js'; -import extend from '../../../../phet-core/js/extend.js'; import inheritance from '../../../../phet-core/js/inheritance.js'; import StringUtils from '../../../../phetcommon/js/util/StringUtils.js'; import responseCollector from '../../../../utterance-queue/js/responseCollector.js'; @@ -34,350 +33,344 @@ const CONTENT_HINT_PATTERN = '{{readingBlockContent}}. {{hintResponse}}'; -const ReadingBlock = { - - /** - * @public - * @trait {Node} - * @mixes {Voicing} - * @param {function(new:Node)} type - The constructor for Node - */ - compose( type ) { - assert && assert( _.includes( inheritance( type ), Node ) ); +const ReadingBlock = Type => { - const proto = type.prototype; + assert && assert( _.includes( inheritance( Type ), Node ), 'Only Node subtypes should compose Voicing' ); - // compose with Voicing - Voicing.compose( type ); + const X = class extends Voicing( Type ) { - extend( proto, { - - /** - * {Array.} - String keys for all of the allowed options that will be set by node.mutate( options ), in - * the order they will be evaluated. - * @protected - * - * NOTE: See Node's _mutatorKeys documentation for more information on how this operates, and potential special - * cases that may apply. - */ - _mutatorKeys: READING_BLOCK_OPTION_KEYS.concat( proto._mutatorKeys ), - /** - * This should be called in the constructor to initialize ReadingBlock. Note, this must be called before - * options are mutated, so most often you must call options via `mutate()` instead of passing directly to - * `super()`. - */ - initializeReadingBlock() { + /** + * This should be called in the constructor to initialize ReadingBlock. Note, this must be called before + * options are mutated, so most often you must call options via `mutate()` instead of passing directly to + * `super()`. + */ + constructor( ...args ) { - // initialize the parent trait - this.initializeVoicing(); + super( ...args ); - // @private {boolean} - to make sure that initializeReadingBlock is called before trying to use trait features - this.readingBlockInitialized = true; + // TODO: delete, https://github.com/phetsims/scenery/issues/1340 + // @private {boolean} - to make sure that initializeReadingBlock is called before trying to use trait features + this.readingBlockInitialized = true; - // @private {string|null} - The tagName used for the ReadingBlock when "Voicing" is enabled, default - // of button so that it is added to the focus order and can receive 'click' events. You may wish to set this - // to some other tagName or set to null to remove the ReadingBlock from the focus order. If this is changed, - // be be sure that the ReadingBlock will still respond to `click` events when enabled. - this._readingBlockTagName = 'button'; + // @private {string|null} - The tagName used for the ReadingBlock when "Voicing" is enabled, default + // of button so that it is added to the focus order and can receive 'click' events. You may wish to set this + // to some other tagName or set to null to remove the ReadingBlock from the focus order. If this is changed, + // be be sure that the ReadingBlock will still respond to `click` events when enabled. + this._readingBlockTagName = 'button'; - // @private {string|null} - The content for this ReadingBlock that will be spoken by SpeechSynthesis when - // the ReadingBlock receives input. ReadingBlocks don't use the categories of Voicing content provided by - // Voicing.js because ReadingBlocks are always spoken regardless of the Properties of responseCollector. - this._readingBlockContent = null; + // @private {string|null} - The content for this ReadingBlock that will be spoken by SpeechSynthesis when + // the ReadingBlock receives input. ReadingBlocks don't use the categories of Voicing content provided by + // Voicing.ts because ReadingBlocks are always spoken regardless of the Properties of responseCollector. + this._readingBlockContent = null; - // @private {string|null} - The help content that is read when this ReadingBlock is activated by input, - // but only when "Helpful Hints" is enabled by the user. - this._readingBlockHintResponse = null; + // @private {string|null} - The help content that is read when this ReadingBlock is activated by input, + // but only when "Helpful Hints" is enabled by the user. + this._readingBlockHintResponse = null; - // @private {string} - The tagName to apply to the Node when voicing is disabled. - this._readingBlockDisabledTagName = 'p'; + // @private {string} - The tagName to apply to the Node when voicing is disabled. + this._readingBlockDisabledTagName = 'p'; - // @private {null|Shape|Node} - The highlight that surrounds this ReadingBlock when it is "active" and - // the Voicing framework is speaking the content associated with this Node. By default, a semi-transparent - // yellow highlight surrounds this Node's bounds. - this._readingBlockActiveHighlight = null; + // @private {null|Shape|Node} - The highlight that surrounds this ReadingBlock when it is "active" and + // the Voicing framework is speaking the content associated with this Node. By default, a semi-transparent + // yellow highlight surrounds this Node's bounds. + this._readingBlockActiveHighlight = null; - // @public (scenery-internal) {TinyEmitter} - Sends a message when the highlight for the ReadingBlock changes. Used - // by the HighlightOverlay to redraw it if it changes while the highlight is active. - this.readingBlockActiveHighlightChangedEmitter = new TinyEmitter(); + // @public (scenery-internal) {TinyEmitter} - Sends a message when the highlight for the ReadingBlock changes. Used + // by the HighlightOverlay to redraw it if it changes while the highlight is active. + this.readingBlockActiveHighlightChangedEmitter = new TinyEmitter(); - // @private {function} - Updates the hit bounds of this Node when the local bounds change. - this.localBoundsChangedListener = this.onLocalBoundsChanged.bind( this ); - this.localBoundsProperty.link( this.localBoundsChangedListener ); + // @private {function} - Updates the hit bounds of this Node when the local bounds change. + this.localBoundsChangedListener = this.onLocalBoundsChanged.bind( this ); + this.localBoundsProperty.link( this.localBoundsChangedListener ); - // @private {Object} - Triggers activation of the ReadingBlock, requesting speech of its content. - this.readingBlockInputListener = { - focus: event => this.speakReadingBlockContent( event ), - up: event => this.speakReadingBlockContent( event ), - click: event => this.speakReadingBlockContent( event ) - }; + // @private {Object} - Triggers activation of the ReadingBlock, requesting speech of its content. + this.readingBlockInputListener = { + focus: event => this.speakReadingBlockContent( event ), + up: event => this.speakReadingBlockContent( event ), + click: event => this.speakReadingBlockContent( event ) + }; - // @private - Controls whether or not the ReadingBlock should be interactiveand focusable. - // At the time of this writing, that is true for all ReadingBlocks when the voicingManager is - // fully enabled and can speak. - this.readingBlockFocusableChangeListener = this.onReadingBlockFocusableChanged.bind( this ); - voicingManager.speechAllowedAndFullyEnabledProperty.link( this.readingBlockFocusableChangeListener ); + // @private - Controls whether or not the ReadingBlock should be interactiveand focusable. + // At the time of this writing, that is true for all ReadingBlocks when the voicingManager is + // fully enabled and can speak. + this.readingBlockFocusableChangeListener = this.onReadingBlockFocusableChanged.bind( this ); + voicingManager.speechAllowedAndFullyEnabledProperty.link( this.readingBlockFocusableChangeListener ); - // All ReadingBlocks have a ReadingBlockHighlight, a focus highlight that is black to indicate it has - // a different behavior. - this.focusHighlight = new ReadingBlockHighlight( this ); - }, + // All ReadingBlocks have a ReadingBlockHighlight, a focus highlight that is black to indicate it has + // a different behavior. + this.focusHighlight = new ReadingBlockHighlight( this ); + } - /** - * Whether or not a Node composes ReadingBlock. - * @returns {boolean} - */ - get isReadingBlock() { - return true; - }, + /** + * Whether or not a Node composes ReadingBlock. + * @returns {boolean} + */ + get isReadingBlock() { + return true; + } - /** - * Set the tagName for the ReadingBlockNode. This is the tagName (of ParallelDOM) that will be applied - * to this Node when Reading Blocks are enabled. - * @public - * - * @param {string|null} tagName - */ - setReadingBlockTagName( tagName ) { - this._readingBlockTagName = tagName; - this.onReadingBlockFocusableChanged( voicingManager.speechAllowedAndFullyEnabledProperty.value ); - }, - set readingBlockTagName( tagName ) { this.setReadingBlockTagName( tagName ); }, + /** + * Set the tagName for the ReadingBlockNode. This is the tagName (of ParallelDOM) that will be applied + * to this Node when Reading Blocks are enabled. + * @public + * + * @param {string|null} tagName + */ + setReadingBlockTagName( tagName ) { + this._readingBlockTagName = tagName; + this.onReadingBlockFocusableChanged( voicingManager.speechAllowedAndFullyEnabledProperty.value ); + } + + set readingBlockTagName( tagName ) { this.setReadingBlockTagName( tagName ); } - /** - * Get the tagName for this Node (of ParallelDOM) when Reading Blocks are enabled. - * @public - * - * @returns {string|null} - */ - getReadingBlockTagName() { - return this._readingBlockTagName; - }, - get readingBlockTagName() { return this.getReadingBlockTagName(); }, + /** + * Get the tagName for this Node (of ParallelDOM) when Reading Blocks are enabled. + * @public + * + * @returns {string|null} + */ + getReadingBlockTagName() { + return this._readingBlockTagName; + } + + get readingBlockTagName() { return this.getReadingBlockTagName(); } - /** - * Sets the content that should be read whenever the ReadingBlock receives input that initiates speech. - * @public - * - * @param {string|null} content - */ - setReadingBlockContent( content ) { - this._readingBlockContent = content; - }, - set readingBlockContent( content ) { this.setReadingBlockContent( content ); }, + /** + * Sets the content that should be read whenever the ReadingBlock receives input that initiates speech. + * @public + * + * @param {string|null} content + */ + setReadingBlockContent( content ) { + this._readingBlockContent = content; + } + + set readingBlockContent( content ) { this.setReadingBlockContent( content ); } - /** - * Gets the content that is spoken whenever the ReadingBLock receives input that would initiate speech. - * @public - * - * @returns {string|null} - */ - getReadingBlockContent() { - return this._readingBlockContent; - }, - get readingBlockContent() { return this.getReadingBlockContent(); }, + /** + * Gets the content that is spoken whenever the ReadingBLock receives input that would initiate speech. + * @public + * + * @returns {string|null} + */ + getReadingBlockContent() { + return this._readingBlockContent; + } + + get readingBlockContent() { return this.getReadingBlockContent(); } - /** - * Sets the hint response for this ReadingBlock. This is only spoken if "Helpful Hints" are enabled by the user. - * @public - * - * @param {string|null} content - */ - setReadingBlockHintResponse( content ) { - this._readingBlockHintResponse = content; - }, - set readingBlockHintResponse( content ) { this.setReadingBlockHintResponse( content ); }, + /** + * Sets the hint response for this ReadingBlock. This is only spoken if "Helpful Hints" are enabled by the user. + * @public + * + * @param {string|null} content + */ + setReadingBlockHintResponse( content ) { + this._readingBlockHintResponse = content; + } + + set readingBlockHintResponse( content ) { this.setReadingBlockHintResponse( content ); } - /** - * Get the hint response for this ReadingBlock. This is additional content that is only read if "Helpful Hints" - * are enabled. - * @public - * - * @returns {string|null} - */ - getReadingBlockHintResponse() { - return this._readingBlockHintResponse; - }, - get readingBlockHintResponse() { return this.getReadingBlockHintResponse(); }, + /** + * Get the hint response for this ReadingBlock. This is additional content that is only read if "Helpful Hints" + * are enabled. + * @public + * + * @returns {string|null} + */ + getReadingBlockHintResponse() { + return this._readingBlockHintResponse; + } + + get readingBlockHintResponse() { return this.getReadingBlockHintResponse(); } - /** - * Sets the highlight used to surround this Node while the Voicing framework is speaking this content. - * Do not add this Node to the scene graph, it is added and made visible by the HighlightOverlay. - * @public - * - * @param readingBlockActiveHighlight - */ - setReadingBlockActiveHighlight( readingBlockActiveHighlight ) { - assert && assert( readingBlockActiveHighlight === null || - readingBlockActiveHighlight instanceof Node || - readingBlockActiveHighlight instanceof Shape ); + /** + * Sets the highlight used to surround this Node while the Voicing framework is speaking this content. + * Do not add this Node to the scene graph, it is added and made visible by the HighlightOverlay. + * @public + * + * @param readingBlockActiveHighlight + */ + setReadingBlockActiveHighlight( readingBlockActiveHighlight ) { + assert && assert( readingBlockActiveHighlight === null || + readingBlockActiveHighlight instanceof Node || + readingBlockActiveHighlight instanceof Shape ); - if ( this._readingBlockActiveHighlight !== readingBlockActiveHighlight ) { - this._readingBlockActiveHighlight = readingBlockActiveHighlight; + if ( this._readingBlockActiveHighlight !== readingBlockActiveHighlight ) { + this._readingBlockActiveHighlight = readingBlockActiveHighlight; - if ( this.readingBlockInitialized ) { - this.readingBlockActiveHighlightChangedEmitter.emit(); - } - } - }, - set readingBlockActiveHighlight( readingBlockActiveHighlight ) { this.setReadingBlockActiveHighlight( readingBlockActiveHighlight ); }, + if ( this.readingBlockInitialized ) { + this.readingBlockActiveHighlightChangedEmitter.emit(); + } + } + } + + set readingBlockActiveHighlight( readingBlockActiveHighlight ) { this.setReadingBlockActiveHighlight( readingBlockActiveHighlight ); } - /** - * Returns the highlight used to surround this Node when the Voicing framework is reading its - * content. - * @returns {null|Shape|Node} - */ - getReadingBlockActiveHighlight() { - return this._readingBlockActiveHighlight; - }, - get readingBlockActiveHighlight() { return this._readingBlockActiveHighlight; }, + /** + * Returns the highlight used to surround this Node when the Voicing framework is reading its + * content. + * @returns {null|Shape|Node} + * @public + */ + getReadingBlockActiveHighlight() { + return this._readingBlockActiveHighlight; + } + + get readingBlockActiveHighlight() { return this._readingBlockActiveHighlight; } - /** - * Returns true if this ReadingBlock is "activated", indicating that it has received interaction - * and the Voicing framework is speaking its content. - * @public - * - * @returns {boolean} - */ - isReadingBlockActivated() { - let activated = false; + /** + * Returns true if this ReadingBlock is "activated", indicating that it has received interaction + * and the Voicing framework is speaking its content. + * @public + * + * @returns {boolean} + */ + isReadingBlockActivated() { + let activated = false; - const trailIds = Object.keys( this._displays ); - for ( let i = 0; i < trailIds.length; i++ ) { - const pointerFocus = this._displays[ trailIds[ i ] ].focusManager.readingBlockFocusProperty.value; - if ( pointerFocus && pointerFocus.trail.lastNode() === this ) { - activated = true; - break; - } - } - return activated; - }, - get readingBlockActivated() { return this.isReadingBlockActivated(); }, + const trailIds = Object.keys( this._displays ); + for ( let i = 0; i < trailIds.length; i++ ) { + const pointerFocus = this._displays[ trailIds[ i ] ].focusManager.readingBlockFocusProperty.value; + if ( pointerFocus && pointerFocus.trail.lastNode() === this ) { + activated = true; + break; + } + } + return activated; + } + + get readingBlockActivated() { return this.isReadingBlockActivated(); } - /** - * When this Node becomes focusable (because Reading Blocks have just been enabled or disabled), either - * apply or remove the readingBlockTagName. - * @private - * - * @param {boolean} focusable - whether or not ReadingBlocks should be focusable - */ - onReadingBlockFocusableChanged( focusable ) { + /** + * When this Node becomes focusable (because Reading Blocks have just been enabled or disabled), either + * apply or remove the readingBlockTagName. + * @private + * + * @param {boolean} focusable - whether or not ReadingBlocks should be focusable + */ + onReadingBlockFocusableChanged( focusable ) { - // wait until we have been initialized, it is possible to call setters from mutate before properties of - // ReadingBlock are defined - if ( !this.readingBlockInitialized ) { - return; - } + // wait until we have been initialized, it is possible to call setters from mutate before properties of + // ReadingBlock are defined + if ( !this.readingBlockInitialized ) { + return; + } - this.focusable = focusable; + this.focusable = focusable; - if ( focusable ) { - this.tagName = this._readingBlockTagName; + if ( focusable ) { + this.tagName = this._readingBlockTagName; - // don't add the input listener if we are already active, we may just be updating the tagName in this case - if ( !this.hasInputListener( this.readingBlockInputListener ) ) { - this.addInputListener( this.readingBlockInputListener ); - } - } - else { - this.tagName = this._readingBlockDisabledTagName; - if ( this.hasInputListener( this.readingBlockInputListener ) ) { - this.removeInputListener( this.readingBlockInputListener ); - } - } - }, + // don't add the input listener if we are already active, we may just be updating the tagName in this case + if ( !this.hasInputListener( this.readingBlockInputListener ) ) { + this.addInputListener( this.readingBlockInputListener ); + } + } + else { + this.tagName = this._readingBlockDisabledTagName; + if ( this.hasInputListener( this.readingBlockInputListener ) ) { + this.removeInputListener( this.readingBlockInputListener ); + } + } + } - /** - * Update the hit areas for this Node whenever the bounds change. - * @private - * - * @param localBounds - */ - onLocalBoundsChanged( localBounds ) { - this.mouseArea = localBounds; - this.touchArea = localBounds; - }, + /** + * Update the hit areas for this Node whenever the bounds change. + * @private + * + * @param localBounds + */ + onLocalBoundsChanged( localBounds ) { + this.mouseArea = localBounds; + this.touchArea = localBounds; + } - /** - * Speak the content associated with the ReadingBlock. Sets the readingBlockFocusProperties on - * the displays so that HighlightOverlays know to activate a highlight while the voicingManager - * is reading about this Node. - * @private - * - * @param {SceneryEvent} event - */ - speakReadingBlockContent( event ) { - assert && assert( this.readingBlockInitialized, 'ReadingBlock must be initialized before speaking' ); + /** + * Speak the content associated with the ReadingBlock. Sets the readingBlockFocusProperties on + * the displays so that HighlightOverlays know to activate a highlight while the voicingManager + * is reading about this Node. + * @private + * + * @param {SceneryEvent} event + */ + speakReadingBlockContent( event ) { + assert && assert( this.readingBlockInitialized, 'ReadingBlock must be initialized before speaking' ); - const displays = this.getConnectedDisplays(); + const displays = this.getConnectedDisplays(); - const content = this.collectReadingBlockResponses(); - if ( content ) { - for ( let i = 0; i < displays.length; i++ ) { - if ( !this.getDescendantsUseHighlighting( event.trail ) ) { + const content = this.collectReadingBlockResponses(); + if ( content ) { + for ( let i = 0; i < displays.length; i++ ) { + if ( !this.getDescendantsUseHighlighting( event.trail ) ) { - // the SceneryEvent might have gone through a descendant of this Node - const rootToSelf = event.trail.subtrailTo( this ); + // the SceneryEvent might have gone through a descendant of this Node + const rootToSelf = event.trail.subtrailTo( this ); - // the trail to a Node may be discontinuous for PDOM events due to pdomOrder, - // this finds the actual visual trail to use - const visualTrail = scenery.PDOMInstance.guessVisualTrail( rootToSelf, displays[ i ].rootNode ); + // the trail to a Node may be discontinuous for PDOM events due to pdomOrder, + // this finds the actual visual trail to use + const visualTrail = scenery.PDOMInstance.guessVisualTrail( rootToSelf, displays[ i ].rootNode ); - const focus = new Focus( displays[ i ], visualTrail ); - const readingBlockUtterance = new ReadingBlockUtterance( focus, { - alert: content - } ); - this.speakContent( readingBlockUtterance ); - } - } - } - }, + const focus = new Focus( displays[ i ], visualTrail ); + const readingBlockUtterance = new ReadingBlockUtterance( focus, { + alert: content + } ); + this.speakContent( readingBlockUtterance ); + } + } + } + } - /** - * Collect responses for the ReadingBlock, putting together the content and the hint response. The hint response - * is only read if it exists and hints are enabled by the user. Otherwise, only the readingBlock content will - * be spoken. - * @returns {string} - */ - collectReadingBlockResponses() { - assert && assert( this.readingBlockInitialized, 'ReadingBlock must be initialized before collecting responses' ); + /** + * Collect responses for the ReadingBlock, putting together the content and the hint response. The hint response + * is only read if it exists and hints are enabled by the user. Otherwise, only the readingBlock content will + * be spoken. + * @returns {string} + * @public + */ + collectReadingBlockResponses() { + assert && assert( this.readingBlockInitialized, 'ReadingBlock must be initialized before collecting responses' ); - const usesHelpContent = this._readingBlockHintResponse && responseCollector.hintResponsesEnabledProperty.value; + const usesHelpContent = this._readingBlockHintResponse && responseCollector.hintResponsesEnabledProperty.value; - let response = null; - if ( usesHelpContent ) { - response = StringUtils.fillIn( CONTENT_HINT_PATTERN, { - readingBlockContent: this._readingBlockContent, - hintResponse: this._readingBlockHintResponse - } ); - } - else { - response = this._readingBlockContent; - } + let response = null; + if ( usesHelpContent ) { + response = StringUtils.fillIn( CONTENT_HINT_PATTERN, { + readingBlockContent: this._readingBlockContent, + hintResponse: this._readingBlockHintResponse + } ); + } + else { + response = this._readingBlockContent; + } - return response; - }, + return response; + } - /** - * @public - */ - disposeReadingBlock() { - this.readingBlockInitialized = false; - voicingManager.speechAllowedAndFullyEnabledProperty.unlink( this.readingBlockFocusableChangeListener ); - this.localBoundsProperty.unlink( this.localBoundsChangedListener ); + /** + * @public + */ + disposeReadingBlock() { + this.readingBlockInitialized = false; + voicingManager.speechAllowedAndFullyEnabledProperty.unlink( this.readingBlockFocusableChangeListener ); + this.localBoundsProperty.unlink( this.localBoundsChangedListener ); - // remove the input listener that activates the ReadingBlock, only do this if the listener is attached while - // the ReadingBlock is enabled - if ( this.hasInputListener( this.readingBlockInputListener ) ) { - this.removeInputListener( this.readingBlockInputListener ); - } + // remove the input listener that activates the ReadingBlock, only do this if the listener is attached while + // the ReadingBlock is enabled + if ( this.hasInputListener( this.readingBlockInputListener ) ) { + this.removeInputListener( this.readingBlockInputListener ); + } - this.disposeVoicing(); - } - } ); - } + this.disposeVoicing(); + } + }; + + + X.prototype._mutatorKeys = _.uniq( ( X.prototype._mutatorKeys ? X.prototype._mutatorKeys : [] ).concat( READING_BLOCK_OPTION_KEYS ) ); + return X; }; + scenery.register( 'ReadingBlock', ReadingBlock ); export default ReadingBlock; Index: scenery/js/accessibility/voicing/ReadingBlockNode.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/scenery/js/accessibility/voicing/ReadingBlockNode.js b/scenery/js/accessibility/voicing/ReadingBlockNode.js --- a/scenery/js/accessibility/voicing/ReadingBlockNode.js (revision faa4e9d6dbd874a6d3bd9a0b6dd4acabb54651b9) +++ b/scenery/js/accessibility/voicing/ReadingBlockNode.js (date 1643062484395) @@ -17,7 +17,7 @@ import merge from '../../../../phet-core/js/merge.js'; import { scenery, Node, ReadingBlock, ReadingBlockHighlight } from '../../imports.js'; -class ReadingBlockNode extends Node { +class ReadingBlockNode extends ReadingBlock( Node ) { /** * @param {Object} [options] @@ -31,7 +31,6 @@ }, options ); super(); - this.initializeReadingBlock(); // default highlight for a ReadingBlock is styled to indicate that the Node is different // from other interactive things, but is still clickable @@ -42,7 +41,5 @@ } } -ReadingBlock.compose( ReadingBlockNode ); - scenery.register( 'ReadingBlockNode', ReadingBlockNode ); export default ReadingBlockNode; \ No newline at end of file Index: scenery/js/accessibility/voicing/nodes/VoicingRichText.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/scenery/js/accessibility/voicing/nodes/VoicingRichText.js b/scenery/js/accessibility/voicing/nodes/VoicingRichText.js --- a/scenery/js/accessibility/voicing/nodes/VoicingRichText.js (revision faa4e9d6dbd874a6d3bd9a0b6dd4acabb54651b9) +++ b/scenery/js/accessibility/voicing/nodes/VoicingRichText.js (date 1643062484389) @@ -10,7 +10,7 @@ import merge from '../../../../../phet-core/js/merge.js'; import { ReadingBlock, ReadingBlockHighlight, RichText, scenery } from '../../../imports.js'; -class VoicingRichText extends RichText { +class VoicingRichText extends ReadingBlock( RichText ) { /** * @param {string} text @@ -41,8 +41,6 @@ this.focusHighlight = new ReadingBlockHighlight( this ); - this.initializeReadingBlock(); - this.mutate( options ); } @@ -55,7 +53,5 @@ } } -ReadingBlock.compose( VoicingRichText ); - scenery.register( 'VoicingRichText', VoicingRichText ); export default VoicingRichText; Index: scenery/js/accessibility/voicing/nodes/VoicingText.js IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/scenery/js/accessibility/voicing/nodes/VoicingText.js b/scenery/js/accessibility/voicing/nodes/VoicingText.js --- a/scenery/js/accessibility/voicing/nodes/VoicingText.js (revision faa4e9d6dbd874a6d3bd9a0b6dd4acabb54651b9) +++ b/scenery/js/accessibility/voicing/nodes/VoicingText.js (date 1643062648054) @@ -10,7 +10,7 @@ import merge from '../../../../../phet-core/js/merge.js'; import { ReadingBlock, ReadingBlockHighlight, scenery, Text } from '../../../imports.js'; -class VoicingText extends Text { +class VoicingText extends ReadingBlock( Text ) { /** * @param {string} text @@ -38,8 +38,6 @@ // unique highlight for non-interactive components this.focusHighlight = new ReadingBlockHighlight( this ); - // voicing - this.initializeReadingBlock(); this.mutate( options ); } @@ -52,7 +50,5 @@ } } -ReadingBlock.compose( VoicingText ); - scenery.register( 'VoicingText', VoicingText ); export default VoicingText; Index: scenery/doc/accessibility/voicing.html IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/scenery/doc/accessibility/voicing.html b/scenery/doc/accessibility/voicing.html --- a/scenery/doc/accessibility/voicing.html (revision faa4e9d6dbd874a6d3bd9a0b6dd4acabb54651b9) +++ b/scenery/doc/accessibility/voicing.html (date 1643066948363) @@ -149,7 +149,7 @@ - + @@ -213,10 +213,10 @@ -

Responses implemented with Voicing.js

-

Voicing is implemented with a trait called Voicing.js which can be composed with scenery's +

Responses implemented with Voicing.ts

+

Voicing is implemented with a trait called Voicing.ts which can be composed with scenery's Node. It provides the ability to set the various responses on the Node and then make a request to - speak one or more of them. The API of Voicing.js is described in more detail later in this document.

+ speak one or more of them. The API of Voicing.ts is described in more detail later in this document.

Responses collected with responseCollector.js

@@ -227,8 +227,8 @@ and contains utility functions for assembling final Voicing content depending on the state of these Properties.

-

Voicing.js API

-

The following enumerates the Voicing.js API.

+

Voicing.ts API

+

The following enumerates the Voicing.ts API.

voicingNameResponse

A getter/setter for the {string|null} name response for the Node.

@@ -302,7 +302,7 @@

voicingResponsePatternCollection

Sets the collection of patterns to use for voicingManager.collectResponses. This lets you control the - order of Voicing.js responses, as well as customize punctuation and other formatting of the content. + order of Voicing.ts responses, as well as customize punctuation and other formatting of the content. See ResponsePatternCollection.js for more information and how to create your own collection of patterns.

@@ -312,7 +312,7 @@

Code examples

Simple Example

-

The following illustrates a basic example of using Voicing.js with a Node. Click the Rectangle +

The following illustrates a basic example of using Voicing.ts with a Node. Click the Rectangle to hear speech.