From b2093ed272280f8455f5c3dec3d152ff2bb2550b Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Wed, 28 Oct 2026 10:00:39 +0000 Subject: [PATCH 1/2] fix: preserve bounded MyGo stack layout --- docs/api-migrations.md | 20 ++++++++++++++++++++ platform/mygo/layout.go | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/api-migrations.md b/docs/api-migrations.md index 339e882c..e0eb965c 100644 --- a/docs/api-migrations.md +++ b/docs/api-migrations.md @@ -15,6 +15,26 @@ covered separately. ## Unreleased: component API and MyGo parity +### Bounded and content-sized Stacks on MyGo + +The MyGo `stack` adapter now passes its allocated width and height through +each synthetic layer wrapper to its children. A bounded Stack no longer lets +an automatic content-height floor enlarge a nested Stack beyond that allocation. +Stacks without a bounded size still measure to their largest intrinsic child, +and removing that child lets the Stack shrink. Explicit child sizes, minimums, +maximums and margins remain effective; layer order, `z-index`, pointer +pass-through and retained child identity are unchanged. + +Keep the finite shell constraint on the outer Stack, and use `grow: 1` and +`min-height: 0` on the inner flow Column and Scroll that share its height. +For example, an outer Stack with `height-viewport: 1` in an 897-DIP viewport, +containing a nested Stack and a Column with a 48-DIP header, now gives its +growing Scroll the remaining 849 DIPs even when the Scroll content is 1280 +DIPs tall. Do not repeat the full viewport height on the Scroll: that would +ignore the header's share. Applications that require a content-sized Stack +should continue to leave its size unconstrained rather than add viewport +or fill constraints. No wire vocabulary or other host behavior changes. + ### Owned pointer gestures on Row and Box `row` and `box` now accept `?pointer_capture:bool`, `?on_pointer_down`, diff --git a/platform/mygo/layout.go b/platform/mygo/layout.go index 03d826a6..3eff6cbb 100644 --- a/platform/mygo/layout.go +++ b/platform/mygo/layout.go @@ -53,11 +53,11 @@ func (b *Backend) renderLayout(c *ui.Context, n Node, s *controlState, appearanc child := func(id int64) { b.render(c, id, appearances) } switch n.Kind { case "stack": - e := ui.Grid(c).ColumnTracks(ui.FitContent()).RowTracks(ui.FitContent()) + e := ui.Grid(c).ColumnTracks(ui.Fr(1)).RowTracks(ui.Fr(1)) e.Children(func() { for _, id := range n.Children { c.Key(fmt.Sprintf("stack:%d", id)) - ui.Box(c).PassThrough().ColumnStart(1).RowStart(1).ZIndex(int(b.nodes[id].Number("z-index", 0))).Children(func() { child(id) }) + ui.Grid(c).ColumnTracks(ui.Fr(1)).RowTracks(ui.Fr(1)).PassThrough().ColumnStart(1).RowStart(1).ZIndex(int(b.nodes[id].Number("z-index", 0))).Children(func() { child(id) }) } }) return e, true From 1f44501efe0b17dc7d5ae5df237a985b15eae47a Mon Sep 17 00:00:00 2001 From: Tienson Qin Date: Wed, 28 Oct 2026 13:55:46 +0000 Subject: [PATCH 2/2] fix(mygo): bind host commands to standard text editors --- docs/api-migrations.md | 56 +++++++++++++++++++++++++++++++++++++++ platform/mygo/backend.go | 6 +++++ platform/mygo/controls.go | 6 +++++ 3 files changed, 68 insertions(+) diff --git a/docs/api-migrations.md b/docs/api-migrations.md index e0eb965c..cab38503 100644 --- a/docs/api-migrations.md +++ b/docs/api-migrations.md @@ -15,6 +15,62 @@ covered separately. ## Unreleased: component API and MyGo parity +### Host commands on standard MyGo text editors + +MyGo `Config` now accepts two optional, typed hooks: + +```go +InputValue func(Node, *string) +InputControl func(Node, ui.Element, string) +``` + +`InputValue` runs after ordinary model text reconciliation and immediately +before constructing the native `TextInput` or `TextArea`. It may update that +build's text through the pointer. `InputControl` runs after construction and +input-handler setup, receiving the resulting text for focus and selection +binding. It runs before the existing focus-transition sample, retained handle +binding and autofocus logic; their relative order is unchanged. + +Both hooks run synchronously on the UI thread during live construction, never +in `view-that-fits` measurement probes. Do not retain the text pointer, Element +or its Context beyond the call. Nodes are detached read snapshots. Hooks cover +the text-editing paths of `text-field`, `secure-field`, `input`, `search-field`, +`textarea` and `combobox`. They do not run for specialized ColorWell/TimeInput +branches or custom renderers that replace the standard control. Nil hooks +preserve existing behavior; wire vocabulary and other platforms are unchanged. +Existing keyed Config literals need no changes; positional literals must be +updated for the added fields, preferably by switching to keyed initialization. + +An imperative value command does not synthesize `OnChange`, `TextChanged` or +Dispatch, and does not persist a new model value. Keep the existing controlled +reducer and ordinary text reconciliation. The host still owns command queues, +node incarnation/epoch checks, invalidation and post-layout `AfterFrame` +acknowledgement/retirement. Publish and apply a node's creation before sending +commands for it; these hooks do not buffer missing references or repair message +ordering. Apply selection to the actual Element during construction. MyGo +selection offsets count runes: DOM-style UTF-16 offsets must be converted by +the host against the text passed to `InputControl`, without splitting surrogate +pairs. Any returned DOM offsets are build-time observations, not a substitute +for querying the native selection during an input event. + +For a host with existing incarnation-scoped helpers, configure the standard +renderer rather than copying it or adding component-specific exceptions: + +```go +backend := luimygo.New(luimygo.Config{ + Dispatch: runtime.Dispatch, + InputValue: services.BindInputValue, + InputControl: func(n luimygo.Node, e ui.Element, text string) { + services.BindInputControl(n, e, text) + }, +}) +``` + +Here `BindInputValue` has signature `func(luimygo.Node, *string)` and +`BindInputControl` may return UTF-16 `(start, end int)`; the closure deliberately +discards those results. Existing raw/extension renderers retain their own +bindings; the hooks do not duplicate their dispatch or command ownership. + ### Bounded and content-sized Stacks on MyGo The MyGo `stack` adapter now passes its allocated width and height through diff --git a/platform/mygo/backend.go b/platform/mygo/backend.go index 6430bf57..5709993f 100644 --- a/platform/mygo/backend.go +++ b/platform/mygo/backend.go @@ -101,6 +101,12 @@ type Config struct { Extensions map[string]Extension // Renderers deliberately overrides built-in standard component behavior. Renderers map[string]Renderer + // InputValue applies host-owned text commands before a standard text editor + // is constructed. InputControl then binds its native focus and selection. + // Both run during live construction on the UI thread, never fitting probes. + // Do not retain the value pointer or Element; neither hook emits model events. + InputValue func(Node, *string) + InputControl func(Node, ui.Element, string) } type controlState struct { collection collectionState diff --git a/platform/mygo/controls.go b/platform/mygo/controls.go index e1ced8b3..4861b39d 100644 --- a/platform/mygo/controls.go +++ b/platform/mygo/controls.go @@ -302,6 +302,9 @@ func (b *Backend) renderControls(c *ui.Context, n Node, children func(), emit fu return e, true } } + if !b.probing && b.config.InputValue != nil { + b.config.InputValue(n.clone(), &s.text) + } var e ui.Element if n.Kind == "textarea" { e = ui.TextArea(c, &s.text) @@ -329,6 +332,9 @@ func (b *Backend) renderControls(c *ui.Context, n Node, children func(), emit fu return true }) } + if !b.probing && b.config.InputControl != nil { + b.config.InputControl(n.clone(), e, s.text) + } focused := e.Focused() if w.editorFocused && !focused && n.Bool("submit-on-blur") && n.Bool("submit-enabled") && n.Props["enabled"] != false { emit(Event{Kind: "submit"})