logo hsb.horse
← Voltar para o índice de snippets

Snippets

Clonar elemento UI nativo para herdar estilos

Um padrão leve que mantém consistência visual clonando botões existentes da aplicação host com cloneNode, substituindo apenas o ícone e texto. Sem necessidade de lutar com CSS ofuscado.

Publicado: Atualizado:

Em extensões de navegador e UIs incorporadas para portais corporativos, replicar o sistema de design da aplicação host é desafiador. Em vez de perseguir nomes de classes ofuscados ou estilos específicos de frameworks, clonar elementos nativos existentes e substituir apenas o necessário proporciona consistência visual “gratuitamente”.

Código

interface CloneButtonOptions {
/** Seletor para o elemento template a ser clonado */
templateSelector: string
/** Novo texto para substituir */
text: string
/** Novo ícone para substituir (opcional) */
icon?: string | HTMLElement
/** Manipulador de clique */
onClick: (event: MouseEvent) => void
}
/**
* Clonar um botão existente da aplicação host e gerar um novo botão
*/
export const cloneNativeButton = (
options: CloneButtonOptions
): HTMLElement | null => {
const { templateSelector, text, icon, onClick } = options
// Obter o elemento template
const template = document.querySelector<HTMLElement>(templateSelector)
if (!template) {
console.warn(`Template not found: ${templateSelector}`)
return null
}
// Criar um clone profundo (excluindo manipuladores de eventos)
const cloned = template.cloneNode(true) as HTMLElement
// Substituir nó de texto
const textNode = findTextNode(cloned)
if (textNode) {
textNode.textContent = text
}
// Substituir ícone (se especificado)
if (icon) {
const iconElement = findIconElement(cloned)
if (iconElement) {
const newIcon = typeof icon === 'string'
? createIconFromString(icon)
: icon
iconElement.replaceWith(newIcon)
}
}
// Ouvintes de eventos não são clonados, então anexar novos
cloned.addEventListener('click', onClick)
// Remover ID se presente (evitar duplicatas)
if (cloned.id) {
cloned.removeAttribute('id')
}
return cloned
}
/**
* Encontrar o primeiro nó de texto dentro do elemento
*/
const findTextNode = (element: HTMLElement): Text | null => {
const walker = document.createTreeWalker(
element,
NodeFilter.SHOW_TEXT,
{
acceptNode: (node) => {
// Excluir nós apenas com espaços em branco
return node.textContent?.trim()
? NodeFilter.FILTER_ACCEPT
: NodeFilter.FILTER_REJECT
}
}
)
return walker.nextNode() as Text | null
}
/**
* Encontrar o primeiro elemento de ícone (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"]')
)
}
/**
* Criar um elemento de ícone a partir de uma string HTML
*/
const createIconFromString = (html: string): HTMLElement => {
const template = document.createElement('template')
template.innerHTML = html.trim()
return template.content.firstElementChild as HTMLElement
}

Uso

// Reutilizar o estilo de botão existente do GitHub
const myButton = cloneNativeButton({
templateSelector: '.Button--primary',
text: 'Deploy',
icon: '<svg>...</svg>',
onClick: () => {
console.log('Deploying...')
}
})
// Adicionar ao DOM
document.querySelector('.toolbar')?.appendChild(myButton)

Como Funciona

  1. Usar querySelector para obter o elemento template a ser clonado
  2. Criar uma cópia profunda com cloneNode(true) (inclui elementos filhos)
  3. Percorrer nós de texto com TreeWalker e substituí-los
  4. Encontrar elementos de ícone com querySelector e substituí-los
  5. Ouvintes de eventos não são clonados, então anexar novos com addEventListener
  6. Remover o atributo id se presente para evitar duplicatas

Note que cloneNode preserva estilos, classes e atributos data, mas ouvintes de eventos não são copiados.

Benefícios

  • Consistência Visual: Herda naturalmente o sistema de design da aplicação host
  • CSS Não Necessário: Não é preciso perseguir nomes de classes ofuscados ou estilos de framework
  • Manutenível: Segue automaticamente atualizações de design do host
  • Leve: Implementado apenas com APIs DOM, sem dependências

Ressalvas

cloneNode copia o estado atual do DOM, então classes ou estilos que mudam dinamicamente (como estados de hover) não são clonados. Além disso, se o elemento template for removido, essa abordagem para de funcionar, então inclua verificações de existência.

Aplicações

  • Adicionar botões com consistência visual em extensões de navegador
  • Incorporar funcionalidades que se mesclam naturalmente em sistemas de design complexos de portais corporativos
  • Inserir painéis de operação personalizados em dashboards de terceiros