Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save j-manu/3d397d03fe91806ed2d0c2e1b86d478f to your computer and use it in GitHub Desktop.
Save j-manu/3d397d03fe91806ed2d0c2e1b86d478f to your computer and use it in GitHub Desktop.
import { Controller } from '@hotwired/stimulus'
export default class extends Controller {
connect () {
this.element.addEventListener('change', this.handleChange.bind(this))
}
handleChange (event) {
this.traverseDown(event.target, event.target.checked)
this.dispatch('change', {
detail: { element: event.target }
})
}
traverseDown (element, checked) {
let rootElement
// if a <summary> is closer than a <details>, we're in a category summary and need to toggle all children
if (element.closest('details').contains(element.closest('summary'))) {
rootElement = element.closest('details')
}
if (rootElement) {
for (const checkbox of rootElement.querySelectorAll(
':scope > :not(summary) input[type=checkbox]'
)) {
checkbox.checked = checked
checkbox.indeterminate = false
}
}
// else we're in a leaf
this.traverseUp(element)
}
traverseUp (element) {
const closestDetails = element.closest('details')
const childCheckboxes = [
...closestDetails.querySelectorAll(
':scope > :not(summary) input[type=checkbox]'
)
]
const rootCheckbox = closestDetails.querySelector(
':scope > summary input[type=checkbox]'
)
if (childCheckboxes.every(element => element.checked)) {
rootCheckbox.checked = true
rootCheckbox.indeterminate = false
} else if (childCheckboxes.every(element => !element.checked)) {
rootCheckbox.checked = false
rootCheckbox.indeterminate = false
} else {
rootCheckbox.checked = false
rootCheckbox.indeterminate = true
}
const rootDetails =
element.tagName === 'DETAILS'
? element.parentElement.closest('details')
: element.closest('details')
if (rootDetails && rootDetails != element) this.traverseUp(rootDetails)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment