93 lines
3.6 KiB
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
|
|
}
|