Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions docs/api-migrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,82 @@ 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
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`,
Expand Down
6 changes: 6 additions & 0 deletions platform/mygo/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions platform/mygo/controls.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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"})
Expand Down
4 changes: 2 additions & 2 deletions platform/mygo/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading