/** * `x-dialog-dividers`, on ``'s body — the one part of a dialog that scrolls: marks the * `` with `data-md-overflow-top` while the body is scrolled away from its top, and * `data-md-overflow-bottom` while more of it is below. resources/css/components/modal.css draws * those as the dividers under the pinned header and over the pinned actions; a body that fits * marks neither. * * The marks go on the `` rather than the body because the dialog is `wire:ignore.self`: a * Livewire morph hands the body back the server's attributes, which would wipe a mark until the * next scroll, but it never touches the dialog's own. * * Measured on scroll, and whenever the body or what is in it changes size: the body when the window * or the dialog does (opening, too — a closed dialog has no size, and the observer reports the * one it opens to), and the element the component wraps around the slot when a morph, an image or * a disclosure makes the content taller or shorter while the body stays at its cap. */ document.addEventListener('alpine:init', () => { window.Alpine.directive('dialog-dividers', (el, _, { cleanup }) => { const dialog = el.closest('dialog') if (!dialog) { return } // A pixel of slack: at a fractional device pixel ratio the end of a scroll can stop a // fraction short of `scrollHeight`, which is rounded. const measure = () => { dialog.toggleAttribute('data-md-overflow-top', el.scrollTop >= 1) dialog.toggleAttribute('data-md-overflow-bottom', el.scrollHeight - el.clientHeight - el.scrollTop >= 1) } const resizes = new ResizeObserver(measure) resizes.observe(el) const content = el.querySelector(':scope > [data-md-modal-content]') if (content) { resizes.observe(content) } el.addEventListener('scroll', measure, { passive: true }) cleanup(() => { resizes.disconnect() el.removeEventListener('scroll', measure) dialog.removeAttribute('data-md-overflow-top') dialog.removeAttribute('data-md-overflow-bottom') }) }) })