Skip to content

Conversation

@MrWangJustToDo
Copy link

@MrWangJustToDo MrWangJustToDo commented Sep 3, 2025

Currently the <Text /> only support wrap props to make Ink wrap text and split it into multiple lines

In this PR, add a wrap-break-word value for wrap props on <Text /> component, which control this behavior

link #741

@sindresorhus
Copy link
Collaborator

AI review:


Good direction, but the semantics and naming don’t match. As written, it implements CSS-like overflow-wrap:anywhere, not word-break: break-word. Fix the behavior or rename it. Add tests for tricky cases.

API & Semantics

  • The PR adds wrap="wrap-break-word" and implements it with:

    • wrapAnsi(text, maxWidth, {trim:false, hard:true, wordWrap:false}). ([GitHub]2)
  • Problem: wordWrap:false forces column filling and splits words even when not necessary. That’s “break anywhere”. Your new test expecting "hello w\nord!" proves it. True break-word should wrap at spaces when possible and only break inside long tokens when otherwise overflowing. MDN: break-word vs anywhere are different. ([MDN Web Docs]3, [LogRocket Blog]4, [CSS-Tricks]5)

Two viable paths

Pick one and align everything:

  1. Implement true “break-word”
    Use:

    wrapAnsi(text, maxWidth, {trim:false, hard:true /* keep default wordWrap:true */})
    • hard:true allows breaking inside a too-long token; wordWrap:true prefers whitespace splits. This yields:
    • "hello word!" at width 7 → "hello\nword!" (no mid-word break).
    • Long unspaced strings wrap inside the token.
    • Matches standard “break-word” semantics. Docs should say: “Wrap normally; break inside long words only when needed.” Reference options: hard, wordWrap. ([GitHub]6, [npm]7)
  2. Keep current behavior but rename

    • Keep {trim:false, hard:true, wordWrap:false} but rename value to wrap-anywhere (or wrap-hard).
    • Describe: “Fill columns; split anywhere, even when a whitespace break exists earlier.”
    • Update README examples accordingly. ([MDN Web Docs]3)

Naming

  • wrap-break-word is misleading with current implementation. If you really want break-word, change behavior (Path 1). If you want “anywhere”, rename (Path 2). Don’t ship a misleading name.

Types & Internal Consistency

  • You updated Styles['textWrap'] to include the new value. Verify the union matches the public prop union used by <Text wrap>. Ensure the union still includes the full truncate-* strings (no stray 'end'|'middle' without truncate-…). The diff snippet shows a partial union that looks suspicious; double-check the full file. ([GitHub]2)
  • Confirm <Text> props reference Styles['textWrap'] so TypeScript users get the new literal. Also update any theme or style helpers that narrow on wrap values.

Tests

Add coverage beyond the two happy paths you added:

  1. Whitespace vs no-whitespace

    • "hello word!", width 7

      • break-word: "hello\nword!"
      • anywhere: "hello w\nord!" (current)
    • "aaaaaaaaaa", width 5 → must break inside the token for both modes.

  2. ANSI sequences

    • Colored text must not break escape codes; wrap-ansi should already handle this. Add a test with nested <Text color="…">. ([Socket]8)
  3. Full-width/CJK & emoji

    • Include CJK characters and a wide emoji. Make sure width calculations and line breaks don’t split grapheme clusters in a way that renders garbage. There are open issues around width calculation; worth guarding against regressions. ([GitHub]9)
  4. Edge widths

    • maxWidth 1 and 2, very long tokens, and trailing spaces.
  5. Nested <Text>

    • Ensure nested children don’t change behavior vs plain strings.

Docs

  • README change adds the new value but the description is ambiguous: “breaking words if necessary” suggests “break-word”, not “anywhere”. Fix wording to match the chosen behavior and add an explicit example showing the difference from wrap. Also update the <Text> docs section where accepted values are listed. ([GitHub]2)

Suggested copy (if you keep current behavior and rename to wrap-anywhere):

Allowed values: wrap wrap-anywhere truncate truncate-start truncate-middle truncate-end
wrap: Wrap at whitespace; long words may extend past the width.
wrap-anywhere: Fill columns and break anywhere, including inside words.
truncate-*: Truncate to one line with an ellipsis.

Include short code samples that render to the exact outputs.

Implementation details

  • Location of the new branch is fine (before the truncate handling). Keep trim:false to preserve whitespace parity with current behavior. ([GitHub]2)
  • Verify the default wrap path still uses wrapAnsi(text, maxWidth, {trim:false}) (soft wrapping). If not, restore it.

Actionable checklist

  • Decide behavior: break-word (change options) or anywhere (keep options, rename).
  • Align README wording and examples with the chosen behavior. ([GitHub]10)
  • Ensure TextProps['wrap'] union exports the new literal.
  • Add tests for: long tokens, ANSI, CJK/emoji, width=1, nested <Text>.
  • (Optional) Add a small note in the changelog/release notes clarifying the difference between wrap and the new mode.

If you switch to true break-word, the code change is literally one line (wordWrap:true by omission). If you keep the current logic, rename to wrap-anywhere to avoid confusion with CSS semantics.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants