import {type Placement, autoUpdate, computePosition, flip, offset, shift, size} from '@floating-ui/dom' import {controllerFactory} from '@utils/createController' import {useClickOutside, useMutation} from 'stimulus-use' export default class OverlayController extends controllerFactory()({ targets: { anchor: null, popover: null, }, values: { clamped: Boolean, placement: String, }, }) { private changedIds = new Set() private clickHandlers: Array<() => void> = [] labels: Array<{el: HTMLLabelElement}> unsubAutoUpdate: (() => void) | undefined private setupClickHandlers() { const cb = () => this.toggle() for (const fn of this.clickHandlers) { fn() } this.clickHandlers = [] for (const el of this.anchorTarget.querySelectorAll('button, [tabindex]:not([tabindex="-1"])')) { el.addEventListener('click', cb) this.clickHandlers.push(() => el.removeEventListener('click', cb)) } } checkboxClicked(e: Event) { const target = e.target as HTMLInputElement const value = target.value if (this.changedIds.has(value)) { this.changedIds.delete(value) } else { this.changedIds.add(value) } this.dispatch('clicked', {detail: value}) } clickOutside() { this.setupAutoUpdate() this.close() } close() { this.element.open = false } connect() { useClickOutside(this) useMutation(this, {childList: true, subtree: true}) this.setupAutoUpdate() this.setupClickHandlers() } disconnect() { this.unsubAutoUpdate?.() } setupAutoUpdate(): void { if (!this.element.open) { this.unsubAutoUpdate?.() return } const updatePopoverPosition = (): void => { void computePosition(this.anchorTarget, this.popoverTarget, { middleware: [offset(6), flip(), shift({padding: 6})], placement: this.placementValue as Placement, strategy: 'fixed', }).then(({x, y}) => { Object.assign(this.popoverTarget.style, { left: `${x}px`, top: `${y}px`, }) }) } updatePopoverPosition() this.unsubAutoUpdate = autoUpdate(this.anchorTarget, this.popoverTarget, updatePopoverPosition) } toggle(): void { this.element.open = !this.element.open this.setupAutoUpdate() } }