//go:build !js package codegen import ( "sort" "text/template" core "dappco.re/go/core" log "dappco.re/go/core/log" ) // wcTemplate is the Web Component class template. // Uses closed Shadow DOM for isolation. Content is set via the shadow root's // DOM API using trusted go-html codegen output (never user input). var wcTemplate = template.Must(template.New("wc").Parse(`class {{.ClassName}} extends HTMLElement { #shadow; constructor() { super(); this.#shadow = this.attachShadow({ mode: "closed" }); } connectedCallback() { this.#shadow.textContent = ""; const slot = this.getAttribute("data-slot") || "{{.Slot}}"; this.dispatchEvent(new CustomEvent("wc-ready", { detail: { tag: "{{.Tag}}", slot } })); } render(html) { const tpl = document.createElement("template"); tpl.insertAdjacentHTML("afterbegin", html); this.#shadow.textContent = ""; this.#shadow.appendChild(tpl.content.cloneNode(true)); } }`)) // GenerateClass produces a JS class definition for a custom element. // Usage example: js, err := GenerateClass("nav-bar", "H") func GenerateClass(tag, slot string) (string, error) { if !core.Contains(tag, "-") { return "", log.E("codegen.GenerateClass", "custom element tag must contain a hyphen: "+tag, nil) } b := core.NewBuilder() err := wcTemplate.Execute(b, struct { ClassName, Tag, Slot string }{ ClassName: TagToClassName(tag), Tag: tag, Slot: slot, }) if err != nil { return "", log.E("codegen.GenerateClass", "template exec", err) } return b.String(), nil } // GenerateRegistration produces the customElements.define() call. // Usage example: js := GenerateRegistration("nav-bar", "NavBar") func GenerateRegistration(tag, className string) string { return `customElements.define("` + tag + `", ` + className + `);` } // TagToClassName converts a kebab-case tag to PascalCase class name. // Usage example: className := TagToClassName("nav-bar") func TagToClassName(tag string) string { b := core.NewBuilder() for _, p := range core.Split(tag, "-") { if len(p) > 0 { b.WriteString(core.Upper(p[:1])) b.WriteString(p[1:]) } } return b.String() } // GenerateBundle produces all WC class definitions and registrations // for a set of HLCRF slot assignments. // Usage example: js, err := GenerateBundle(map[string]string{"H": "nav-bar"}) func GenerateBundle(slots map[string]string) (string, error) { seen := make(map[string]bool) b := core.NewBuilder() keys := make([]string, 0, len(slots)) for slot := range slots { keys = append(keys, slot) } sort.Strings(keys) for _, slot := range keys { tag := slots[slot] if seen[tag] { continue } seen[tag] = true cls, err := GenerateClass(tag, slot) if err != nil { return "", log.E("codegen.GenerateBundle", "generate class for tag "+tag, err) } b.WriteString(cls) b.WriteByte('\n') b.WriteString(GenerateRegistration(tag, TagToClassName(tag))) b.WriteByte('\n') } return b.String(), nil }