Files
fizzy/app/javascript/controllers/collapsible_columns_controller.js
T
Andy Smith 8280be59c5 Merge branch 'main' into sticky-column-headers-using-fixed
* main:
  Order of concerns
  Redirect to root to avoid changing the path by default
  Make sure that only admins or card creators can delete cards
  Make sure only admins or collection creators can toggle the individual access settings
  Make sure only admins or collection creators can toggle the all access setting
  Update seeds from "Collection" to "Board"
  Rename concern
  Fix: constant name
  Rename script I did not mean to commit
  Support old public collection urls
  Support old collection urls
  Support old events in the timeline
  Invalidate HTTP cache
  Support current events after renaming
  Replace collection with board in the code, tests green
  Baseline replacing collection with board across code
2025-11-05 12:40:19 -06:00

137 lines
3.4 KiB
JavaScript

import { Controller } from "@hotwired/stimulus"
import { nextFrame, debounce } from "helpers/timing_helpers";
export default class extends Controller {
static classes = [ "collapsed", "noTransitions", "titleNotVisible" ]
static targets = [ "column", "button", "title" ]
static values = {
board: String
}
initialize() {
this.restoreState = debounce(this.restoreState.bind(this), 10)
}
async connect() {
await this.#restoreColumnsDisablingTransitions()
this.#setupIntersectionObserver()
}
disconnect() {
if (this._intersectionObserver) {
this._intersectionObserver.disconnect()
this._intersectionObserver = null
}
}
toggle({ target }) {
const column = target.closest('[data-collapsible-columns-target="column"]')
this.#toggleColumn(column);
}
preventToggle(event) {
if (event.target.hasAttribute("data-collapsible-columns-target") && event.detail.attributeName === "class") {
event.preventDefault()
}
}
async restoreState(event) {
await nextFrame()
await this.#restoreColumnsDisablingTransitions()
}
async #restoreColumnsDisablingTransitions() {
this.#disableTransitions()
this.#restoreColumns()
await nextFrame()
this.#enableTransitions()
}
#disableTransitions() {
this.element.classList.add(this.noTransitionsClass)
}
#enableTransitions() {
this.element.classList.remove(this.noTransitionsClass)
}
#toggleColumn(column) {
this.#collapseAllExcept(column)
if (this.#isCollapsed(column)) {
this.#expand(column)
} else {
this.#collapse(column)
}
}
#collapseAllExcept(clickedColumn) {
this.columnTargets.forEach(column => {
if (column !== clickedColumn) {
this.#collapse(column)
}
})
}
#isCollapsed(column) {
return column.classList.contains(this.collapsedClass)
}
#collapse(column) {
const key = this.#localStorageKeyFor(column)
this.#buttonFor(column).setAttribute("aria-expanded", "false")
column.classList.add(this.collapsedClass)
localStorage.removeItem(key)
}
#expand(column) {
const key = this.#localStorageKeyFor(column)
this.#buttonFor(column).setAttribute("aria-expanded", "true")
column.classList.remove(this.collapsedClass)
localStorage.setItem(key, true)
}
#buttonFor(column) {
return this.buttonTargets.find(button => column.contains(button))
}
#restoreColumns() {
this.columnTargets.forEach(column => {
this.#restoreColumn(column)
})
}
#restoreColumn(column) {
const key = this.#localStorageKeyFor(column)
if (localStorage.getItem(key)) {
this.#expand(column)
}
}
#localStorageKeyFor(column) {
return `expand-${this.boardValue}-${column.getAttribute("id")}`
}
#setupIntersectionObserver() {
if (typeof IntersectionObserver === "undefined") return
if (this._intersectionObserver) this._intersectionObserver.disconnect()
this._intersectionObserver = new IntersectionObserver(entries => {
entries.forEach(entry => {
const title = entry.target
const column = title.closest(".cards")
if (!column) return
const offscreen = entry.intersectionRatio === 0
column.classList.toggle(this.titleNotVisibleClass, offscreen)
})
}, { threshold: [0] })
this.titleTargets.forEach(title => this._intersectionObserver.observe(title))
}
}