Skip to content

Commit

Permalink
Add behavior functions for voicing that apply accessibleName and help…
Browse files Browse the repository at this point in the history
…Text to voicing responses by default, see #1672
  • Loading branch information
jessegreenberg committed Jan 7, 2025
1 parent 2712094 commit 8ec1799
Showing 1 changed file with 94 additions and 1 deletion.
95 changes: 94 additions & 1 deletion js/accessibility/voicing/Voicing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import responseCollector from '../../../../utterance-queue/js/responseCollector.
import ResponsePacket, { ResolvedResponse, SpeakableResolvedOptions, VoicingResponse } from '../../../../utterance-queue/js/ResponsePacket.js';
import ResponsePatternCollection from '../../../../utterance-queue/js/ResponsePatternCollection.js';
import Utterance, { TAlertable, UtteranceOptions } from '../../../../utterance-queue/js/Utterance.js';
import { DelayedMutate, Instance, InteractiveHighlighting, InteractiveHighlightingOptions, Node, scenery, SceneryListenerFunction, voicingUtteranceQueue } from '../../imports.js';
import { DelayedMutate, Instance, InteractiveHighlighting, InteractiveHighlightingOptions, Node, ParallelDOM, ParallelDOMOptions, PDOMValueType, scenery, SceneryListenerFunction, voicingUtteranceQueue } from '../../imports.js';
import type { TInteractiveHighlighting } from './InteractiveHighlighting.js';

// Helps enforce that the utterance is defined.
Expand Down Expand Up @@ -110,6 +110,10 @@ export interface TVoicing<SuperType extends Node = Node> extends TInteractiveHig
// @mixin-private - private to this file, but public needed for the interface
_voicingCanSpeakProperty: TinyProperty<boolean>;

// @mixin-private - private to this file, but public needed for the interface
_voicingNameResponseOverride: boolean;
_voicingHintResponseOverride: boolean;

initialize( ...args: IntentionalAny[] ): this;

voicingSpeakFullResponse( providedOptions?: SpeakingOptions ): void;
Expand All @@ -135,6 +139,8 @@ export interface TVoicing<SuperType extends Node = Node> extends TInteractiveHig

getVoicingNameResponse(): ResolvedResponse;

applyDefaultNameResponse( accessibleName: VoicingResponse ): void;

setVoicingObjectResponse( response: VoicingResponse ): void;

voicingObjectResponse: ResolvedResponse;
Expand All @@ -153,6 +159,8 @@ export interface TVoicing<SuperType extends Node = Node> extends TInteractiveHig

getVoicingHintResponse(): ResolvedResponse;

applyDefaultHintResponse( helpText: VoicingResponse ): void;

setVoicingIgnoreVoicingManagerProperties( ignoreProperties: boolean ): void;

voicingIgnoreVoicingManagerProperties: boolean;
Expand Down Expand Up @@ -219,6 +227,12 @@ const Voicing = <SuperType extends Constructor<Node>>( Type: SuperType ): SuperT
// and onInstanceVoicingVisibilityChange for more implementation details.
private _voicingCanSpeakCount!: number;

// Flags that indicate that voicing name or hint response have been set manually. If false,
// a default accessible name or help text from ParallelDOM may be used by applyDefaultNameResponse or
// applyDefaultHintResponse. See those functions for more information.
public _voicingNameResponseOverride = false;
public _voicingHintResponseOverride = false;

// Called when `canVoiceEmitter` emits for an Instance.
private readonly _boundInstanceCanVoiceChangeListener: ( canSpeak: boolean ) => void;

Expand Down Expand Up @@ -446,6 +460,7 @@ const Voicing = <SuperType extends Constructor<Node>>( Type: SuperType ): SuperT
* responseCollector.nameResponsesEnabledProperty is set to true.
*/
public setVoicingNameResponse( response: VoicingResponse ): void {
this._voicingNameResponseOverride = true;
this._voicingResponsePacket.nameResponse = response;
}

Expand All @@ -460,6 +475,23 @@ const Voicing = <SuperType extends Constructor<Node>>( Type: SuperType ): SuperT
return this._voicingResponsePacket.nameResponse;
}

/**
* Sets the name response for this VoicingNode to match the provided accessibleName. If setAccessibleName
* has been used once, this is a no-op.
*
* This should rarely be used. It is intended for use cases where the name response should match the
* ParallelDOM.accessibleName to align the APIs. See Voicing.BASIC_ACCESSIBLE_NAME_BEHAVIOR. It is probably most
* useful in your own behavior function.
*/
public applyDefaultNameResponse( accessibleName: VoicingResponse ): void {

// If the voicingNameResponse has not been set manually, it will take the same value
// as the accessibleName.
if ( !this._voicingNameResponseOverride ) {
this._voicingResponsePacket.nameResponse = accessibleName;
}
}

/**
* 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
Expand Down Expand Up @@ -506,6 +538,7 @@ const Voicing = <SuperType extends Constructor<Node>>( Type: SuperType ): SuperT
* true.
*/
public setVoicingHintResponse( response: VoicingResponse ): void {
this._voicingHintResponseOverride = true;
this._voicingResponsePacket.hintResponse = response;
}

Expand All @@ -520,6 +553,23 @@ const Voicing = <SuperType extends Constructor<Node>>( Type: SuperType ): SuperT
return this._voicingResponsePacket.hintResponse;
}

/**
* Sets the hint response for this VoicingNode to match the provided help text. If setVoicingHintResponse
* has been used once, this is a no-op.
*
* This should rarely be used. It is intended for use cases where the hint response should match the
* ParallelDOM.helpText to align the APIs. See Voicing.BASIC_HELP_TEXT_BEHAVIOR. It is probably most useful in
* your own behavior function.
*/
public applyDefaultHintResponse( helpText: VoicingResponse ): void {

// If the voicingHintResponse has not been set manually, it will take the same value
// as the helpText.
if ( !this._voicingHintResponseOverride ) {
this._voicingResponsePacket.hintResponse = helpText;
}
}

/**
* 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
Expand Down Expand Up @@ -822,6 +872,49 @@ Voicing.unregisterUtteranceToNode = ( utterance: Utterance, node: Node ) => {
utterance.voicingCanAnnounceProperties = withoutBothProperties;
};

/**
* A basic behavior function for Voicing that sets the both the ParallelDOM.accessibleName and Voicing.voicingNameResponse.
* By using a behavior function, we can ensure that the accessibleName and voicingNameResponse are always in sync when you
* mutate the accessibleName.
*
* For example:
* myNode.accessibleNameBehavior = Voicing.BASIC_ACCESSIBLE_NAME_BEHAVIOR;
* myNode.accessibleName = 'My Node'; // This will also set myNode.voicingNameResponse to 'My Node'
*/
Voicing.BASIC_ACCESSIBLE_NAME_BEHAVIOR = ( node: Node, options: ParallelDOMOptions, accessibleName: PDOMValueType ): ParallelDOMOptions => {
assert && assert( isVoicing( node ), 'Node must be a VoicingNode to use Voicing.BASIC_ACCESSIBLE_NAME_BEHAVIOR' );
const voicingNode = node as VoicingNode;

// Create the basic accessible name options - the behavior function will use these options to mutate the Node.
options = ParallelDOM.BASIC_ACCESSIBLE_NAME_BEHAVIOR( voicingNode, options, accessibleName );
voicingNode.applyDefaultNameResponse( accessibleName );

return options;
};

/**
* A basic behavior function for Voicing that sets the both the ParallelDOM.helpText and Voicing.voicingHintResponse.
* By using a behavior function, we can ensure that the helpText and voicingHintResponse are always in sync when you
* mutate the helpText.
*
* For example:
* myNode.helpTextBehavior = Voicing.BASIC_HELP_TEXT_BEHAVIOR;
* myNode.helpText = 'This is a helpful hint'; // This will also set myNode.voicingHintResponse to 'This is a helpful hint'
*/
Voicing.BASIC_HELP_TEXT_BEHAVIOR = ( node: Node, options: ParallelDOMOptions, helpText: PDOMValueType ): ParallelDOMOptions => {
assert && assert( isVoicing( node ), 'Node must be a VoicingNode to use Voicing.BASIC_HELP_TEXT_BEHAVIOR' );
const voicingNode = node as VoicingNode;

// Create the basic help text options - the behavior function will use these options to mutate the Node.
options = ParallelDOM.HELP_TEXT_AFTER_CONTENT( voicingNode, options, helpText );

// If the voicingNameResponse has not been set manually, it will take the same value
// as the accessibleName.
voicingNode.applyDefaultHintResponse( helpText );

return options;
};

export type VoicingNode = Node & TVoicing;

export function isVoicing( something: IntentionalAny ): something is VoicingNode {
Expand Down

0 comments on commit 8ec1799

Please sign in to comment.