Files
kjol/go/wasmruntime/host.go

93 lines
3.6 KiB
Go

package wasmruntime
import (
"strconv"
"strings"
)
// The host API: the channel through which the browser tells WebAssembly things Go
// cannot work out on its own — how big an element is, where the pointer is, how
// tall the viewport is — plus the imperative escape hatches (write a style, focus
// an input, download a file) that a pure re-render cannot express.
//
// Every function here exists in two builds: the real one (host_wasm.go, js+wasm)
// and a no-op that returns zero values (host_native.go). That is what lets the
// neutral kjol/webui components — which must also compile on the server for SSR —
// call them unconditionally. On the server a measurement is simply the zero Rect,
// a listener is never installed, and a timer never fires; components render their
// pre-measurement state (a panel with `visibility: hidden`), which is exactly what
// SSR should ship.
//
// Two rules for callers:
//
// 1. Measure only after the DOM exists. A ref is empty until the reconciler has
// committed; use AfterRender to run measurement code once the current render
// is on screen.
// 2. Do NOT position through signals. Signal.Set re-renders the whole tree; doing
// that on every scroll frame is pathological. Write positions with SetStyle,
// which mutates the DOM node directly and leaves the vdom alone.
// Rect is an element's box in viewport coordinates — the result of
// getBoundingClientRect. Viewport coordinates compose directly with
// `position: fixed`, so no scroll compensation is needed (or wanted) anywhere.
type Rect struct{ X, Y, Width, Height float64 }
func (r Rect) Top() float64 { return r.Y }
func (r Rect) Left() float64 { return r.X }
func (r Rect) Right() float64 { return r.X + r.Width }
func (r Rect) Bottom() float64 { return r.Y + r.Height }
func (r Rect) CenterX() float64 { return r.X + r.Width/2 }
func (r Rect) CenterY() float64 { return r.Y + r.Height/2 }
// Empty reports whether the rect carries no useful geometry — an unmounted ref, or
// an element that has not been laid out yet. Positioning code must treat this as
// "cannot measure yet" rather than as a box at the origin, or the panel paints at
// 0,0 for a frame.
func (r Rect) Empty() bool { return r.Width == 0 && r.Height == 0 }
// Size is a width/height pair — the viewport, or an element's size.
type Size struct{ Width, Height float64 }
// Unsub removes a listener installed by OnWindow / OnDocument / ObserveResize.
// Always call it when the thing that installed it goes away; a floating panel that
// leaks a window scroll listener per open will crawl.
type Unsub func()
// ScrollBlock values for ScrollIntoView.
const (
ScrollBlockStart = "start"
ScrollBlockCenter = "center"
ScrollBlockEnd = "end"
ScrollBlockNearest = "nearest"
)
// parseCSSLength resolves a CSS length to pixels. rootFontSize is the computed
// font-size of :root (itself a px string), which is what a rem is measured
// against. Unitless and unparseable values yield 0.
func parseCSSLength(value, rootFontSize string) float64 {
value = strings.TrimSpace(value)
if value == "" {
return 0
}
switch {
case strings.HasSuffix(value, "px"):
return parseFloat(strings.TrimSuffix(value, "px"))
case strings.HasSuffix(value, "rem"):
root := parseFloat(strings.TrimSuffix(strings.TrimSpace(rootFontSize), "px"))
if root == 0 {
root = 16 // the browser default, if :root's font-size is unreadable
}
return parseFloat(strings.TrimSuffix(value, "rem")) * root
default:
return parseFloat(value)
}
}
func parseFloat(s string) float64 {
f, err := strconv.ParseFloat(strings.TrimSpace(s), 64)
if err != nil {
return 0
}
return f
}