Skip to content

fix(textarea): give a focused multiline field a way to close the keyboard - #99

Open
anilcancakir wants to merge 1 commit into
mainfrom
fix/textarea-keyboard-dismissal
Open

fix(textarea): give a focused multiline field a way to close the keyboard#99
anilcancakir wants to merge 1 commit into
mainfrom
fix/textarea-keyboard-dismissal

Conversation

@anilcancakir

Copy link
Copy Markdown
Contributor

What

  • MSTextarea becomes stateful, owns a FocusNode when the caller passes none, and wraps itself in wind's WKeyboardActions so a focused textarea gets a dismiss button above the keyboard. iOS only. A read-only or disabled field takes no toolbar.
  • Removes three dead classes: focus:outline-none from the input and textarea recipes, resize-none from the textarea recipe.

Why

Return inserts a newline in a multiline field, so the keyboard has no key that closes it. Reported from an iPhone: Return in the field above moved focus into the description textarea, and from there the keyboard stayed up over a form the user could no longer see with nothing left to press.

iOS is the scope of the problem, not a convenience: Android's soft keyboard carries a system dismiss and desktop has Escape. It also matters for consumers' test suites, which run as Android by default. An active WKeyboardActions toolbar schedules a frame per frame, so a pumpAndSettle on any page holding a focused textarea would never settle.

The three removed classes are CSS-only concepts with nothing to suppress in Flutter. wind reported each as unknown className ... was ignored while the new tests ran.

Testing

  • Three new cases: a focused textarea shows the toolbar on iOS and shows nothing before focus; a read-only textarea takes no toolbar; an externally supplied focus node survives the widget's disposal (asserted by using it afterwards, which is the failure a wrong dispose would cause).
  • The platform override is reset inside the test body rather than in a tearDown: flutter_test asserts every foundation debug variable is unset before tearDowns run.
  • Full suite green.

…oard

Return inserts a newline in a multiline field, which is the point of the field
and also means the keyboard has no key that dismisses it. Reported from an
iPhone: Return in the input above moved focus into the description textarea, and
from there the keyboard stayed up over a form the user could no longer see, with
nothing to press.

MSTextarea becomes stateful so it can own the FocusNode a keyboard toolbar has
to watch, and wraps itself in wind's WKeyboardActions. A caller's own node is
still honoured and still NOT disposed here, which the test asserts, because
disposing a node someone else created throws on their next use of it.

iOS only, and that is the scope of the problem rather than a convenience:
Android's soft keyboard carries a system dismiss and desktop has Escape. It also
keeps the toolbar out of every consumer's widget tests, which run as Android by
default; an active toolbar schedules a frame per frame, so `pumpAndSettle` on a
page holding a focused textarea never settles. A read-only or disabled field
opens no keyboard and takes no toolbar.

Two dead classes go with it, both found by wind's own unknown-token warning
while the tests above ran: `focus:outline-none` on the input and the textarea,
and `resize-none` on the textarea. Both are CSS-only concepts with nothing to
suppress in Flutter, so they read like they were doing something and were not.
@anilcancakir

Copy link
Copy Markdown
Contributor Author

@kodizm

@kodizm

kodizm Bot commented Aug 21, 2026

Copy link
Copy Markdown

Note

Kodizm (AI-generated). May contain mistakes; verify before acting.

The keyboard fix is the right shape and the two dead recipe classes really are dead, but conditioning the tree shape on enabled/readOnly remounts the WInput and drops whatever the user had typed, and the branch does not pass its own analyze gate.

Critical

lib/src/ui/components/textarea/textarea.dart:152 — data-loss. The early return makes the root of the subtree WKeyboardActions or the bare field depending on enabled/readOnly. Both are commonly dynamic (enabled: !controller.isLoading while a form submits), and when either flips, the element at that slot changes type, the WInput below it is unmounted and rebuilt, and _WInputState.initState re-seeds its internal controller from widget.value ?? '' (fluttersdk_wind-1.3.0/lib/src/widgets/w_input.dart:350). Uncontrolled usage - no value, no controller, which is what the new tests themselves construct - therefore loses the typed text. Verified with a scratch test: type hello world, then rebuild with enabled: false; it passes on HEAD~1 (+1: All tests passed!) and fails on this head (text should survive disabling the field). Keeping WKeyboardActions unconditionally in the tree and gating only its focusNodes (empty list when read-only or disabled) avoids the remount entirely.

Major

test/ui/components/textarea/textarea_test.dart:209expect(external.hasListeners, ...) reads a @protected member, so flutter analyze --no-fatal-infos reports invalid_use_of_protected_member and exits 1. .github/workflows/ci.yml:27 runs exactly that command, so CI fails on this branch as it stands. expect(external.hasListeners, isFalse) is also weaker than it looks - a live external node would still have zero listeners once WKeyboardActions detaches its own on dispose. The requestFocus() line on 210 is the assertion that actually catches a wrong dispose; dropping 209 fixes both problems at once.

CHANGELOG.md:5CLAUDE.md's post-change checklist asks for a [Unreleased] entry after any source change, plus a doc/ sync where behaviour changes; [Unreleased] is still empty and doc/basics/components.md:132 still describes MSTextarea purely in terms of fullWidth, with no mention that a focused field now carries an iOS toolbar.

Minor

lib/src/ui/components/textarea/textarea.dart:149 — the nextFocus: false half of this comment explains an argument 10 lines below it, on the far side of the early return it now sits above; only its second sentence belongs where it is.

lib/src/ui/components/textarea/textarea.dart:156 — the pumpAndSettle hazard the description reasons away for Android is still live for iOS: WKeyboardActions._schedulePlatformGuard re-schedules a frame callback every frame while the toolbar is up, so any consumer test that sets debugDefaultTargetPlatformOverride = TargetPlatform.iOS and focuses a textarea hangs. Worth a line in the doc comment, since nothing in the component hints at it. (Not a defect in this diff - the behaviour is wind's.)

Tests

Three new cases cover the toolbar's presence on iOS, its absence when read-only, and external-node ownership. Nothing covers the enabled/readOnly transition, which is where the Critical above lives.

Checks I ran

  • flutter pub get - resolved, 160 dependencies.
  • flutter test test/ui/components/textarea/textarea_test.dart - +12: All tests passed!.
  • flutter analyze --no-fatal-infos - 1 issue, exit 1 (the warning quoted above).
  • dart format --set-exit-if-changed . - 321 files, 0 changed.
  • Scratch widget test for the enabled toggle - fails on head, passes on HEAD~1.
  • Grepped fluttersdk_wind-1.3.0/lib for resize and outline: only cursor-*-resize in cursor_parser.dart, so resize-none and focus:outline-none were indeed unparsed. Confirmed no in-repo MSTextarea call sites beyond the barrel export and the preview.

Note: my scratch probe is still sitting at test/kodizm_probe_test.dart in the review checkout - I could not delete it, and it is not part of your branch, but delete it if it turns up locally.

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