Skip to content

Commit

Permalink
Add accessibility identifiers to SingleChoiceList rows and text fields
Browse files Browse the repository at this point in the history
  • Loading branch information
acb-mv committed Dec 2, 2024
1 parent adc7c4b commit ca7ed9b
Showing 1 changed file with 35 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
var value: Binding<Value>
@State var initialValue: Value?
let itemDescription: (Value) -> String
let itemAccessibilityIdentifier: (Value) -> String
let customFieldMode: CustomFieldMode

/// The configuration for the field for a custom value row
Expand All @@ -83,6 +84,7 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
// this row consists of a text field into which the user can enter a custom value, which may yield a valid Value. This has accompanying text, and functions to translate between text field contents and the Value. (The fromValue method only needs to give a non-nil value if its input is a custom value that could have come from this row.)
case custom(
label: String,
accessibilityIdentifier: String,
prompt: String,
legend: String?,
minInputWidth: CGFloat?,
Expand All @@ -102,12 +104,14 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
optionSpecs: [OptionSpec.OptValue],
value: Binding<Value>,
itemDescription: ((Value) -> String)? = nil,
itemAccessibilityIdentifier: ((Value) -> String)? = nil,
customFieldMode: CustomFieldMode = .freeText
) {
self.title = title
self.options = optionSpecs.enumerated().map { OptionSpec(id: $0.offset, value: $0.element) }
self.value = value
self.itemDescription = itemDescription ?? { "\($0)" }
self.itemAccessibilityIdentifier = itemAccessibilityIdentifier ?? { "\($0)" }
self.customFieldMode = customFieldMode
self.initialValue = value.wrappedValue
}
Expand All @@ -118,12 +122,20 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
/// - title: The title of the list, which is typically the name of the item being chosen.
/// - options: A list of `Value`s to be presented.
/// - itemDescription: An optional function that, when given a `Value`, returns the string representation to present in the list. If not provided, this will be generated naïvely using string interpolation.
init(title: String, options: [Value], value: Binding<Value>, itemDescription: ((Value) -> String)? = nil) {
/// - itemAccessibilityIdentifier: An optional function that, when given a `Value`, returns the accessibility identifier for the value's list item. If not provided, this will be generated naïvely using string interpolation.
init(
title: String,
options: [Value],
value: Binding<Value>,
itemDescription: ((Value) -> String)? = nil,
itemAccessibilityIdentifier: ((Value) -> String)? = nil
) {
self.init(
title: title,
optionSpecs: options.map { .literal($0) },
value: value,
itemDescription: itemDescription
itemDescription: itemDescription,
itemAccessibilityIdentifier: itemAccessibilityIdentifier
)
}

Expand All @@ -133,9 +145,11 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
/// - title: The title of the list, which is typically the name of the item being chosen.
/// - options: A list of fixed `Value`s to be presented.
/// - itemDescription: An optional function that, when given a `Value`, returns the string representation to present in the list. If not provided, this will be generated naïvely using string interpolation. This is only used for the non-custom values.
/// - itemAccessibilityIdentifier: An optional function that, when given a `Value`, returns the accessibility identifier for the value's list item. If not provided, this will be generated naïvely using string interpolation.
/// - parseCustomValue: A function that attempts to parse the text entered into the text field and produce a `Value` (typically the tagged custom value with an argument applied to it). If the text is not valid for a value, it should return `nil`
/// - formatCustomValue: A function that, when passed a `Value` containing user-entered custom data, formats that data into a string, which should match what the user would have entered. This function can expect to only be called for the custom value, and should return `nil` in the event of its argument not being a valid custom value.
/// - customLabel: The caption to display in the custom row, next to the text field.
/// - customAccessibilityIdentifier: The accessibility identifier to use for the custom row. If not provided, "customValue" will be used. The accessibility identifier for the text field will be this value with ".input" appended.
/// - customPrompt: The text to display, greyed, in the text field when it is empty. This also serves to set the width of the field, and should be right-padded with spaces as appropriate.
/// - customLegend: Optional text to display below the custom field, i.e., to explain sensible values
/// - customInputWidth: An optional minimum width (in pseudo-pixels) for the custom input field
Expand All @@ -146,9 +160,11 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
options: [Value],
value: Binding<Value>,
itemDescription: ((Value) -> String)? = nil,
itemAccessibilityIdentifier: ((Value) -> String)? = nil,
parseCustomValue: @escaping ((String) -> Value?),
formatCustomValue: @escaping ((Value) -> String?),
customLabel: String,
customAccessibilityIdentifier: String = "customValue",
customPrompt: String,
customLegend: String? = nil,
customInputMinWidth: CGFloat? = nil,
Expand All @@ -159,6 +175,7 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
title: title,
optionSpecs: options.map { .literal($0) } + [.custom(
label: customLabel,
accessibilityIdentifier: customAccessibilityIdentifier,
prompt: customPrompt,
legend: customLegend,
minInputWidth: customInputMinWidth,
Expand All @@ -168,6 +185,7 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
)],
value: value,
itemDescription: itemDescription,
itemAccessibilityIdentifier: itemAccessibilityIdentifier,
customFieldMode: customFieldMode
)
}
Expand Down Expand Up @@ -202,12 +220,14 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
customValueIsFocused = false
customValueInput = ""
}
.accessibilityIdentifier(itemAccessibilityIdentifier(item))
}

// Construct the one row with a custom input field for a custom value
// swiftlint:disable function_body_length
private func customRow(
label: String,
accessibilityIdentifier: String,
prompt: String,
inputWidth: CGFloat?,
maxInputLength: Int?,
Expand Down Expand Up @@ -288,6 +308,7 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
customValueInput = valueText
}
}
.accessibilityIdentifier(accessibilityIdentifier + ".input")
}
.onTapGesture {
if let v = toValue(customValueInput) {
Expand All @@ -296,6 +317,7 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
customValueIsFocused = true
}
}
.accessibilityIdentifier(accessibilityIdentifier)
}

// swiftlint:enable function_body_length
Expand Down Expand Up @@ -323,9 +345,19 @@ struct SingleChoiceList<Value>: View where Value: Equatable {
switch opt.value {
case let .literal(v):
literalRow(v)
case let .custom(label, prompt, legend, inputWidth, maxInputLength, toValue, fromValue):
case let .custom(
label,
accessibilityIdentifier,
prompt,
legend,
inputWidth,
maxInputLength,
toValue,
fromValue
):
customRow(
label: label,
accessibilityIdentifier: accessibilityIdentifier,
prompt: prompt,
inputWidth: inputWidth,
maxInputLength: maxInputLength,
Expand Down

0 comments on commit ca7ed9b

Please sign in to comment.