logo hsb.horse
← Retour à l’index des snippets

Snippets

Cloner un élément UI natif pour hériter des styles

Un pattern léger qui maintient la cohérence visuelle en clonant les boutons existants de l'application hôte avec cloneNode, puis en remplaçant uniquement l'icône et le texte. Pas besoin de se battre avec du CSS obfusqué.

Publié: Mis à jour:

Dans les extensions de navigateur et les UI intégrées pour les portails d’entreprise, reproduire le système de design de l’application hôte est difficile. Plutôt que de poursuivre des noms de classes obfusqués ou des styles spécifiques à un framework, cloner des éléments natifs existants et ne remplacer que le nécessaire vous donne une cohérence visuelle “gratuitement.”

Code

interface CloneButtonOptions {
/** Sélecteur de l'élément template à cloner */
templateSelector: string
/** Nouveau texte à remplacer */
text: string
/** Nouvelle icône à remplacer (optionnel) */
icon?: string | HTMLElement
/** Gestionnaire de clic */
onClick: (event: MouseEvent) => void
}
/**
* Cloner un bouton existant de l'application hôte et générer un nouveau bouton
*/
export const cloneNativeButton = (
options: CloneButtonOptions
): HTMLElement | null => {
const { templateSelector, text, icon, onClick } = options
// Récupérer l'élément template
const template = document.querySelector<HTMLElement>(templateSelector)
if (!template) {
console.warn(`Template not found: ${templateSelector}`)
return null
}
// Créer un clone profond (excluant les gestionnaires d'événements)
const cloned = template.cloneNode(true) as HTMLElement
// Remplacer le nœud de texte
const textNode = findTextNode(cloned)
if (textNode) {
textNode.textContent = text
}
// Remplacer l'icône (si spécifié)
if (icon) {
const iconElement = findIconElement(cloned)
if (iconElement) {
const newIcon = typeof icon === 'string'
? createIconFromString(icon)
: icon
iconElement.replaceWith(newIcon)
}
}
// Les écouteurs d'événements ne sont pas clonés, donc en attacher de nouveaux
cloned.addEventListener('click', onClick)
// Supprimer l'ID si présent (éviter les doublons)
if (cloned.id) {
cloned.removeAttribute('id')
}
return cloned
}
/**
* Trouver le premier nœud de texte dans l'élément
*/
const findTextNode = (element: HTMLElement): Text | null => {
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
{
acceptNode: (node) => {
// Exclure les nœuds contenant uniquement des espaces
return node.textContent?.trim()
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT
}
}
)
return walker.nextNode() as Text | null
}
/**
* Trouver le premier élément d'icône (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"]')
)
}
/**
* Créer un élément d'icône à partir d'une chaîne HTML
*/
const createIconFromString = (html: string): HTMLElement => {
const template = document.createElement('template')
template.innerHTML = html.trim()
return template.content.firstElementChild as HTMLElement
}

Utilisation

// Réutiliser le style de bouton existant de GitHub
const myButton = cloneNativeButton({
templateSelector: '.Button--primary',
text: 'Deploy',
icon: '<svg>...</svg>',
onClick: () => {
console.log('Deploying...')
}
})
// Ajouter au DOM
document.querySelector('.toolbar')?.appendChild(myButton)

Comment ça marche

  1. Utiliser querySelector pour récupérer l’élément template à cloner
  2. Créer une copie profonde avec cloneNode(true) (inclut les éléments enfants)
  3. Parcourir les nœuds de texte avec TreeWalker et les remplacer
  4. Trouver les éléments d’icône avec querySelector et les remplacer
  5. Les écouteurs d’événements ne sont pas clonés, donc en attacher de nouveaux avec addEventListener
  6. Supprimer l’attribut id s’il est présent pour éviter les doublons

Notez que cloneNode préserve les styles, classes et attributs data, mais les écouteurs d’événements ne sont pas copiés.

Avantages

  • Cohérence visuelle : Hérite naturellement du système de design de l’application hôte
  • Pas de CSS requis : Pas besoin de poursuivre des noms de classes obfusqués ou des styles de framework
  • Maintenable : Suit automatiquement les mises à jour du design hôte
  • Léger : Implémenté uniquement avec les API DOM, sans dépendances

Mise en garde

cloneNode copie l’état actuel du DOM, donc les classes ou styles qui changent dynamiquement (comme les états de survol) ne sont pas clonés. De plus, si l’élément template est supprimé, cette approche cesse de fonctionner, donc incluez des vérifications d’existence.

Applications

  • Ajouter des boutons avec cohérence visuelle dans les extensions de navigateur
  • Intégrer des fonctionnalités qui se fondent naturellement dans les systèmes de design complexes des portails d’entreprise
  • Insérer des panneaux d’opération personnalisés dans des tableaux de bord tiers