Generates Custom Element v1 classes with closed Shadow DOM from manifest slot assignments. Includes tag validation, PascalCase conversion, and bundle generation for full HLCRF layout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
90 lines
2.4 KiB
Go
90 lines
2.4 KiB
Go
package codegen
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"text/template"
|
|
)
|
|
|
|
// 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.
|
|
func GenerateClass(tag, slot string) (string, error) {
|
|
if !strings.Contains(tag, "-") {
|
|
return "", fmt.Errorf("codegen: custom element tag %q must contain a hyphen", tag)
|
|
}
|
|
var b strings.Builder
|
|
err := wcTemplate.Execute(&b, struct {
|
|
ClassName, Tag, Slot string
|
|
}{
|
|
ClassName: TagToClassName(tag),
|
|
Tag: tag,
|
|
Slot: slot,
|
|
})
|
|
if err != nil {
|
|
return "", fmt.Errorf("codegen: template exec: %w", err)
|
|
}
|
|
return b.String(), nil
|
|
}
|
|
|
|
// GenerateRegistration produces the customElements.define() call.
|
|
func GenerateRegistration(tag, className string) string {
|
|
return fmt.Sprintf(`customElements.define("%s", %s);`, tag, className)
|
|
}
|
|
|
|
// TagToClassName converts a kebab-case tag to PascalCase class name.
|
|
func TagToClassName(tag string) string {
|
|
parts := strings.Split(tag, "-")
|
|
var b strings.Builder
|
|
for _, p := range parts {
|
|
if len(p) > 0 {
|
|
b.WriteString(strings.ToUpper(p[:1]))
|
|
b.WriteString(p[1:])
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
// GenerateBundle produces all WC class definitions and registrations
|
|
// for a set of HLCRF slot assignments.
|
|
func GenerateBundle(slots map[string]string) (string, error) {
|
|
seen := make(map[string]bool)
|
|
var b strings.Builder
|
|
|
|
for slot, tag := range slots {
|
|
if seen[tag] {
|
|
continue
|
|
}
|
|
seen[tag] = true
|
|
|
|
cls, err := GenerateClass(tag, slot)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
b.WriteString(cls)
|
|
b.WriteByte('\n')
|
|
b.WriteString(GenerateRegistration(tag, TagToClassName(tag)))
|
|
b.WriteByte('\n')
|
|
}
|
|
return b.String(), nil
|
|
}
|