/** * TT-Core Intersection Observer Composable (Vue 3) * Provides lazy-loading and visibility detection */ /** * Create an intersection observer composable * @param {Function} callback - Callback when element becomes visible * @param {Object} options - Observer options * @returns {Object} - Composable with ref and cleanup */ export function useIntersectionObserver(callback, options = {}) { const { ref, onMounted, onBeforeUnmount } = Vue; const targetRef = ref(null); let observer = null; onMounted(() => { const threshold = options.threshold || 0.1; const rootMargin = options.rootMargin || '0px'; const once = options.once !== undefined ? options.once : true; observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { callback(entry); if (once && observer) { observer.disconnect(); observer = null; } } }, { threshold, rootMargin, root: options.root || null } ); if (targetRef.value) { observer.observe(targetRef.value); } }); onBeforeUnmount(() => { if (observer) { observer.disconnect(); observer = null; } }); return { targetRef }; } /** * Create an intersection observer mixin (backward compatibility) * @param {Object} options - Observer options * @returns {Object} - Vue mixin */ export function createIntersectionObserverMixin(options = {}) { return { data() { return { isVisible: false, hasBeenVisible: false, observer: null }; }, mounted() { this.setupObserver(); }, beforeUnmount() { if (this.observer) { this.observer.disconnect(); this.observer = null; } }, methods: { setupObserver() { const threshold = options.threshold || 0.1; const rootMargin = options.rootMargin || '0px'; this.observer = new IntersectionObserver( ([entry]) => { this.isVisible = entry.isIntersecting; if (entry.isIntersecting && !this.hasBeenVisible) { this.hasBeenVisible = true; if (this.onFirstVisible) { this.onFirstVisible(); } } }, { threshold, rootMargin } ); if (this.$refs.root) { this.observer.observe(this.$refs.root); } } } }; }