PageSourceSearch

https://www.groverclevelandhs.org/shared/assets/swiper-init.js

js groverclevelandhs.org collected 2026-09-30 11:37:00 UTC 12,137 bytes, 367 lines download raw bytes

1(function() {
2	'use strict';
3
4	// Version number for debugging
5	const SWIPER_INIT_VERSION = '1.0.0';
6	window._swiperInitVersion = SWIPER_INIT_VERSION;
7
8	// Accessibility message templates by content type
9	const SWIPER_A11Y_CONTEXTS = {
10		'events': {
11			containerRoleDescriptionMessage: 'Events carousel',
12			itemRoleDescriptionMessage: 'slide',
13			prevSlideMessage: 'Previous event',
14			nextSlideMessage: 'Next event',
15			firstSlideMessage: 'This is the first event',
16			lastSlideMessage: 'This is the last event',
17			paginationBulletMessage: 'Go to event {{index}}'
18		},
19		'news': {
20			containerRoleDescriptionMessage: 'News articles carousel',
21			itemRoleDescriptionMessage: 'slide',
22			prevSlideMessage: 'Previous article',
23			nextSlideMessage: 'Next article',
24			firstSlideMessage: 'This is the first article',
25			lastSlideMessage: 'This is the last article',
26			paginationBulletMessage: 'Go to article {{index}}'
27		},
28		'links': {
29			containerRoleDescriptionMessage: 'Links carousel',
30			itemRoleDescriptionMessage: 'slide',
31			prevSlideMessage: 'Previous link',
32			nextSlideMessage: 'Next link',
33			firstSlideMessage: 'This is the first link',
34			lastSlideMessage: 'This is the last link',
35			paginationBulletMessage: 'Go to link {{index}}'
36		},
37		'infographics': {
38			containerRoleDescriptionMessage: 'Infographics carousel',
39			itemRoleDescriptionMessage: 'slide',
40			prevSlideMessage: 'Previous infographic',
41			nextSlideMessage: 'Next infographic',
42			firstSlideMessage: 'This is the first infographic',
43			lastSlideMessage: 'This is the last infographic',
44			paginationBulletMessage: 'Go to infographic {{index}}'
45		},
46		'shuffle-gallery': {
47			containerRoleDescriptionMessage: 'Image gallery carousel',
48			itemRoleDescriptionMessage: 'slide',
49			prevSlideMessage: 'Previous image',
50			nextSlideMessage: 'Next image',
51			firstSlideMessage: 'This is the first image',
52			lastSlideMessage: 'This is the last image',
53			paginationBulletMessage: 'Go to image {{index}}'
54		},
55		'video': {
56			containerRoleDescriptionMessage: 'Video carousel',
57			itemRoleDescriptionMessage: 'slide',
58			prevSlideMessage: 'Previous video',
59			nextSlideMessage: 'Next video',
60			firstSlideMessage: 'This is the first video',
61			lastSlideMessage: 'This is the last video',
62			paginationBulletMessage: 'Go to video {{index}}'
63		}
64	};
65
66	// Pause videos on slide change
67	function addVideoPauseHandler(swiperInstance) {
68		if (!swiperInstance || !swiperInstance.on) return;
69
70		swiperInstance.on('slideChangeTransitionStart', function() {
71			this.el.querySelectorAll('video').forEach(function(video) {
72				if (!video.paused) {
73					video.pause();
74				}
75			});
76		});
77	}
78
79	// Resolve direction from breakpoints for current viewport width
80	// Swiper ignores direction in breakpoints natively, so we resolve it manually
81	function resolveDirectionForViewport(config) {
82		var direction = config.direction || 'horizontal';
83		if (config.breakpoints) {
84			var width = window.innerWidth;
85			var bps = Object.keys(config.breakpoints).map(Number).sort(function(a, b) { return a - b; });
86			for (var i = bps.length - 1; i >= 0; i--) {
87				if (width >= bps[i]) {
88					if (config.breakpoints[bps[i]].direction) {
89						direction = config.breakpoints[bps[i]].direction;
90					}
91					break;
92				}
93			}
94		}
95		return direction;
96	}
97
98	// Create and initialize a Swiper instance with merged config
99	function createSwiperInstance(config) {
100		const containerId = config.containerId;
101		const snippetType = config.snippetType || 'generic';
102		const selector = '#' + containerId + ' .swiper';
103		const a11yContext = SWIPER_A11Y_CONTEXTS[snippetType] || {};
104
105		// Build defaults (lowest priority)
106		const defaults = {
107			normalizeSlideIndex: false,
108			a11y: {
109				enabled: true,
110				containerRole: 'region',
111				containerRoleDescriptionMessage: a11yContext.containerRoleDescriptionMessage || 'Carousel',
112				itemRoleDescriptionMessage: a11yContext.itemRoleDescriptionMessage || 'slide',
113				prevSlideMessage: a11yContext.prevSlideMessage || 'Previous slide',
114				nextSlideMessage: a11yContext.nextSlideMessage || 'Next slide',
115				firstSlideMessage: a11yContext.firstSlideMessage || 'This is the first slide',
116				lastSlideMessage: a11yContext.lastSlideMessage || 'This is the last slide',
117				paginationBulletMessage: a11yContext.paginationBulletMessage || 'Go to slide {{index}}'
118			},
119			keyboard: {
120				enabled: true,
121				onlyInViewport: true,
122				pageUpDown: true
123			}
124		};
125
126		// Auto-generate navigation/pagination if not provided and enabled
127		if (config.swiperNavigation && !config.navigation) {
128			defaults.navigation = {
129				nextEl: '#' + containerId + ' .swiper-button-next',
130				prevEl: '#' + containerId + ' .swiper-button-prev'
131			};
132		}
133
134		if (config.swiperPagination && !config.pagination) {
135			defaults.pagination = {
136				el: '#' + containerId + ' .swiper-pagination',
137				clickable: true
138			};
139		}
140
141		// Start with top-level merge (shallow is fine for primitives)
142		const enhancedConfig = Object.assign({}, defaults, config);
143
144		// Deep merge nested objects to preserve defaults (fix shallow merge bug)
145		// Without this, config.a11y completely replaces defaults.a11y, losing enabled/containerRole
146		const nestedObjects = ['a11y', 'keyboard', 'navigation', 'pagination'];
147		nestedObjects.forEach(function(key) {
148			if (defaults[key] && config[key]) {
149				// Both exist - merge them (config wins for duplicate keys)
150				enhancedConfig[key] = Object.assign({}, defaults[key], config[key]);
151			}
152			// If only one exists, Object.assign already handled it correctly
153		});
154
155		// Defensive check: warn if thumbs swiper is null (timing issue)
156		if (config.thumbs && config.thumbs.swiper === null) {
157			console.warn('Swiper: Thumbnail swiper not initialized yet for:', containerId);
158		}
159
160		// Deep merge event handlers - combine default and custom handlers
161		const defaultHandlers = {
162			breakpoint: function(swiper) {
163				// Swiper doesn't support direction in breakpoints natively — resolve it manually
164				if (swiper._swiperInitConfig) {
165					var intendedDirection = resolveDirectionForViewport(swiper._swiperInitConfig);
166					if (intendedDirection !== swiper.currentBreakpointDirection) {
167						swiper.changeDirection(intendedDirection, false);
168						swiper.currentBreakpointDirection = intendedDirection;
169					}
170				}
171
172				swiper.update();
173
174				// Swiper's autoHeight only sets height, never removes it
175				if (!swiper.params.autoHeight && swiper.wrapperEl.style.height) {
176					swiper.wrapperEl.style.height = '';
177				}
178			}
179		};
180
181		if (config.on) {
182			enhancedConfig.on = {};
183
184			// Get union of all event names from both default and config handlers
185			const allEvents = new Set([
186				...Object.keys(defaultHandlers),
187				...Object.keys(config.on)
188			]);
189
190			allEvents.forEach(function(eventName) {
191				const defaultHandler = defaultHandlers[eventName];
192				const customHandler = config.on[eventName];
193
194				if (defaultHandler && customHandler) {
195					// Both exist - create composite handler that calls both
196					enhancedConfig.on[eventName] = function() {
197						defaultHandler.apply(this, arguments);
198						customHandler.apply(this, arguments);
199					};
200				} else {
201					// Only one exists - use whichever is defined
202					enhancedConfig.on[eventName] = defaultHandler || customHandler;
203				}
204			});
205		} else {
206			// No custom handlers - use defaults only
207			enhancedConfig.on = defaultHandlers;
208		}
209
210		// Clean up meta properties not used by Swiper
211		delete enhancedConfig.snippetType;
212		delete enhancedConfig.containerId;
213		delete enhancedConfig.responsiveDisable;
214		delete enhancedConfig.swiperNavigation;
215		delete enhancedConfig.swiperPagination;
216
217		const instance = new Swiper(selector, enhancedConfig);
218		instance._swiperInitConfig = config;
219
220		// Resolve initial direction from breakpoints and apply if needed
221		var initialDirection = resolveDirectionForViewport(config);
222		if (initialDirection !== (config.direction || 'horizontal')) {
223			instance.changeDirection(initialDirection, false);
224		}
225		instance.currentBreakpointDirection = initialDirection;
226
227		addVideoPauseHandler(instance);
228
229		return instance;
230	}
231
232	// Parse responsive disable config from HTML class
233	function getResponsiveDisableFromClasses(container) {
234		if (!container) return null;
235
236		const classList = container.className;
237		const match = classList.match(/swiper-disable-at-(\w+)/);
238
239		if (match) {
240			const breakpointMap = {
241				'small': 320,
242				'mid': 768,
243				'large': 1024,
244				'xl': 1366,
245				'xxl': 1920
246			};
247
248			return {
249				enabled: true,
250				breakpoint: breakpointMap[match[1]] || 1024
251			};
252		}
253
254		return null;
255	}
256
257	// Get responsive disable config (HTML class takes priority over config object)
258	// Returns {enabled: true, breakpoint: number} or null
259	function getResponsiveDisableConfig(config, container) {
260		const classConfig = getResponsiveDisableFromClasses(container);
261		if (classConfig) {
262			return classConfig;
263		}
264
265		if (config.responsiveDisable && config.responsiveDisable.enabled) {
266			// Normalize config-based responsive disable to consistent shape
267			const breakpoint = config.responsiveDisable.breakpoint || 1024;
268			return {
269				enabled: true,
270				breakpoint: breakpoint
271			};
272		}
273
274		return null;
275	}
276
277	// Handle responsive destroy/recreate at breakpoints
278	function handleResponsiveDisable(config, swiperInstance) {
279		const containerId = config.containerId;
280		const container = document.getElementById(containerId);
281
282		if (!container) {
283			console.warn('Swiper: Container not found for responsive disable:', containerId);
284			return;
285		}
286
287		const responsiveConfig = getResponsiveDisableConfig(config, container);
288
289		if (!responsiveConfig || !responsiveConfig.enabled) {
290			return;
291		}
292
293		const breakpoint = responsiveConfig.breakpoint;
294		const mediaQuery = window.matchMedia('(min-width: ' + breakpoint + 'px)');
295		let currentInstance = swiperInstance;
296
297		function handleBreakpointChange(e) {
298			if (e.matches) {
299				if (currentInstance && !currentInstance.destroyed) {
300					currentInstance.destroy(true, true);
301					currentInstance = null;
302				}
303				container.classList.add('swiper-disabled');
304			} else {
305				if (!currentInstance || currentInstance.destroyed) {
306					currentInstance = createSwiperInstance(config);
307					handleResponsiveDisable(config, currentInstance);
308				}
309				container.classList.remove('swiper-disabled');
310			}
311		}
312
313		mediaQuery.addEventListener('change', handleBreakpointChange);
314		handleBreakpointChange(mediaQuery);
315	}
316
317	// Auto-discover and initialize all Swiper configs on DOM ready
318	document.addEventListener('DOMContentLoaded', function() {
319		// Check if Swiper library is loaded
320		if (typeof Swiper === 'undefined') {
321			console.error('Swiper: Swiper library not loaded. Ensure swiper-bundle.js is included before swiper-init.js');
322			return;
323		}
324
325		// Find all swiper containers and initialize them
326		document.querySelectorAll('.swiper-container').forEach(function(container) {
327			// Derive config key from container ID
328			const containerId = container.id;
329			if (!containerId) {
330				console.warn('Swiper: Container missing ID attribute, skipping:', container);
331				return;
332			}
333
334			// Look for matching config in window
335			// Pattern: container ID "events_123_container" -> config key "events_123SwiperConfig"
336			const snippetId = containerId.replace(/_container$/, '');
337			const configKey = snippetId + 'SwiperConfig';
338			const config = window[configKey];
339
340			if (!config || typeof config !== 'object') {
341				// No config found - skip silently (might be a swiper initialized elsewhere)
342				return;
343			}
344
345			if (!config.containerId) {
346				console.warn('Swiper: Config missing containerId property:', configKey);
347				return;
348			}
349
350			try {
351				const instance = createSwiperInstance(config);
352				handleResponsiveDisable(config, instance);
353				config._instance = instance;
354			} catch (error) {
355				console.error('Swiper: Failed to initialize:', {
356					configKey: configKey,
357					containerId: config.containerId,
358					error: error.message
359				});
360
361				// Graceful degradation: remove swiper classes to show static list
362				container.classList.remove('swiper-container');
363				container.classList.add('swiper-error');
364			}
365		});
366	});
367})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.