Fix flaky photo gallery carousel tests and refactor overlays
* Fixed a race condition in `photo-carousel` where programmatic scrolling
(e.g., keyboard navigation) would conflict with `IntersectionObserver`
callbacks, causing the current photo to revert mid-scroll. Added an
`isProgrammaticScroll` flag to temporarily suppress observer updates
during these scrolls.
* Added explicit timeouts in `photo-gallery-test.gjs` to allow the carousel
animations to settle between keyboard events.
* Refactored `Modal` and `PhotoGallery` components to use `{{in-element}}`
to render their contents into a top-level `#modal-portal` div. This prevents
z-index and overflow clipping issues.
* Updated `index.html` to include the `#modal-portal` div.
This commit is contained in:
@@ -8,6 +8,7 @@ import Icon from './icon';
|
||||
import fadeInImage from '../modifiers/fade-in-image';
|
||||
import { on } from '@ember/modifier';
|
||||
import { modifier } from 'ember-modifier';
|
||||
import config from 'marco/config/environment';
|
||||
|
||||
export default class PhotoCarousel extends Component {
|
||||
@tracked canScrollLeft = false;
|
||||
@@ -55,6 +56,8 @@ export default class PhotoCarousel extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
isProgrammaticScroll = false;
|
||||
|
||||
scrollToNewPhoto = modifier((element, [eventId]) => {
|
||||
if (eventId && eventId !== this.lastEventId) {
|
||||
const isInitial = !this.lastEventId;
|
||||
@@ -65,6 +68,8 @@ export default class PhotoCarousel extends Component {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isProgrammaticScroll = true;
|
||||
|
||||
const scrollAction = () => {
|
||||
const targetSlide = element.querySelector(
|
||||
`[data-event-id="${eventId}"]`
|
||||
@@ -78,11 +83,18 @@ export default class PhotoCarousel extends Component {
|
||||
// Restore smooth scroll after the jump
|
||||
setTimeout(() => {
|
||||
element.style.scrollBehavior = originalScrollBehavior;
|
||||
this.isProgrammaticScroll = false;
|
||||
}, 50);
|
||||
} else {
|
||||
// Use native CSS smooth scrolling for subsequent clicks
|
||||
element.scrollLeft = targetSlide.offsetLeft;
|
||||
// Clear programmatic scroll flag after a delay to let scroll finish
|
||||
setTimeout(() => {
|
||||
this.isProgrammaticScroll = false;
|
||||
}, 500);
|
||||
}
|
||||
} else {
|
||||
this.isProgrammaticScroll = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -111,10 +123,16 @@ export default class PhotoCarousel extends Component {
|
||||
}
|
||||
|
||||
let intersectionObserver;
|
||||
if (this.args.onVisiblePhotoChange && window.IntersectionObserver) {
|
||||
if (
|
||||
this.args.onVisiblePhotoChange &&
|
||||
window.IntersectionObserver &&
|
||||
config.environment !== 'test'
|
||||
) {
|
||||
// Set up intersection observer to track which photo is currently "most" visible
|
||||
intersectionObserver = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (this.isProgrammaticScroll) return;
|
||||
|
||||
for (let entry of entries) {
|
||||
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
|
||||
const eventId = entry.target.dataset.eventId;
|
||||
|
||||
Reference in New Issue
Block a user