1import { Controller } from "@hotwired/stimulus" 2 3// Cabecalho que acompanha a rolagem numa tabela larga demais para caber na tela. Quem gruda 4// nao e o `<thead>` original: uma copia dele vai para um bloco de posicao fixa, com a 5// largura de cada coluna medida na tabela real toda vez que aparece. 6// 7// `position: sticky` no proprio `<thead>` nao resolve o caso: a tabela tambem rola na 8// horizontal, dentro de um container com `overflow`, e o cabecalho preso acompanharia a 9// rolagem vertical da pagina mas nao a horizontal do container â as colunas sairiam do 10// lugar. A copia e reposicionada a cada exibicao justamente por isso. 11export default class extends Controller { 12 static values = { topo: { type: Number, default: 64 } } 13 14 connect() { 15 this.tabela = this.element.querySelector("table") 16 this.cabecalho = this.tabela?.querySelector("thead") 17 if (!this.cabecalho) return 18 19 this.#montarCopia() 20 21 this.aoRolar = () => this.#posicionar() 22 this.aoRedimensionar = () => this.#visivel() && this.#medir() 23 24 window.addEventListener("scroll", this.aoRolar, { passive: true }) 25 window.addEventListener("resize", this.aoRedimensionar) 26 this.#posicionar() 27 } 28 29 disconnect() { 30 if (!this.cabecalho) return 31 32 window.removeEventListener("scroll", this.aoRolar) 33 window.removeEventListener("resize", this.aoRedimensionar) 34 this.copia?.remove() 35 } 36 37 #montarCopia() { 38 this.copiaCabecalho = this.cabecalho.cloneNode(true) 39 40 this.copiaTabela = document.createElement("table") 41 this.copiaTabela.className = this.tabela.className 42 this.copiaTabela.appendChild(this.copiaCabecalho) 43 44 // Fora do container que rola na horizontal: preso la dentro, o bloco fixo seria 45 // recortado pelo `overflow`. 46 this.copia = document.createElement("div") 47 this.copia.className = "sticky-thead-clone" 48 this.copia.style.top = `${this.topoValue}px` 49 this.copia.appendChild(this.copiaTabela) 50 document.body.appendChild(this.copia) 51 } 52 53 #posicionar() { 54 const cabecalho = this.cabecalho.getBoundingClientRect() 55 const tabela = this.tabela.getBoundingClientRect() 56 const passou = cabecalho.top < this.topoValue && tabela.bottom > this.topoValue 57 58 if (passou) this.#medir() 59 this.copia.style.display = passou ? "block" : "none" 60 } 61 62 #medir() { 63 const linhas = this.copiaCabecalho.querySelectorAll("tr") 64 65 this.cabecalho.querySelectorAll("tr").forEach((linha, indice) => { 66 const copias = linhas[indice]?.querySelectorAll("th") 67 if (!copias) return 68 69 linha.querySelectorAll("th").forEach((celula, coluna) => { 70 const copia = copias[coluna] 71 if (!copia) return 72 73 const largura = `${celula.getBoundingClientRect().width}px` 74 Object.assign(copia.style, { width: largura, minWidth: largura, maxWidth: largura, boxSizing: "border-box" }) 75 }) 76 }) 77 78 const tabela = this.tabela.getBoundingClientRect() 79 this.copia.style.left = `${tabela.left}px` 80 this.copia.style.width = `${tabela.width}px` 81 this.copiaTabela.style.width = `${tabela.width}px` 82 } 83 84 #visivel() { 85 return this.copia.style.display === "block" 86 } 87}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.