Loading Animation
Kryzo Logo

Custom Elements

Der Custom Elements Handler vereinfacht die Erstellung nativer Web Components und stellt eine kompakte API zur Verfügung, um eigene HTML-Elemente innerhalb der Anwendung zu registrieren. Ziel ist es, die Entwicklung wiederverwendbarer Komponenten zu beschleunigen, ohne den vollständigen Boilerplate-Code der nativen CustomElementRegistry manuell implementieren zu müssen. Dabei bleibt Kryzo vollständig auf den nativen Browser-Standards aufgebaut. Es werden keine virtuellen DOM-Strukturen, Compiler oder Framework-Abhängigkeiten verwendet. Die erzeugten Komponenten sind echte Web Components und können überall dort eingesetzt werden, wo Standard-HTML unterstützt wird.

Der Handler übernimmt wiederkehrende Aufgaben wie die Registrierung von Komponenten, die Verarbeitung von Attributänderungen, das Event-Management, die Template-Erzeugung sowie die Einbindung von Stylesheets. Dadurch kann der Fokus vollständig auf die eigentliche Komponentenlogik gelegt werden. Je nach Anwendungsfall können Komponenten mit oder ohne Shadow DOM erstellt werden. Für gekapselte Oberflächen steht ein vollständiger Shadow-DOM-Support inklusive gemeinsam genutzter Stylesheets zur Verfügung. Alternativ können Inhalte direkt im Light DOM gerendert werden, um bestehende CSS-Strukturen oder Legacy-Anwendungen einfacher einzubinden.

Achtung: Wichtige Informationen zu "Verwendung von CustomElements" in ThirdParty Kombination


Initialisierung
document.addEventListener('kryzo:ready', ({ detail: { CUSTOM } }) => { /* Ab hier stellen alle Custom Element Funktionen zur Verfügung */ });
Beispiel 1: Einfacher Badge mit Shadow DOM
/* Verwendung im HTML: */ CUSTOM.register('my-badge', { shadow: true, /* Shadow DOM aktiv (default) */ shadowMode: 'open', /* shadow.host etc. von außen zugreifbar (default) */ /* Beobachtete Attribute – attributeChangedCallback wird nur für diese gefeuert */ attrs: ['label', 'color'], /* CSS wird einmalig als CSSStyleSheet kompiliert und von allen Instanzen geteilt */ css: ` :host { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: 999px; font-size: 12px; font-weight: 600; } :host([color="green"]) { background: #d1fae5; color: #065f46; } :host([color="red"]) { background: #fee2e2; color: #991b1b; } :host([color="blue"]) { background: #dbeafe; color: #1e40af; } span { pointer-events: none; } `, /* template bekommt das attrData-Objekt aller aktuellen Attribute /* template: ({ label = '' }) => `${label}`, /* Events: Key = "eventtyp" oder "eventtyp selector" (delegiert) */ events: { 'click': (e, el) => { console.log('[my-badge] geklickt, label:', el.getAttribute('label')); } }, /* Wird gefeuert wenn eines der attrs[] sich ändert */ onChange: (el, name, oldVal, newVal) => { if (name === 'label') { /* Shadow Root über el.root erreichbar */ const span = el.root.querySelector('span'); if (span) span.textContent = newVal; } if (name === 'color') { el.setAttribute('color', newVal); } } });
Beispiel 2: Karte mit delegierten Events, kein Shadow DOM
/* Verwendung im HTML: */ CUSTOM.register('my-card', { shadow: false, /* Kein Shadow DOM – template landet direkt im Light DOM */ css: ` my-card { display: block; border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px; } my-card h2 { margin: 0 0 8px; font-size: 16px; } my-card button { margin-top: 12px; cursor: pointer; } `, template: ({ title = 'Karte' }) => `

${title}

`, events: { /* Delegiert: feuert nur wenn e.target .card-btn matcht */ 'click .card-btn': (e, el) => { console.log('[my-card] Button geklickt in:', el.getAttribute('title')); }, /* Direkt auf dem Host-Element */ 'mouseenter': (e, el) => { el.style.boxShadow = '0 4px 12px rgba(0,0,0,.1)'; }, 'mouseleave': (e, el) => { el.style.boxShadow = ''; } } });
Beispiel 3: Interaktiver Counter – State im Element
/* Verwendung im HTML: */ CUSTOM.register('my-counter', { shadow: true, attrs: ['start', 'step'], css: ` :host { display: inline-flex; align-items: center; gap: 8px; } button { width: 28px; height: 28px; border: 1px solid #d1d5db; border-radius: 6px; cursor: pointer; background: #fff; } button:hover { background: #f3f4f6; } span { min-width: 32px; text-align: center; font-variant-numeric: tabular-nums; } `, template: ({ start = '0' }) => ` ${start} `, events: { 'click .inc': (e, el) => { const step = Number(el.getAttribute('step') ?? 1); const span = el.root.querySelector('span'); span.textContent = Number(span.textContent) + step; }, 'click .dec': (e, el) => { const step = Number(el.getAttribute('step') ?? 1); const span = el.root.querySelector('span'); span.textContent = Number(span.textContent) - step; } }, onChange: (el, name, oldVal, newVal) => { /* start-Attribut zur Laufzeit geändert → Anzeige zurücksetzen */ if (name === 'start') { const span = el.root.querySelector('span'); if (span) span.textContent = newVal; } } });

Chaining

/* register() gibt this zurück → alle Registrierungen in einem Block möglich */ CUSTOM .register('ui-tag-a', { template: 'A' }) .register('ui-tag-b', { template: 'B' }) .register('ui-tag-c', { template: 'C' });
Zugriff auf el.root von außen (shadowMode: 'open')
const badge = document.querySelector('my-badge'); badge.__ready.then(() => { /* __ready resolved nach connectedCallback */ const span = badge.root.querySelector('span'); console.log('[my-badge] aktueller Text:', span?.textContent); /* Attribut ändern → onChange wird gefeuert */ badge.setAttribute('label', 'Geändert'); badge.setAttribute('color', 'red'); });