go-html/cmd/wasm/components.go
Virgil 967182a676
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
feat(wasm): add component registration export
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-03 20:13:14 +00:00

66 lines
2 KiB
Go

// SPDX-Licence-Identifier: EUPL-1.2
package main
import (
"strconv"
"strings"
)
var canonicalSlotOrder = []string{"H", "L", "C", "R", "F"}
// 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
}
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
}
// tagToClassName converts a kebab-case tag into a PascalCase class name.
// Example: nav-bar -> 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()
}
// jsStringLiteral returns a quoted JavaScript string literal.
func jsStringLiteral(s string) string {
return strconv.Quote(s)
}
// 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 {" +
"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}}));}" +
"render(html){const tpl=document.createElement(\"template\");tpl.insertAdjacentHTML(\"afterbegin\",html);" +
"this._shadow.textContent=\"\";this._shadow.appendChild(tpl.content.cloneNode(true));}" +
"}"
}