How to set the cursor to any part of the text using TextFieldValue (w/ FocusRequester) on pressing/clicking the TextField - focus

I tried to look around but I can't find a way to
force a focus,
set the cursor to the end of text
and still be able to set the cursor to any part of the text when
pressing/clicking.
With FocusRequester the cursor is set to the start of text, but with TextFieldValue(<text>, <range>) I'm able to place the cursor at the end, the problem is that with this approach the cursor is always forced to any specified selection = TextRange(<index>)(in my case its the end using the current length of the changing value onValueChange), I have no idea how to set the cursor in any part (selection) when I press/click the TextField.
My Implementation:
var textFieldValue by remember { mutableStateOf(TextFieldValue("Some Text")) }
Row (
modifier = Modifier
.height(150.dp)
.fillMaxWidth()
.clickable {
focusRequester.requestFocus()
textFieldValue = textFieldValue.copy(selection = TextRange("Some Text".length))
}
) {
BasicTextField(
modifier = Modifier.focusRequester(focusRequester),
value = textFieldValue,
onValueChange = {
textFieldValue =
textFieldValue.copy(text = it.text, selection = TextRange(it.text.length))
},
)
}
And what I'm trying to achieve (Large text area with text set and starts from top-left), its entire part should be clickable and triggers focus for the text field, the reason why I wrapped it in a Row with a clickable modifier.
I wasn't able to achieve this with a single text field with specified height, as TextAlign doesn't have a Top-Start alignment

Related

Jetpack compose: how to set intrinsic height as minimum, but expand to fill space?

I'm trying to set it so that a TextField refuses to shrink past its intrinsic height (defined by minLines), but also grow to fill max height:
// context: this is inside of a Column that has fillMaxHeight on it
OutlinedTextField(
// unrelated fields
minLines = 26,
modifier = Modifier
//.height(IntrinsicSize.Min)
.fillMaxWidth()
.fillMaxHeight()
.weight(1f)
.height(IntrinsicSize.Max)
)
However, everything I've tried (setting fillMaxHeight, weight(1f), using requiredHeight instead of height, etc) has issues. It seems like it's mutually exclusive: I can either set a minimum height based on intrinsic height or I can grow to fill the height (using weight seems to work for that, but not fillMaxHeight for some reason.
What is the proper way to have IntrinsicSize be the minimum, but grow to fill remaining space?
If i understand this question correctly you want your TextField height to be one line tall then increases as user enters text. You can selectively change modifier using Modifier.then()
Edit
As of jetpack compose 1.4.0-alpha02 minLines param is added to OutlineTextField and other text composables
#Composable
private fun Sample() {
var text by remember {
mutableStateOf("Hello World")
}
var focused by remember {
mutableStateOf(false)
}
Column(modifier = Modifier.fillMaxHeight()) {
OutlinedTextField(
value = text,
onValueChange = {
text = it
},
minLines = 26,
// unrelated fields
modifier = Modifier
.fillMaxWidth()
.then(
if (focused) {
Modifier.wrapContentHeight()
} else Modifier.fillMaxHeight()
)
.onFocusChanged {
focused = it.isFocused
}
)
}
}

Jetpack Compose ConstraintLayout TextField baseline moves

I'm experimenting with Jetpack Compose and ConstraintLayout.
I have a Text()-Resource and a TextField. I want the Text to vertically align with TextFields baseline.
#Composable
fun MyComposable() {
ContraintLayout {
val (text, textfield) = createRefs()
var tfValue by remember { mutableStateOf("") }
Text(
"foo",
Modifier.constrainAs(text) {
baseline.linkTo(textfield.baseline)
}
)
TextField(
value = tfValue,
onValueChange = { tfValue = it },
label = { Text("bar") },
modifier = Modifier
.constrainAs(textfield) {
top.linkTo(parent.top, margin = 24.dp)
}
)
}
}
When I use this composable, the text "foo" is perfectly aligned with the baseline of textfield. But when i focus the textfield and the label moves to the top of the textfield, the Text()-Component moves with it. Is this intended?
How can i constrain foo's baseline to the baseline of the content of Textfield rather than to Textfield's label?

How to make ClickableText accessible to screen readers

This code creates a ClickableText element in Jetpack Compose Composable:
ClickableText(
text = forgotPasswordAnnotatedString,
onClick = {
context.startActivity(intent)
},
modifier = Modifier
.padding(top = mediumPadding)
)
The annotated string is defined here to make the text look like a link:
val forgotPasswordAnnotatedString = buildAnnotatedString {
append(stringResource(R.string.forgot_password))
addStyle(
style = SpanStyle(
textDecoration = TextDecoration.Underline,
color = Color.White,
fontSize = 16.sp,
fontWeight = FontWeight.Medium
),
start = 0,
end = 21,
)
}
When I encounter this text using the TalkBalk screen reader in Android, the screenreader does not make it clear that this is clickable text that will do something which tapped on. The reader just reads the text.
Is there a way to make it clear to the screen reader that this text is interactive? Otherwise should I just use a button and style it to look like a link?
It looks like your intention is for the whole text to be clickable? In which you best option is probably a TextButton as suggested by
Gabriele Mariotti.
But if you wan't only part of the link to be clickable, or to have multiple clickable sections, the best I've been able to land on is to draw an invisible box overtop of the Text. It means that I can control the touch target of the clickable area to be at least 48.dp and can use the semantics{} modifier to control how a screen reader interprets it.
Would welcome any suggestions.
// remember variables to hold the start and end position of the clickable text
val startX = remember { mutableStateOf(0f) }
val endX = remember { mutableStateOf(0f) }
// convert to Dp and work out width of button
val buttonPaddingX = with(LocalDensity.current) { startX.value.toDp() }
val buttonWidth = with(LocalDensity.current) { (endX.value - startX.value).toDp() }
Text(
text = forgotPasswordAnnotatedString,
onTextLayout = {
startX.value = it.getBoundingBox(0).left // where 0 is the start index of the range you want to be clickable
endX.value = it.getBoundingBox(21 - 1).right // where 21 is the end index of the range you want to be clickable
}
)
Note that buttonPaddingX is relative to the Text position not the Window, so you may have to surround both in a Box{} or use ConstraintLayout.
Then to draw the invisible box
Box(modifier = Modifier
.sizeIn(minWidth = 48.dp, minHeight = 48.dp) // minimum touch target size
.padding(start = buttonPaddingX)
.width(buttonWidth)
// .background(Color.Magenta.copy(alpha = 0.5f)) // uncomment this to debug where the box is drawn
.clickable(onClick = { context.startActivity(intent) })
.semantics {
// tell TalkBack whatever you need to here
role = Role.Button
contentDescription = "Insert button description here"
}
)
In my code I'm using pushStringAnnotation(TAG, annotation) rather than reference string indexes directly. That way I can get the start and end index of the clickable area with annotatedString.getStringAnnotations(TAG,0,annotatedString.length).first(). Useful if there a multiple links within the text.
It's disappointing that ClickableText doesn't have accessibility in mind from the get go, hopefully we'll be able to use it again in a future update.
Adding .semantics.contentDescription to the Modifier changes what is read by the screen reader. I had to word contentDescription to make it clear that this was a link to reset the your password.
The screen reader still doesn't recognize the element a clickable but hopefully the description will be useful to convey to the user that this element is interactive.
ClickableText(
text = forgotPasswordAnnotatedString,
onClick = {
context.startActivity(intent)
},
modifier = Modifier
.padding(top = mediumPadding)
// new code here:
.semantics {
contentDescription = "Forgot your password? link"
}
)

amCharts 4: display legend tooltip on truncated (with ellipsis) values only

I've enabled the legend on a amCharts v4 chart with the following code but I have issues with ellipsis:
// Add legend
chart.legend = new am4charts.Legend();
chart.legend.fontSize = 11;
// Truncate long labels (more than 160px)
chart.legend.labels.template.maxWidth = 160;
chart.legend.labels.template.truncate = true;
chart.legend.labels.template.fullWords = true;
// Set custom ellipsis as default one is not displayed correctly
chart.legend.labels.template.ellipsis = "...";
// Set tooltip content to name field
chart.legend.itemContainers.template.tooltipText = "{name}";
As you can see I had to set a custom ellipsis property because Firefox v76 displayed €| instead of … on the truncated legend labels. It happens even on the sample on the amChart website but, surprisingly, not if I open the same URL in a private tab... How can I fix that?
Then I would like to display the tooltip on the legend only for truncated labels. Adding an adapter:
chart.legend.itemContainers.template.adapter.add("tooltipText", function(text, target) {
// 'text' here contains the non-truncated string
return "My modified " + text;
})
of course works, but how can I identify inside the adapter code if the label that I'm processing is truncated and clear the text variable? It doesn't make sense to display tooltips for non-truncated legend items.
Not sure the most ideal way but...
You get the text inside the adaptor callback.
You can add a text.length check like:
chart.legend.itemContainers.template.adapter.add("tooltipText", function(text, target) {
// 'text' here contains the non-truncated string
return text.length > someValyeBasedOnMaxwidth> ? "My modified " + text: "";
})
I found the answer about the tooltip adapter; this works:
chart.legend.itemContainers.template.adapter.add("tooltipText", function(text, target) {
if (!target.dataItem.label.isOversized) {
// Legend label is NOT truncated, disable the tooltip
return "";
}
// Legend label is truncated, display the tooltip
return text;
})
Still I don't know why the ellipsis are not displayed correctly without setting the property...

UITextField's width is never less than it's width with placeholder text

I have a UITextField and UILabel sitting together in a UIView as so:
and here it is in Xcode:
The label is hidden until the user enters some text into the text field, so it serves to provide a persistent "suffix" to the numeric entry. The problem is that when the user types a number into the text field, it doesn't shrink down to the size of the text, it remains at the size of the original placeholder, even though it isn't visible, as so:
Is there any way I can constrain the text field's width to be the minimum size to accommodate the user's text, and not pay attention to the invisible placeholder text's width?
Thank you
I managed to solve it myself:
Whenever the text is edited, the text field is checked to see if there is any text inside. If there is no text, the 'mg' suffix is hidden, and the placeholder is added. If there is text, the 'mg' suffix is shown and the placeholder is removed. Like so: (Swift)
func textFieldTextChanged(_ textField: UITextField) {
milligramTextField.invalidateIntrinsicContentSize()
view.layoutIfNeeded()
if textField.text == "" {
milligramSuffixLabel.text = ""
milligramTextField.placeholder = "0 mg"
} else {
milligramSuffixLabel.text = " mg"
milligramTextField.placeholder = ""
}
}
This answer was of great help.

Resources