// Package webui is a Go/WebAssembly UI component kit for the gowasm engine — a // direct port of kjol's Solid.js TSX kit (web/kit). Components are neutral // *vdom.VNode builders: they render to HTML on the server (SSR) and hydrate on // the client, styled with Tailwind utility classes. // // Port conventions (how the TSX maps to Go): // // - A TSX component `function Foo(props)` becomes `func Foo(p FooProps, children ...*vdom.VNode) *vdom.VNode` // (children variadic when the component wraps content). // - Solid's reactive accessors (`value | () => value`) collapse away: the whole // component re-renders on a signal write (React-style), so callers pass plain // current values — read a signal with `.Get()` at the call site. // - `onclick={fn}` → `vdom.On(vdom.EVENT_CLICK, fn)`; handlers needing the event use // `OnEvent`. `class` overrides are the trailing `Class` field, joined via cx. // - Tailwind classes are copied verbatim so kjol/cmd/twcss (scanning these .go // files) emits the matching CSS. Custom tokens (rounded-default, bg-primary, // text-text-heading, …) come from the app's @theme block. // // # Browser-backed components // // Anything that has to measure the page — a popover that must not fall off the // screen, a column you can drag wider, a tour that spotlights an element — reaches // the browser through the host API in kjol/wasmruntime (Measure, Viewport, SetStyle, // RAF, AfterRender, OnDocument/OnWindow, storage, …). That API is dual-build: real // in the browser, no-op stubs natively. So these components stay neutral and still // server-render: on the server every measurement is the zero Rect, no listener is // installed, and the component renders its unmeasured state (a panel that is closed, // a table in its declared column order). See kjol/wasmruntime/host.go. // // Such components are CONTROLLERS, not plain functions: they own refs, timers and // open state, so they must be created ONCE — alongside your signals, never inside a // render closure, which would rebuild them every frame: // // menu := webui.NewMenu(webui.MenuOptions{}) // table := webui.NewAutoTableState(cols, webui.AutoTableStateOptions{}) // // return func() *vdom.VNode { // return Div(menu.Trigger(…), menu.Content(…), table.Render(…)) // } // // Floating panels (Tooltip, Popover, Menu, Submenu, DatePicker, Modal, Tutorial) are // all built on the Floating controller in floating.go, which portals them to // document.body and positions them with the engine in position.go. package webui import ( "strings" "kjol/wasmruntime/vdom" ) // cx joins non-empty class fragments with single spaces (a tiny clsx). Trailing // user `Class` overrides go last so they win under equal specificity. func cx(parts ...string) string { var b strings.Builder for _, p := range parts { p = strings.TrimSpace(p) if p == "" { continue } if b.Len() > 0 { b.WriteByte(' ') } b.WriteString(p) } return b.String() } // pick returns v if non-empty, else def (for defaulted string props like type). func pick(v, def string) string { if v == "" { return def } return v } // kids appends children VNodes onto a mod slice (helper for the props+children shape). func kids(mods []vdom.Mod, children []*vdom.VNode) []vdom.Mod { for _, c := range children { if c != nil { mods = append(mods, c) } } return mods }