In Browser-Erweiterungen und eingebetteten UIs für Unternehmensportale ist es schwierig, das Design-System der Host-App nachzubilden. Anstatt obfuskierte Klassennamen oder Framework-spezifische Stile zu verfolgen, erhält man durch Klonen bestehender nativer Elemente und Ersetzen nur des Notwendigen visuelle Konsistenz „kostenlos”.
Code
interface CloneButtonOptions { /** Selektor für das zu klonende Template-Element */ templateSelector: string /** Neuer Text zum Ersetzen */ text: string /** Neues Icon zum Ersetzen (optional) */ icon?: string | HTMLElement /** Click-Handler */ onClick: (event: MouseEvent) => void}
/** * Einen bestehenden Button der Host-App klonen und einen neuen Button generieren */export const cloneNativeButton = ( options: CloneButtonOptions): HTMLElement | null => { const { templateSelector, text, icon, onClick } = options
// Template-Element abrufen const template = document.querySelector<HTMLElement>(templateSelector) if (!template) { console.warn(`Template not found: ${templateSelector}`) return null }
// Tiefen Klon erstellen (ohne Event-Handler) const cloned = template.cloneNode(true) as HTMLElement
// Textknoten ersetzen const textNode = findTextNode(cloned) if (textNode) { textNode.textContent = text }
// Icon ersetzen (falls angegeben) if (icon) { const iconElement = findIconElement(cloned) if (iconElement) { const newIcon = typeof icon === 'string' ? createIconFromString(icon) : icon iconElement.replaceWith(newIcon) } }
// Event-Listener werden nicht geklont, also neue anhängen cloned.addEventListener('click', onClick)
// ID entfernen, falls vorhanden (Duplikate vermeiden) if (cloned.id) { cloned.removeAttribute('id') }
return cloned}
/** * Den ersten Textknoten innerhalb des Elements finden */const findTextNode = (element: HTMLElement): Text | null => { const walker = document.createTreeWalker( element, NodeFilter.SHOW_TEXT, { acceptNode: (node) => { // Nur-Leerzeichen-Knoten ausschließen return node.textContent?.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT } } ) return walker.nextNode() as Text | null}
/** * Das erste Icon-Element finden (svg, img, i, etc.) */const findIconElement = (element: HTMLElement): Element | null => { return ( element.querySelector('svg') || element.querySelector('img') || element.querySelector('i[class*="icon"]') || element.querySelector('[role="img"]') )}
/** * Ein Icon-Element aus einem HTML-String erstellen */const createIconFromString = (html: string): HTMLElement => { const template = document.createElement('template') template.innerHTML = html.trim() return template.content.firstElementChild as HTMLElement}Verwendung
// GitHubs bestehenden Button-Stil wiederverwendenconst myButton = cloneNativeButton({ templateSelector: '.Button--primary', text: 'Deploy', icon: '<svg>...</svg>', onClick: () => { console.log('Deploying...') }})
// Zum DOM hinzufügendocument.querySelector('.toolbar')?.appendChild(myButton)Funktionsweise
querySelectorverwenden, um das zu klonende Template-Element abzurufen- Eine tiefe Kopie mit
cloneNode(true)erstellen (inkl. Kindelemente) - Textknoten mit
TreeWalkerdurchlaufen und ersetzen - Icon-Elemente mit
querySelectorfinden und ersetzen - Event-Listener werden nicht geklont, also neue mit
addEventListeneranhängen - Das
id-Attribut entfernen, falls vorhanden, um Duplikate zu vermeiden
Beachten Sie, dass cloneNode Stile, Klassen und Data-Attribute bewahrt, Event-Listener jedoch nicht kopiert werden.
Vorteile
- Visuelle Konsistenz: Erbt natürlich das Design-System der Host-App
- Kein CSS erforderlich: Keine Notwendigkeit, obfuskierte Klassennamen oder Framework-Stile zu verfolgen
- Wartbar: Folgt automatisch Design-Updates des Hosts
- Leichtgewichtig: Nur mit DOM-APIs implementiert, keine Abhängigkeiten
Einschränkungen
cloneNode kopiert den aktuellen Zustand des DOM, sodass sich dynamisch ändernde Klassen oder Stile (wie Hover-Zustände) nicht geklont werden. Wenn das Template-Element entfernt wird, funktioniert dieser Ansatz nicht mehr. Daher sollten Existenzprüfungen eingebaut werden.
Anwendungsfälle
- Buttons mit visueller Konsistenz in Browser-Erweiterungen hinzufügen
- Features einbetten, die sich natürlich in komplexe Design-Systeme von Unternehmensportalen einfügen
- Benutzerdefinierte Bedienpanels in Drittanbieter-Dashboards einfügen
hsb.horse