Skip to content

Commit 8b23efd

Browse files
authored
Adding prop accessibilityUnit to TextAttributesProps (fabric) (#3)
## Summary Adds the accessibilityUnit to fabric CPP API (part of PR facebook#35130 Adding support for Android Accessibility TtsSpan API) The TtsSpan API now supports verbatim, date, and fraction but may not work with other span types like phone numbers, currency, time, measure, and money. An explanation of the use case is as follows: - Time is spelled 1 0 3 0 instead of ten thirty - The developer does not have the option to specify if 10m stands for 10 meters or 10 miles, or 10 minutes - Phone numbers, for example, 33312344, are not yet supported Implementing the above functionality requires adding a new prop to the react-native API responsible for managing attributes of nested Text. - Java and CPP TextAttributes manage the functionality. - Nested Text does not correspond to a Widget on Android but an Android TextView Span. The spans are updated based on a caching mechanism that re-renders the Android TextView only if specific attributes are updated.
The updates are triggered only if the Text or Paragraph Text attributes change. - An example of Text attributes is accessibilityRole, font-weight, and backgroundColor. - An example of Paragraph attributes is textBreakstrategy. They are updated with different mechanisms because they may change the paragraph layout. These mappings are configured in Java, CPP, and Javascript. As this and PR [facebook#33468](facebook#33468) require changes to the text attributes, the below work was needed: - issue documented in PR [facebook#33468 (comment)](facebook#33468 (comment)) and PR [facebook#35130 (comment)](facebook#35130 (comment)). The CPP changes for facebook#35130 are moved to a separate PR ([fabriziobertoglio1987#2](#2)). The RNTester App would crash without a clear stack trace on startup after making changes to CPP. This was later fixed by adding the CPP headers to the prefab library rrc_view (see this [comment](facebook#35130 (comment))). - Adding the CPP configurations (see [fabriziobertoglio1987#2](https://github.com/fabriziobertoglio1987/react-- native/pull/2)) required extensive troubleshooting and analysis Related: PR #4 - separating accessibilityUnit from accessibilityMinutes and accessibilityHours
1 parent 3e1d292 commit 8b23efd

19 files changed

+61
-99
lines changed

Libraries/Components/View/ViewAccessibility.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -158,10 +158,6 @@ export type AccessibilityState = {
158158
...
159159
};
160160

161-
export type AccessibilityUnit = {
162-
hours?: number,
163-
};
164-
165161
export type AccessibilityValue = $ReadOnly<{|
166162
/**
167163
* The minimum value of this component's range. (should be an integer)

Libraries/Components/View/ViewPropTypes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -502,7 +502,7 @@ export type ViewProps = $ReadOnly<{|
502502
* Indicates to accessibility services that UI Component is in a specific State.
503503
*/
504504
accessibilityState?: ?AccessibilityState,
505-
accessibilityUnit?: ?AccessibilityUnit,
505+
accessibilityUnit?: ?Stringish,
506506
accessibilityValue?: ?AccessibilityValue,
507507

508508
/**

ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -318,12 +318,6 @@ public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilitySta
318318
}
319319
}
320320

321-
@Override
322-
@ReactProp(name = ViewProps.ACCESSIBILITY_UNIT)
323-
public void setAccessibilityUnit(@NonNull T view, @Nullable ReadableMap accessibilityUnit) {
324-
// do nothing
325-
}
326-
327321
private void updateViewContentDescription(@NonNull T view) {
328322
final String accessibilityLabel = (String) view.getTag(R.id.accessibility_label);
329323
final ReadableMap accessibilityState = (ReadableMap) view.getTag(R.id.accessibility_state);

ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerAdapter.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,6 @@ public void setAccessibilityCollectionItem(
4242
@Override
4343
public void setViewState(@NonNull T view, @Nullable ReadableMap accessibilityState) {}
4444

45-
@Override
46-
public void setAccessibilityUnit(@NonNull T view, @Nullable ReadableMap accessibilityUnit) {}
47-
4845
@Override
4946
public void setBackgroundColor(@NonNull T view, int backgroundColor) {}
5047

ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManagerInterface.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ public interface BaseViewManagerInterface<T extends View> {
3434

3535
void setViewState(T view, @Nullable ReadableMap accessibilityState);
3636

37-
void setAccessibilityUnit(T view, @Nullable ReadableMap accessibilityUnit);
38-
3937
void setBackgroundColor(T view, int backgroundColor);
4038

4139
void setBorderRadius(T view, float borderRadius);

ReactAndroid/src/main/java/com/facebook/react/views/text/ReactBaseTextShadowNode.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,6 @@ private static void buildSpannedFromShadowNode(
184184
ops.add(
185185
new SetSpanOperation(start, end, new ReactClickableSpan(textShadowNode.getReactTag())));
186186
}
187-
if (textShadowNode.mAccessibilityUnit != null && Build.VERSION.SDK_INT >= 21) {
188-
/*
189-
ops.add(
190-
new SetSpanOperation(
191-
start, end, new ReactTtsSpan.Builder(textShadowNode.mAccessibilityUnit).build()));
192-
*/
193-
}
194187
float effectiveLetterSpacing = textAttributes.getEffectiveLetterSpacing();
195188
if (!Float.isNaN(effectiveLetterSpacing)
196189
&& (parentTextAttributes == null
@@ -517,13 +510,6 @@ public void setIsAccessibilityLink(@Nullable String accessibilityRole) {
517510
}
518511
}
519512

520-
@ReactProp(name = "accessibilityUnit")
521-
public void setAccessibilityUnit(@Nullable String accessibilityUnit) {
522-
if (isVirtual()) {
523-
markUpdated();
524-
}
525-
}
526-
527513
@ReactProp(name = ViewProps.FONT_FAMILY)
528514
public void setFontFamily(@Nullable String fontFamily) {
529515
mFontFamily = fontFamily;

ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextAnchorViewManager.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,11 @@
1212
import android.text.Spannable;
1313
import android.text.TextUtils;
1414
import android.text.util.Linkify;
15-
import android.util.Log;
1615
import android.view.Gravity;
1716
import android.view.View;
18-
import androidx.annotation.NonNull;
1917
import androidx.annotation.Nullable;
2018
import com.facebook.common.logging.FLog;
2119
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
22-
import com.facebook.react.bridge.ReadableMap;
2320
import com.facebook.react.uimanager.BaseViewManager;
2421
import com.facebook.react.uimanager.PixelUtil;
2522
import com.facebook.react.uimanager.Spacing;
@@ -51,13 +48,6 @@ public void setAccessible(ReactTextView view, boolean accessible) {
5148
view.setFocusable(accessible);
5249
}
5350

54-
@Override
55-
@ReactProp(name = ViewProps.ACCESSIBILITY_UNIT)
56-
public void setAccessibilityUnit(@NonNull T view, @Nullable ReadableMap accessibilityUnit) {
57-
Log.w("TESTING::ReactTextAnchorViewManager", "setAccessibilityUnit");
58-
Log.w("TESTING::ReactTextAnchorViewManager", "accessibilityUnit: " + (accessibilityUnit));
59-
}
60-
6151
// maxLines can only be set in master view (block), doesn't really make sense to set in a span
6252
@ReactProp(name = ViewProps.NUMBER_OF_LINES, defaultInt = ViewDefaults.NUMBER_OF_LINES)
6353
public void setNumberOfLines(ReactTextView view, int numberOfLines) {

ReactAndroid/src/main/java/com/facebook/react/views/text/TextAttributeProps.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public class TextAttributeProps {
5353
public static final short TA_KEY_IS_HIGHLIGHTED = 20;
5454
public static final short TA_KEY_LAYOUT_DIRECTION = 21;
5555
public static final short TA_KEY_ACCESSIBILITY_ROLE = 22;
56+
public static final short TA_KEY_ACCESSIBILITY_UNIT = 47;
5657

5758
public static final int UNSET = -1;
5859

@@ -104,6 +105,7 @@ public class TextAttributeProps {
104105
protected boolean mIsAccessibilityRoleSet = false;
105106
protected @Nullable String mAccessibilityUnit = null;
106107
protected boolean mIsAccessibilityLink = false;
108+
protected boolean mIsAccessibilityTtsSpan = false;
107109

108110
protected int mFontStyle = UNSET;
109111
protected int mFontWeight = UNSET;
@@ -206,6 +208,9 @@ public static TextAttributeProps fromMapBuffer(MapBuffer props) {
206208
case TA_KEY_ACCESSIBILITY_ROLE:
207209
result.setAccessibilityRole(entry.getStringValue());
208210
break;
211+
case TA_KEY_ACCESSIBILITY_UNIT:
212+
result.setAccessibilityUnit(entry.getStringValue());
213+
break;
209214
}
210215
}
211216

@@ -607,13 +612,15 @@ private void setAccessibilityRole(@Nullable String accessibilityRole) {
607612
mIsAccessibilityLink = mAccessibilityRole.equals(AccessibilityRole.LINK);
608613
String roleClassName =
609614
AccessibilityRole.getValue(AccessibilityRole.fromValue(accessibilityRole));
610-
mAccessibilityUnit =
611-
ReactTtsSpan.SUPPORTED_UNIT_TYPES.contains(roleClassName) ? roleClassName : null;
615+
mIsAccessibilityTtsSpan =
616+
ReactTtsSpan.SUPPORTED_UNIT_TYPES.contains(roleClassName) && Build.VERSION.SDK_INT >= 21;
612617
}
613618
}
614619

615620
private void setAccessibilityUnit(@Nullable String accessibilityUnit) {
616-
// not yet implemented
621+
if (accessibilityUnit != null) {
622+
mAccessibilityUnit = accessibilityUnit;
623+
}
617624
}
618625

619626
public static int getTextBreakStrategy(@Nullable String textBreakStrategy) {

ReactAndroid/src/main/java/com/facebook/react/views/text/TextLayoutManagerMapBuffer.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,10 +143,22 @@ private static void buildSpannableFromFragment(
143143
if (textAttributes.mIsAccessibilityLink) {
144144
ops.add(new SetSpanOperation(start, end, new ReactClickableSpan(reactTag)));
145145
}
146-
if (textAttributes.mAccessibilityUnit != null && Build.VERSION.SDK_INT >= 21) {
146+
if (textAttributes.mIsAccessibilityTtsSpan) {
147+
// check that textAttributes.mAccessibilityRole == "time"
147148
ReactTtsSpan.Builder builder = new ReactTtsSpan.Builder(ReactTtsSpan.TYPE_TIME);
148-
builder.setIntArgument(ReactTtsSpan.ARG_HOURS, 10);
149-
builder.setIntArgument(ReactTtsSpan.ARG_MINUTES, 30);
149+
if (textAttributes.mAccessibilityUnit != null) {
150+
String[] time = textAttributes.mAccessibilityUnit.split(":");
151+
if (time[0] != null && time[1] != null) {
152+
try {
153+
Integer hours = Integer.parseInt(time[0]);
154+
Integer minutes = Integer.parseInt(time[1]);
155+
builder.setIntArgument(ReactTtsSpan.ARG_HOURS, hours);
156+
builder.setIntArgument(ReactTtsSpan.ARG_MINUTES, minutes);
157+
} catch (Exception e) {
158+
FLog.w(TAG, "TtsSpan with type Time failed to set hours and minutes. Error: " + e);
159+
}
160+
}
161+
}
150162
ops.add(new SetSpanOperation(start, end, builder.build()));
151163
}
152164
if (textAttributes.mIsColorSet) {

ReactAndroid/src/main/java/com/facebook/react/views/view/ReactMapBufferPropSetter.kt

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
*/
77

88
package com.facebook.react.views.view
9-
import android.util.Log;
9+
1010
import android.graphics.Color
1111
import android.graphics.Rect
1212
import androidx.core.view.ViewCompat
@@ -64,7 +64,6 @@ object ReactMapBufferPropSetter {
6464
private const val VP_POINTER_OVER_CAPTURE = 44
6565
private const val VP_BORDER_CURVES = 45 // iOS only
6666
private const val VP_FG_COLOR = 46 // iOS only?
67-
private const val VP_ACCESSIBILITY_UNIT = 47
6867

6968
// Yoga values
7069
private const val YG_BORDER_WIDTH = 100
@@ -108,7 +107,6 @@ object ReactMapBufferPropSetter {
108107
private const val UNDEF_COLOR = Int.MAX_VALUE
109108

110109
fun setProps(view: ReactViewGroup, viewManager: ReactViewManager, props: MapBuffer) {
111-
Log.w("TESTING::ReactMapBufferPropSetter", "props: " + ( props ));
112110
for (entry in props) {
113111
when (entry.key) {
114112
VP_ACCESSIBILITY_ACTIONS -> {
@@ -132,9 +130,6 @@ object ReactMapBufferPropSetter {
132130
VP_ACCESSIBILITY_STATE -> {
133131
viewManager.accessibilityState(view, entry.mapBufferValue)
134132
}
135-
VP_ACCESSIBILITY_UNIT -> {
136-
// viewManager.accessibilityUnit(view, entry.mapBufferValue)
137-
}
138133
VP_ACCESSIBILITY_VALUE -> {
139134
viewManager.accessibilityValue(view, entry.stringValue)
140135
}
@@ -283,14 +278,6 @@ object ReactMapBufferPropSetter {
283278
ViewCompat.setAccessibilityLiveRegion(this, mode)
284279
}
285280

286-
private fun ReactViewManager.accessibilityUnit(view: ReactViewGroup, value: MapBuffer) {
287-
Log.w("TESTING::ReactMapBufferPropSetter", "accessibilityUnit");
288-
val accessibilityUnit = JavaOnlyMap()
289-
accessibilityUnit.putString("hours", "10")
290-
291-
setAccessibilityUnit(view, accessibilityUnit)
292-
}
293-
294281
private fun ReactViewManager.accessibilityState(view: ReactViewGroup, value: MapBuffer) {
295282
val accessibilityState = JavaOnlyMap()
296283
accessibilityState.putBoolean("selected", value.getBoolean(ACCESSIBILITY_STATE_SELECTED))

ReactCommon/react/renderer/attributedstring/TextAttributes.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ void TextAttributes::apply(TextAttributes textAttributes) {
9898
accessibilityRole = textAttributes.accessibilityRole.has_value()
9999
? textAttributes.accessibilityRole
100100
: accessibilityRole;
101+
accessibilityUnit = !textAttributes.accessibilityUnit.empty() ? textAttributes.accessibilityUnit
102+
: accessibilityUnit;
101103
}
102104

103105
#pragma mark - Operators
@@ -122,6 +124,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
122124
isHighlighted,
123125
layoutDirection,
124126
accessibilityRole,
127+
accessibilityUnit,
125128
textTransform) ==
126129
std::tie(
127130
rhs.foregroundColor,
@@ -142,6 +145,7 @@ bool TextAttributes::operator==(const TextAttributes &rhs) const {
142145
rhs.isHighlighted,
143146
rhs.layoutDirection,
144147
rhs.accessibilityRole,
148+
rhs.accessibilityUnit,
145149
rhs.textTransform) &&
146150
floatEquality(opacity, rhs.opacity) &&
147151
floatEquality(fontSize, rhs.fontSize) &&
@@ -209,6 +213,7 @@ SharedDebugStringConvertibleList TextAttributes::getDebugProps() const {
209213
debugStringConvertibleItem("isHighlighted", isHighlighted),
210214
debugStringConvertibleItem("layoutDirection", layoutDirection),
211215
debugStringConvertibleItem("accessibilityRole", accessibilityRole),
216+
debugStringConvertibleItem("accessibilityUnit", accessibilityUnit),
212217
};
213218
}
214219
#endif

ReactCommon/react/renderer/attributedstring/TextAttributes.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ class TextAttributes : public DebugStringConvertible {
7979
// construction.
8080
std::optional<LayoutDirection> layoutDirection{};
8181
std::optional<AccessibilityRole> accessibilityRole{};
82+
std::string accessibilityUnit{};
8283

8384
#pragma mark - Operations
8485

@@ -131,7 +132,8 @@ struct hash<facebook::react::TextAttributes> {
131132
textAttributes.textShadowColor,
132133
textAttributes.isHighlighted,
133134
textAttributes.layoutDirection,
134-
textAttributes.accessibilityRole);
135+
textAttributes.accessibilityRole,
136+
textAttributes.accessibilityUnit);
135137
}
136138
};
137139
} // namespace std

ReactCommon/react/renderer/attributedstring/conversions.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,10 @@ inline folly::dynamic toDynamic(const TextAttributes &textAttributes) {
10091009
_textAttributes(
10101010
"accessibilityRole", toString(*textAttributes.accessibilityRole));
10111011
}
1012+
if (!textAttributes.accessibilityUnit.empty()) {
1013+
_textAttributes(
1014+
"accessibilityUnit", textAttributes.accessibilityUnit);
1015+
}
10121016
return _textAttributes;
10131017
}
10141018

@@ -1083,6 +1087,7 @@ constexpr static MapBuffer::Key TA_KEY_IS_HIGHLIGHTED = 20;
10831087
constexpr static MapBuffer::Key TA_KEY_LAYOUT_DIRECTION = 21;
10841088
constexpr static MapBuffer::Key TA_KEY_ACCESSIBILITY_ROLE = 22;
10851089
constexpr static MapBuffer::Key TA_KEY_LINE_BREAK_STRATEGY = 23;
1090+
constexpr static MapBuffer::Key TA_KEY_ACCESSIBILITY_UNIT = 47;
10861091

10871092
// constants for ParagraphAttributes serialization
10881093
constexpr static MapBuffer::Key PA_KEY_MAX_NUMBER_OF_LINES = 0;
@@ -1229,6 +1234,10 @@ inline MapBuffer toMapBuffer(const TextAttributes &textAttributes) {
12291234
builder.putString(
12301235
TA_KEY_ACCESSIBILITY_ROLE, toString(*textAttributes.accessibilityRole));
12311236
}
1237+
if (!textAttributes.accessibilityUnit.empty()) {
1238+
builder.putString(
1239+
TA_KEY_ACCESSIBILITY_UNIT, textAttributes.accessibilityUnit);
1240+
}
12321241
return builder.build();
12331242
}
12341243

ReactCommon/react/renderer/components/text/BaseTextProps.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,13 @@ static TextAttributes convertRawProp(
177177
sourceTextAttributes.accessibilityRole,
178178
defaultTextAttributes.accessibilityRole);
179179

180+
textAttributes.accessibilityUnit = convertRawProp(
181+
context,
182+
rawProps,
183+
"accessibilityUnit",
184+
sourceTextAttributes.accessibilityUnit,
185+
defaultTextAttributes.accessibilityUnit);
186+
180187
// Color (accessed in this order by ViewProps)
181188
textAttributes.opacity = convertRawProp(
182189
context,
@@ -287,6 +294,12 @@ void BaseTextProps::setProp(
287294
textAttributes,
288295
accessibilityRole,
289296
"accessibilityRole");
297+
REBUILD_FIELD_SWITCH_CASE(
298+
defaults,
299+
value,
300+
textAttributes,
301+
accessibilityUnit,
302+
"accessibilityUnit");
290303
REBUILD_FIELD_SWITCH_CASE(
291304
defaults, value, textAttributes, opacity, "opacity");
292305
REBUILD_FIELD_SWITCH_CASE(

ReactCommon/react/renderer/components/view/AccessibilityPrimitives.h

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -88,22 +88,6 @@ constexpr bool operator!=(
8888
return !(rhs == lhs);
8989
}
9090

91-
struct AccessibilityUnit {
92-
std::optional<std::string> hours;
93-
};
94-
95-
constexpr bool operator==(
96-
AccessibilityUnit const &lhs,
97-
AccessibilityUnit const &rhs) {
98-
return lhs.hours == rhs.hours;
99-
}
100-
101-
constexpr bool operator!=(
102-
AccessibilityUnit const &lhs,
103-
AccessibilityUnit const &rhs) {
104-
return !(rhs == lhs);
105-
}
106-
10791
struct AccessibilityLabelledBy {
10892
std::vector<std::string> value{};
10993
};

ReactCommon/react/renderer/components/view/AccessibilityProps.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class AccessibilityProps {
3939

4040
bool accessible{false};
4141
AccessibilityState accessibilityState;
42-
AccessibilityUnit accessibilityUnit;
42+
std::string accessibilityUnit;
4343
std::string accessibilityLabel{""};
4444
AccessibilityLabelledBy accessibilityLabelledBy{};
4545
AccessibilityLiveRegion accessibilityLiveRegion{

0 commit comments

Comments
 (0)