@govbr-ds/webcomponents
Version:
Biblioteca de Web Components baseado no GovBR-DS
260 lines (223 loc) • 9.15 kB
JavaScript
const CONTENT_ELEMENT = document.getElementById('content')
const COMPONENTS_FILTER = document.getElementById('components-filter')
const CLEAR_COMPONENTS_FILTER = document.getElementById('clear-components-filter')
let renderedFilesContent = {}
let currentLoadedPath = null
function removeRelatedScripts() {
const existing = document.querySelectorAll('script[data-related-script="true"]')
existing.forEach((el) => el.parentNode && el.parentNode.removeChild(el))
}
function getBaseUrl() {
// Se o Pages injetou um prefixo conhecido, usa-o (terminado com /)
if (typeof window.WWW_PREFIX === 'string' && window.WWW_PREFIX.length > 0) {
return window.WWW_PREFIX.replace(/\/+$/, '')
}
const baseUrl = window.location.pathname.split('/').slice(0, -1).join('/')
return baseUrl === '' ? '' : baseUrl
}
function loadPage(path, files) {
CONTENT_ELEMENT.innerHTML = ''
renderedFilesContent = {}
if (COMPONENTS_FILTER) COMPONENTS_FILTER.value = '' // Limpa o campo de busca ao trocar de página
if (CLEAR_COMPONENTS_FILTER) CLEAR_COMPONENTS_FILTER.style.display = 'none' // Esconde o botão de limpar
const baseUrl = getBaseUrl()
// Remove scripts relacionados anteriores para evitar duplicação
removeRelatedScripts()
const promises = files.map((file) =>
fetch(`${baseUrl}/pages/components/${path}/${file}.html`).then((response) =>
response.text().then((text) => ({ file, text }))
)
)
Promise.all(promises)
.then((results) => {
if (results.length > 0) {
results.forEach(({ file, text }) => {
const contentDiv = document.createElement('div')
contentDiv.classList.add('file', 'mt-2', 'mb-5')
const titleHTML = `<h3>${file}</h3><div class="br-divider"></div>`
contentDiv.insertAdjacentHTML('beforeend', titleHTML + text)
renderedFilesContent[file] = { content: text.toLowerCase(), element: contentDiv }
CONTENT_ELEMENT.appendChild(contentDiv)
})
}
const relatedScript = document.createElement('script')
relatedScript.src = `${getBaseUrl()}/pages/components/${path}/index.js`
relatedScript.setAttribute('data-related-script', 'true')
relatedScript.id = `related-script-${path}`
document.body.appendChild(relatedScript)
currentLoadedPath = path
})
.catch((error) => {
console.log('Erro ao carregar a página:', error)
})
}
function loadMarkdown(path) {
CONTENT_ELEMENT.innerHTML = ''
renderedFilesContent = {}
if (COMPONENTS_FILTER) COMPONENTS_FILTER.value = '' // Limpa o campo de busca ao trocar de página
if (CLEAR_COMPONENTS_FILTER) CLEAR_COMPONENTS_FILTER.style.display = 'none' // Esconde o botão de limpar
const baseUrl = getBaseUrl()
// Remove scripts relacionados anteriores para evitar duplicação
removeRelatedScripts()
fetch(`${baseUrl}/assets/stencil-generated-docs/${path}.md`)
.then((response) => response.text())
.then((text) => {
const contentDiv = document.createElement('div')
contentDiv.classList.add('file', 'mt-2', 'mb-5')
contentDiv.innerHTML = marked.parse(text)
CONTENT_ELEMENT.appendChild(contentDiv)
currentLoadedPath = path
renderedFilesContent[path] = { content: contentDiv.innerHTML.toLowerCase(), element: contentDiv }
})
.catch((error) => {
console.error('Erro ao carregar a página:', error)
})
}
function filterContent() {
const filterValue = (COMPONENTS_FILTER?.value || '').toLowerCase()
const normalizedFilterValue = filterValue.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
Object.values(renderedFilesContent).forEach(({ content, element }) => {
const titleElement = element.querySelector('h3')
const titleText = titleElement ? titleElement.textContent.toLowerCase() : ''
const normalizedTitleText = titleText.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
// Inicializa o conteúdo do shadow DOM para componentes personalizados
let shadowContent = ''
// Função para coletar o conteúdo dos slots de um custom element
const collectSlotContent = (customElement) => {
if (customElement.shadowRoot) {
const slots = customElement.shadowRoot.querySelectorAll('slot')
slots.forEach((slot) => {
const assignedNodes = slot.assignedNodes()
assignedNodes.forEach((node) => {
shadowContent += node.textContent.toLowerCase() + ' '
})
})
}
}
// Procura todos os custom elements dentro do elemento
const customElements = element.querySelectorAll('*')
customElements.forEach((customElement) => {
if (customElement.tagName.includes('-')) {
// Identifica se é um custom element
collectSlotContent(customElement)
}
})
// Filtra o conteúdo
const combinedContent = content + ' ' + shadowContent // Combina conteúdo e shadow content
if (element.querySelector('table')) {
const rows = element.querySelectorAll('table tr')
rows.forEach((row, index) => {
if (index === 0) {
row.style.display = ''
} else {
const rowText = row.textContent
.toLowerCase()
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
row.style.display = rowText.includes(normalizedFilterValue) ? '' : 'none'
}
})
} else if (
normalizedFilterValue &&
(combinedContent.includes(normalizedFilterValue) || normalizedTitleText.includes(normalizedFilterValue))
) {
element.style.display = '' // Exibe o elemento se o conteúdo combinado inclui o valor filtrado
} else {
element.style.display = normalizedFilterValue ? 'none' : '' // Oculta se não houver filtro
}
})
if (CLEAR_COMPONENTS_FILTER) CLEAR_COMPONENTS_FILTER.style.display = filterValue ? 'block' : 'none'
}
// Liga eventos apenas se os elementos existirem
if (COMPONENTS_FILTER) {
COMPONENTS_FILTER.addEventListener('input', filterContent)
}
if (CLEAR_COMPONENTS_FILTER) {
CLEAR_COMPONENTS_FILTER.addEventListener('click', () => {
if (COMPONENTS_FILTER) COMPONENTS_FILTER.value = ''
filterContent()
})
}
function loadPageFromURL() {
const baseUrl = getBaseUrl()
const currentPath = window.location.pathname.replace(baseUrl, '').split('/').filter(Boolean)[0] || ''
// Evita recarregar a mesma rota em chamadas repetidas
if (currentPath === currentLoadedPath) {
return
}
const menuItems = Array.isArray(window.MENU_ITEMS) ? window.MENU_ITEMS : []
const menuBody = typeof window.MENU_BODY !== 'undefined' ? window.MENU_BODY : null
// Remove seleções antigas
if (menuBody) Array.from(menuBody.children).forEach((item) => item.classList.remove('active'))
menuItems.forEach((page) => {
if (!page || !page.path) return
const link = menuBody
? Array.from(menuBody.children).find((item) => item.getAttribute('href') === `/${page.path}`)
: null
if (page.path === currentPath) {
if (link) {
link.classList.add('active')
}
if (Array.isArray(page.files)) {
loadPage(page.path, page.files)
}
}
})
const staticItems = window.MENU_STATIC_ITEMS
if (Array.isArray(staticItems)) {
staticItems.forEach((page) => {
if (!page || !page.path) return
const link = menuBody
? Array.from(menuBody.children).find((item) => item.getAttribute('href') === `/${page.path}`)
: null
if (page.path === currentPath) {
if (link) {
link.classList.add('active')
}
loadMarkdown(page.path)
}
})
}
if (currentPath.trim().length === 0 && menuItems.length > 0) {
const firstPage = menuItems.find((p) => p && p.path && Array.isArray(p.files))
if (firstPage) {
loadPage(firstPage.path, firstPage.files)
const firstLink = menuBody ? menuBody.querySelector('.menu-item') : null
if (firstLink) {
firstLink.classList.add('active')
window.history.pushState({}, '', `${baseUrl}/${firstPage.path}`)
}
}
}
}
// Aguarda DOM e dados do menu antes de tentar carregar a rota atual
function isMenuReady() {
const hasItems =
(Array.isArray(window.MENU_ITEMS) && window.MENU_ITEMS.length > 0) ||
(Array.isArray(window.MENU_STATIC_ITEMS) && window.MENU_STATIC_ITEMS.length > 0)
return !!CONTENT_ELEMENT && hasItems
}
function bootstrapRouting(attempt = 0) {
const maxAttempts = 200 // ~10s com 50ms
if (!isMenuReady()) {
if (attempt < maxAttempts) {
setTimeout(() => bootstrapRouting(attempt + 1), 50)
} else {
// Última tentativa mesmo sem menu (evita ficar em branco caso os itens não sejam necessários)
try {
loadPageFromURL()
} catch (err) {
console.warn('Falha ao carregar rota sem menu pronto:', err)
}
}
return
}
loadPageFromURL()
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => bootstrapRouting())
} else {
bootstrapRouting()
}
// Garante atualização ao navegar com back/forward
window.addEventListener('popstate', () => bootstrapRouting())