// SPDX-Licence-Identifier: EUPL-1.2 package main import ( "strconv" "strings" ) var canonicalSlotOrder = []string{"H", "L", "C", "R", "F"} var reservedCustomElementTags = map[string]struct{}{ "annotation-xml": {}, "color-profile": {}, "font-face": {}, "font-face-src": {}, "font-face-uri": {}, "font-face-format": {}, "font-face-name": {}, "missing-glyph": {}, } // cmd/wasm/components.go: isValidCustomElementTag reports whether tag is a safe // custom element name. // It mirrors the codegen package validation without importing the heavier // template and logging dependencies into the WASM-linked path. func isValidCustomElementTag(tag string) bool { if tag == "" || !strings.Contains(tag, "-") { return false } if tag[0] < 'a' || tag[0] > 'z' { return false } if _, reserved := reservedCustomElementTags[tag]; reserved { return false } for i := range len(tag) { ch := tag[i] switch { case ch >= 'a' && ch <= 'z': case ch >= '0' && ch <= '9': case ch == '-' || ch == '.' || ch == '_': default: return false } } return true } // cmd/wasm/components.go: tagToClassName converts a kebab-case tag into a // PascalCase class name. // Example: tagToClassName("nav-bar") returns NavBar. func tagToClassName(tag string) string { var b strings.Builder for part := range strings.SplitSeq(tag, "-") { if len(part) == 0 { continue } b.WriteString(strings.ToUpper(part[:1])) b.WriteString(part[1:]) } return b.String() } // cmd/wasm/components.go: jsStringLiteral returns a quoted JavaScript string literal. func jsStringLiteral(s string) string { return strconv.Quote(s) } // cmd/wasm/components.go: customElementClassSource returns a JavaScript class // expression that mirrors the codegen bundle's closed-shadow custom element // behaviour. func customElementClassSource(tag, slot string) string { className := tagToClassName(tag) return "class " + className + " extends HTMLElement {" + "#shadow;" + "constructor(){super();this.#shadow=this.attachShadow({mode:\"closed\"});}" + "connectedCallback(){this.#shadow.textContent=\"\";const slot=this.getAttribute(\"data-slot\")||" + jsStringLiteral(slot) + ";" + "this.dispatchEvent(new CustomEvent(\"wc-ready\",{detail:{tag:" + jsStringLiteral(tag) + ",slot},bubbles:true,composed:true}));}" + "render(html){const tpl=document.createElement(\"template\");tpl.insertAdjacentHTML(\"afterbegin\",html);" + "this.#shadow.textContent=\"\";this.#shadow.appendChild(tpl.content.cloneNode(true));}" + "}" }