Skip to content

fix(editor): le champ suit le curseur pendant la frappe (#447 point 1)#449

Merged
XaaT merged 2 commits into
devfrom
feat/447-cursor-follow
Jun 12, 2026
Merged

fix(editor): le champ suit le curseur pendant la frappe (#447 point 1)#449
XaaT merged 2 commits into
devfrom
feat/447-cursor-follow

Conversation

@XaaT

@XaaT XaaT commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Action par Claude Fable 5 (demandée par @xatrix)

refs-#447 (point 1 — le point 2, drag-to-scroll de sélection, reste ouvert et sera traité séparément). Retour bêta-dev v123 de Dintr-un lemn (post #2787456).

Problème

Depuis le passage du champ en scroll externe (#422/#434), Compose ne suit plus le caret pendant la frappe : ce suivi n'est câblé nativement que quand le TextField possède son propre scroll interne. Quand la frappe fait descendre le caret sous la zone visible (clavier ouvert), il fallait scroller à la main.

Solution

  • BbcodeFieldImpl réécrit : BasicTextField (overload TextFieldValue, qui expose onTextLayout — M3 OutlinedTextField ne l'expose pas) habillé par OutlinedTextFieldDefaults.DecorationBox pour la parité visuelle (label flottant, bordure focus, couleurs M3). APIs vérifiées dans les artefacts réels du cache Gradle : DecorationBox stable en M3 1.4.0, BringIntoViewRequester stable en foundation 1.11.2.
  • BringIntoViewRequester attaché au nœud texte interne (le rect de TextLayoutResult.getCursorRect est dans son espace de coordonnées, pas celui de la boîte décorée), déclenché par LaunchedEffect(isFocused, selection, textLayout) — jamais sur value.text seul, qui ferait viser le layout périmé du texte précédent.
  • Parité M3 répliquée : semantics(mergeDescendants) + padding(top = 8.dp) (réserve la moitié haute du label flottant — vérifié rogné sans, screenshot émulateur), textStyle onSurface, caret primary, capitalisation Sentences (Phase 2F — Éditeur : pas de capitalisation auto en début de phrase (Gboard) vs RF1 #237 conservé).

Validation

  • Docker 2 parts : detektAll test :app:assembleProdDebug puis :app:lintProdDebug — verts (re-run incrémental après le fix padding : 4/4 tests BbcodeTextFieldViewportTest dont le nouveau).
  • Test Robolectric ajouté : sélection déplacée en fin de contenu long → le viewport scrolle (> 0) pour révéler le caret.
  • Émulateur : 30 lignes tapées au clavier → le viewport suit la frappe, caret visible au-dessus de l'IME ; label flottant entier ; états focused/unfocused conformes.

🤖 Generated with Claude Code

XaaT and others added 2 commits June 12, 2026 21:20
…int 1)

Le champ en scroll externe (#422/#434) ne demandait jamais au scrollable
ancêtre de suivre le caret en frappe — Compose ne câble ce suivi que
quand le TextField possède son propre scroll interne. Retour bêta-dev
v123 de Dintr-un lemn (post #2787456).

- BbcodeFieldImpl réécrit : BasicTextField (overload TextFieldValue,
  qui expose onTextLayout — M3 OutlinedTextField ne l'expose pas) +
  OutlinedTextFieldDefaults.DecorationBox pour la parité visuelle
  (label flottant, bordure focus, couleurs M3).
- BringIntoViewRequester attaché au nœud texte INTERNE (le caret rect
  de TextLayoutResult.getCursorRect est dans son espace de coordonnées,
  pas celui de la boîte décorée), déclenché sur (focus, sélection,
  layout) — jamais sur value.text seul (layout périmé).
- Parité M3 répliquée : semantics(mergeDescendants) + padding(top=8.dp)
  (réserve la moitié haute du label flottant — vérifié rogné sans),
  textStyle onSurface, caret primary, capitalisation Sentences.
- Test Robolectric : sélection déplacée en fin de contenu long →
  le viewport scrolle (>0) pour révéler le caret.

Vérifié sur émulateur : 30 lignes tapées, le viewport suit et le caret
reste visible au-dessus de l'IME ; label flottant entier.

Le point 2 de #447 (drag-to-scroll de sélection) reste ouvert —
structurellement plus coûteux, traité séparément.

Demandé par @xatrix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2 IMPORTANT + 1 MINOR de la review Codex sur #449 :

- L'assertion « scrollAfter > scrollBefore » prouvait un scroll, pas la
  révélation du caret : un requester accroché au mauvais ancêtre (ou un
  rect lu dans l'espace de la boîte décorée) sous-scrollerait d'un
  offset constant et passait quand même. Caret en DERNIÈRE ligne =>
  assert scrollAfter >= 95% du maxValue du range.
- Le mode défaut (fillViewport=false dans un verticalScroll externe,
  layout TopicFormScreen) n'était pas couvert : 2e test avec Column
  scrollable externe taguée, même contrat.
- Commentaires resserrés : le wrapper Box du requester est offset-free
  PAR CONTRAT (son origine coïncide avec celle du layout texte).

5/5 tests BbcodeTextFieldViewportTest verts + detektAll.

Demandé par @xatrix

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@XaaT XaaT merged commit c6091e4 into dev Jun 12, 2026
1 check passed
@XaaT XaaT deleted the feat/447-cursor-follow branch June 12, 2026 19:39
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.

1 participant