Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add accessibility identifiers to SingleChoiceList rows and text fields #7273

Merged
merged 1 commit into from
Dec 4, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading