go-html/cmd/wasm/components.go
Virgil 799317b788
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
feat(codegen): reject reserved custom element tags
Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-04 01:31:38 +00:00

83 lines
2.5 KiB
Go

// 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));}" +
"}"
}