Skip to content

1.6.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 16:36
· 18 commits to master since this release
b53ece9

Added

  • WSelect.onOpen (and the same on WFormSelect), because opening the menu silently resets the visible list under a paginating caller (#207). _toggleMenu puts _searchQuery back to empty and _filteredOptions back to options every time the menu opens, which is right for the widget and invisible to the caller that owns onLoadMore and hasMore. Its cursor survives the reset, so the next scroll to the bottom asks for the page AFTER the one the reader can no longer see. Reported against a searchable timezone select backed by a paged endpoint: open, scroll once to pull page two, close, reopen, and page two's twenty identifiers were unreachable without searching for them, which is the same symptom pagination was added to fix. A callback rather than internal state, because the cursor belongs to whoever owns onLoadMore and nothing in this widget needs it; a select that passes none behaves exactly as before. Forwarded through both form wrappers, WFormSelect and WFormMultiSelect: a multi-select stays open while the reader picks, so a paginating caller reaches the reset more often there than on a single select. The callback fires after the setState that resets the list rather than inside it, which is where every other caller callback in the widget already ran: a caller throwing there would otherwise abandon the closure half done and leave the menu marked open with its overlay mount never scheduled, so the next tap closed a menu the reader never saw. A test throws from onOpen and asserts the menu is on screen, which is the property the placement exists for rather than the dispatch the other tests already cover. (lib/src/widgets/w_select.dart, lib/src/widgets/w_form_select.dart, doc/widgets/w-select.md, example/lib/pages/forms/select_pagination.dart, skills/wind-ui/references/widgets.md, skills/wind-ui/references/forms.md)

Changed

  • README.md is rewritten around what a reader decides in the first screen, and the comparison table of other packages is gone. The table ranked six alternatives and cost a paragraph each to say what Wind is not, which is the wrong job for the one page pub.dev ranks and renders. What replaces it: the positioning sentence and a runnable example inside the first forty lines, the raw Flutter equivalent folded into a <details> so the verbosity argument is made by the diff rather than by prose, a capability list that names the recent work (keyboard clearance on a focused field, activation from a keyboard, a gamepad and a remote), and a className rules table an agent can act on without opening the docs site. The ecosystem now gets one footer sentence instead of a table, which is where every comparable family package keeps it. dart.dev's package-page guidance is followed on the two points the old page broke: no repeated package name at the top, and the first copy-pasteable example early rather than after the comparison. (README.md)

Fixed

  • A focused WInput now clears the keyboard entirely, not just its first line (#208). EditableText already scrolls itself into view on focus and on every keyboard metrics change, but what it scrolls is the CARET rect with scrollPadding around it. On a single-line field the caret and the field are the same box and the behaviour is right. On a multiline one they are not: the caret sits on the first line, so line one clears the keyboard and every line below it stays under it. Reported against a three-line update composer, where tapping the field left most of it behind the keyboard and the reader scrolled by hand to see what they were typing. Measured on a 900px viewport with a 300px keyboard: the field ran to 608 against a keyboard starting at 600, and now ends at 592.

    The reserve follows the caret as the reader types, which took three pieces and none of them was optional. A listener on the controller, because typing does not rebuild this widget: the only other listener drives the placeholder and its subtree collapses the moment the field is not empty, so without one the reserve stayed the value computed at focus while EditableText re-revealed the descending caret against it, measured at 582 through forty typed lines. The recomputation deferred to a post-frame, because the controller notifies SYNCHRONOUSLY and the render editable has not laid the new text out, so reading then returns the caret rect from before the keystroke and the comparison bails for good. And a bringIntoView afterwards, because the reveal that came with the keystroke already used the stale reserve and a corrected one does not move the view back on its own.

    The mechanism is scrollPadding, widened below by how much of the field sits BELOW THE CARET, so Flutter's own caret scroll clears the rest of the field with it. Below the caret and not the whole height, because EditableText inflates the caret's rect rather than the field's: the two agree only while the caret is on line one, and once the reader types down to the last line a full-height term reserves a second field below it. Measured on a 156px field, that put the bottom 112px clear of the keyboard instead of 42, and on a field taller than the visible area it pushed the top 268 pixels off the top of the screen, which is the same failure the removed ensureVisible had. A first version added a separate Scrollable.ensureVisible instead and it was removed: measured against a single metrics dispatch it moved the field not at all, and on a page with content below the field it scrolled it 308 pixels off the TOP of the screen. It had looked correct only because the test raised the keyboard twice, and this widget had no de-dup where EditableText has one, so it got two post-frame callbacks to Flutter's one and ran last.

    The keyboard toolbar occludes too, and nothing reported it. WKeyboardActions draws its bar in an overlay at bottom: viewInsets.bottom, which is directly on top of the keyboard, so the region a focused field has to clear is the keyboard PLUS the bar and the engine knows about only the first. A field that cleared viewInsets came out from under the keyboard and straight under the toolbar, which is what a three-line composer showing half of its first line was. WKeyboardActions now measures its own bar and publishes the height through WKeyboardToolbarInset, and WInput adds it. The bar is iOS-gated and widget tests run as Android, which is why none of this reproduced in a test until one opted into iOS. (lib/src/widgets/w_input.dart, lib/src/widgets/w_keyboard_actions.dart, doc/widgets/w-input.md, doc/widgets/w-keyboard-actions.md, skills/wind-ui/references/forms.md)

  • A WSelect response that was in flight when the menu reopened no longer writes to the list the reopen restored (#207). Both async paths had the same hole and neither could see it from what it was comparing. A search checked the query string, and the empty query is equal to itself across the reset, so typing a word, clearing the box and reopening inside the window let the stale response overwrite options with the cleared search's rows. A page had no query to compare at all, so a page two asked for before the close was stitched onto the end of a list the reader had never scrolled past, which reads as duplicate or out-of-order rows appearing from nowhere. The question neither one was asking is not "is this the answer I last requested" but "is the list I was answering still on screen", so both now capture a list epoch that the open bumps, and a response whose epoch has moved is dropped. The load-more path still lowers its in-flight flag when it drops a page, because leaving it raised would refuse every later scroll. The epoch covers the third place the list is replaced wholesale as well: a caller refreshing options while the menu is open used to get an in-flight page stitched onto the refreshed list, which is the reopen symptom reached by a different door. (lib/src/widgets/w_select.dart)

  • Reopening a WSelect no longer strands the menu on a spinner when a search was still in flight (#207). Opening resets _searchQuery to empty and _filteredOptions back to options, but _isSearching survived that reset, and the only place that lowers it is _filterOptions's resolve path, which is guarded on the response still matching _searchQuery. The reset guarantees it never does, so a search closed before its response landed left the reopened menu showing loadingBuilder over an empty list with nothing to lower the flag: no rows, no empty state, and no recovery until the reader typed another character. A debounced caller makes the window ordinary rather than exotic, because it is the debounce rather than the network: type, close inside 300ms, reopen. (lib/src/widgets/w_select.dart)

  • A rounded overflow-hidden no longer erases the border at the corners (#206). The clip was a ClipRRect with Clip.hardEdge, which takes no anti-aliasing and can therefore only cut along whole pixels. On a square clip that is right and it is the cheapest option; on a rounded one it saws the curve into a staircase, and the thing standing on that curve is the border. BorderSide.strokeAlign defaults to strokeAlignInside, so the stroke's outer edge sits exactly on the clip path and the staircase eats a 1px line rather than the surface behind it. What a reader sees is a border that thins and vanishes through each corner while the straight runs stay crisp, which reads as a rendering fault rather than as a clip. Found on a consumer app's settings rows and its status-preview card, both overflow-hidden rounded-2xl border; it reaches every recipe in the ecosystem that clips and draws a border, magic_starter's settings sections, accordions, selects and comboboxes among them. Flutter's own guidance reads the same way: hardEdge is documented as reasonable "if the container is an axis-aligned rectangle or an axis-aligned rounded rectangle with very small corner radii" and antiAlias as recommended "when clipping is needed and the shape is not an axis-aligned rectangle", and wind's rounded tokens run from 4px to 32px. The zero-radius case keeps hardEdge and the test pins that branch too: anti-aliasing a straight edge buys nothing and antiAlias carries Flutter's documented bleeding-edge artifact, so the change is scoped to the shape that needed it. (lib/src/widgets/w_div.dart, doc/layout/overflow.md, example/lib/pages/effects/overflow_basic.dart)