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

Clicking Backspace at the start of a list item: Decrease list level or exit list #523

Merged
merged 3 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
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 @@ -1478,7 +1478,7 @@ public class RichTextState internal constructor(
else
it
}
val paragraphFirstChildStartIndex = firstNonEmptyChildIndex ?: selection.min
val paragraphFirstChildStartIndex = (firstNonEmptyChildIndex ?: selection.min).coerceAtLeast(0)

paragraph.type = newType

Expand Down Expand Up @@ -1864,7 +1864,26 @@ public class RichTextState internal constructor(
paragraphFirstChildMinIndex = minParagraphFirstChildMinIndex,
)

// Save the old paragraph type
val minParagraphOldType = minRichSpan.paragraph.type

// Set the paragraph type to DefaultParagraph
minRichSpan.paragraph.type = DefaultParagraph()

// Check if it's a list and handle level appropriately
if (
maxRemoveIndex - minRemoveIndex == 1 &&
minParagraphOldType is ConfigurableListLevel &&
minParagraphOldType.level > 1
) {
// Decrease level instead of exiting list
minParagraphOldType.level -= 1
tempTextFieldValue = updateParagraphType(
paragraph = minRichSpan.paragraph,
newType = minParagraphOldType,
textFieldValue = tempTextFieldValue,
)
}
}
}

Expand All @@ -1877,8 +1896,6 @@ public class RichTextState internal constructor(
paragraphFirstChildMinIndex = maxParagraphFirstChildMinIndex,
)

maxRichSpan.paragraph.type = DefaultParagraph()

tempTextFieldValue = adjustOrderedListsNumbers(
startParagraphIndex = maxParagraphIndex + 1,
startNumber = 1,
Expand Down Expand Up @@ -2271,8 +2288,7 @@ public class RichTextState internal constructor(
// Ignore adding the new paragraph
index--
continue
} else
if (
} else if (
(!config.preserveStyleOnEmptyLine || richSpan.paragraph.isEmpty()) &&
isSelectionAtNewRichSpan
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.mohamedrejeb.richeditor.model

import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.paragraph.RichParagraph
import com.mohamedrejeb.richeditor.paragraph.type.OrderedList
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertIs
import kotlin.test.assertIsNot
import kotlin.test.assertTrue

@OptIn(ExperimentalRichTextApi::class)
class ListBehaviorTest {
@Test
fun testBackspaceOnEmptyListLevel1() {
val state = RichTextState()

// Create a list with level 1
state.addTextAfterSelection("1.")
state.addTextAfterSelection(" ")

// Verify that the list was created
assertIs<OrderedList>(state.richParagraphList.first().type)

// Simulate backspace at the start of empty list item
state.onTextFieldValueChange(TextFieldValue(
text = "1.",
selection = TextRange(2)
))

// Verify that the list was exited (converted to default paragraph)
assertIsNot<OrderedList>(state.richParagraphList.first().type)
}

@Test
fun testBackspaceOnEmptyListLevel2() {
val state = RichTextState(
listOf(
RichParagraph(
type = OrderedList(
number = 1,
initialLevel = 1,
),
).also {
it.children.add(
RichSpan(
text = "a",
paragraph = it,
)
)
},
RichParagraph(
type = OrderedList(
number = 1,
initialLevel = 2,
),
).also {
it.children.add(
RichSpan(
text = "",
paragraph = it,
)
)
}
)
)

// Simulate backspace at the start of empty list item
val newText = state.annotatedString.text.dropLast(1)
state.onTextFieldValueChange(TextFieldValue(
text = newText,
selection = TextRange(newText.length)
))

// Verify that the list level was decreased but still remains a list
val firstParagraphType = state.richParagraphList[0].type
assertIs<OrderedList>(firstParagraphType)
assertEquals(1, firstParagraphType.number)
assertEquals(1, firstParagraphType.level)

val secondParagraphType = state.richParagraphList[1].type
assertIs<OrderedList>(secondParagraphType)
assertEquals(2, secondParagraphType.number)
assertEquals(1, secondParagraphType.level)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2712,10 +2712,12 @@ class RichTextStateTest {
)
)

richTextState.selection = TextRange(4, 14)
richTextState.selection = TextRange(4, 15)
richTextState.removeSelectedText()

assertEquals(2, richTextState.richParagraphList.size)
assertEquals("A", richTextState.richParagraphList[0].children.first().text)
assertEquals("D", richTextState.richParagraphList[1].children.first().text)
}

@OptIn(ExperimentalRichTextApi::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.runDesktopComposeUiTest
import androidx.compose.ui.text.TextRange
import com.mohamedrejeb.richeditor.annotation.ExperimentalRichTextApi
import com.mohamedrejeb.richeditor.paragraph.RichParagraph
import com.mohamedrejeb.richeditor.paragraph.type.OrderedList
import com.mohamedrejeb.richeditor.ui.BasicRichTextEditor
Expand All @@ -20,7 +21,9 @@ import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse

@OptIn(ExperimentalTestApi::class, ExperimentalComposeUiApi::class, InternalComposeUiApi::class)
@OptIn(ExperimentalTestApi::class, ExperimentalComposeUiApi::class, InternalComposeUiApi::class,
ExperimentalRichTextApi::class
)
class RichTextStateKeyEventTest {
@get:Rule
val rule = createComposeRule()
Expand Down
Loading