PageSourceSearch

https://www.thaielephantohio.com/web/website/assets/js/website_template.js?_=1788377785

js thaielephantohio.com collected 2026-09-24 14:19:01 UTC 14,414 bytes, 496 lines download raw bytes

1'use strict';
2
3class Controller {
4	constructor(element) {
5		this.element = element;
6	}
7
8	init() {}
9
10	getInitialPropNumber(name, def) {
11		return this.getInitialProp(name, def, value => this.intval(value, def));
12	}
13
14	getInitialPropJson(name, def) {
15		return this.getInitialProp(name, def, value => {
16			try {
17				return JSON.parse(value);
18			} catch (_) {
19				return null;
20			}
21		})
22	}
23
24	getInitialPropBoolean(name) {
25		return this.getInitialProp(name, false, value => this.boolval(value));
26	}
27
28	getInitialProp(name, def, fn) {
29		if (this.rawInitialProps == null) {
30			this.rawInitialProps = this.loadInitialProps();
31		}
32		let value = name in this.rawInitialProps ?
33			(fn ? fn(this.rawInitialProps[name]) : this.rawInitialProps[name])
34			: null;
35		return value == null ? def : value;
36	}
37
38	getInitialProps() {
39		if (this.rawInitialProps == null) {
40			this.rawInitialProps = this.loadInitialProps();
41		}
42		return this.rawInitialProps;
43	}
44
45	intval(value, def) {
46		let result = parseInt(value, 10);
47		return isNaN(result) ? def : result;
48	}
49
50	boolval(value) {
51		return !(value == null || value == false || value == 'false');
52	}
53
54	loadInitialProps() {
55		let result = {};
56		if (Controller.DATASET_PROPS_KEY in this.element.dataset) {
57			try {
58				let props = JSON.parse(this.element.dataset[Controller.DATASET_PROPS_KEY]);
59				if (props && typeof props === 'object') {
60					Object.assign(result, props);
61				}
62			} catch (_) {
63			}
64		}
65		return result;
66	}
67
68	prevent(event) {
69		event.preventDefault();
70		event.stopPropagation();
71	}
72
73	show(elements) {
74		for (let element of typeof elements.length === 'number' ? elements : [elements]) {
75			element.removeAttribute('hidden');
76		}
77	}
78
79	hide(elements) {
80		for (let element of typeof elements.length === 'number' ? elements : [elements]) {
81			element.setAttribute('hidden', 'hidden');
82		}
83	}
84
85	setVisible(elements, isVisible) {
86		isVisible ? this.show(elements) : this.hide(elements);
87	}
88
89	setClass(element, className, exists) {
90		if (exists) {
91			element.classList.add(className);
92		} else {
93			element.classList.remove(className);
94		}
95	}
96
97}
98
99// Define static properties using traditional syntax
100Controller.DATASET_KEY = 'use';
101Controller.DATASET_PROPS_KEY = 'useProps';
102
103class ScaledTextController extends Controller {
104	init() {
105		super.init();
106		this.maxWidth = this.getInitialPropNumber('maxWidth', 300);
107		this.narrowFontSize = this.getInitialProp('narrowFontSize', '95%');
108		this.wideFontSize = this.getInitialProp('wideFontSize', '100%');
109		this.updateFontSize();
110		if ('ResizeObserver' in window) {
111			new ResizeObserver(() => this.updateFontSize()).observe(this.element);
112		}
113	}
114
115	updateFontSize() {
116		this.element.style.fontSize = this.element.offsetWidth > this.maxWidth ? this.narrowFontSize : this.wideFontSize;
117	}
118}
119ScaledTextController.TYPE = 'ScaledText';
120
121
122class MobileNavController extends Controller {
123	constructor(element) {
124        super(element);
125        this.isExpanded = false;
126    }
127
128	init() {
129		super.init();
130		this.isExpandedClass = this.getInitialProp('isExpandedClass', 'is-expanded');
131		this.expandedStateElements = [];
132		for (let toggleElement of this.element.querySelectorAll('[data-mobile-nav-toggle]')) {
133			toggleElement.addEventListener('click', this.onToggle.bind(this), true);
134			if (!toggleElement.dataset.mobileNavToggle) {
135				this.expandedStateElements.push(toggleElement);
136			}
137		}
138		for (let linkElement of this.element.querySelectorAll('a')) {
139			linkElement.addEventListener('click', this.collapse.bind(this), true);
140			linkElement.addEventListener('keydown', this.onLinkKeydown.bind(this), true);
141		}
142		this.menuPanel = this.element.querySelector('.mobile-menu-container');
143		this.initialFocusElement = this.menuPanel && this.menuPanel.querySelector('[data-mobile-nav-toggle="false"]');
144		this.element.ownerDocument.addEventListener('keydown', this.onKeydown.bind(this));
145		this.onUpdated();
146	}
147
148	onToggle(event) {
149		let toggleType = event.currentTarget.dataset.mobileNavToggle;
150		if (toggleType == null || toggleType === "") {
151			this.isExpanded = !this.isExpanded;
152		} else {
153			this.isExpanded = this.boolval(toggleType);
154		}
155		this.onUpdated();
156		if (this.isExpanded) {
157			if (this.initialFocusElement) {
158				this.initialFocusElement.focus();
159			}
160		} else {
161			this.focusToggleButton();
162		}
163	}
164
165	focusToggleButton() {
166		if (this.expandedStateElements[0]) {
167			this.expandedStateElements[0].focus();
168		}
169	}
170
171	getFocusableElements() {
172		if (!this.menuPanel) {
173			return [];
174		}
175		let elements = this.menuPanel.querySelectorAll('a[href], button:not([disabled]), [tabindex]:not
175([tabindex="-1"])');
176		return Array.from(elements).filter(element => element.offsetParent !== null);
177	}
178
179	onLinkKeydown(event) {
180		if (event.key === 'Enter') {
181			this.collapse();
182		}
183	}
184
185	onKeydown(event) {
186		if (!this.isExpanded) {
187			return;
188		}
189		if (event.key === 'Escape') {
190			this.collapse();
191			this.focusToggleButton();
192			return;
193		}
194		if (event.key !== 'Tab') {
195			return;
196		}
197		let focusable = this.getFocusableElements();
198		if (focusable.length === 0) {
199			return;
200		}
201		let first = focusable[0];
202		let last = focusable[focusable.length - 1];
203		if (event.shiftKey && document.activeElement === first) {
204			event.preventDefault();
205			last.focus();
206		} else if (!event.shiftKey && document.activeElement === last) {
207			event.preventDefault();
208			first.focus();
209		}
210	}
211
212	collapse() {
213		this.isExpanded = false;
214		this.onUpdated();
215	}
216
217	onUpdated() {
218		for (let element of this.element.querySelectorAll('[data-show-if-expanded]')) {
219			this.setVisible(element, this.isExpanded);
220		}
221		for (let element of this.element.querySelectorAll('[data-hide-if-expanded]')) {
222			this.setVisible(element, !this.isExpanded);
223		}
224		this.setClass(this.element, this.isExpandedClass, this.isExpanded);
225		for (let element of this.expandedStateElements) {
226			element.setAttribute('aria-expanded', this.isExpanded ? 'true' : 'false');
227		}
228		if (this.menuPanel) {
229			this.menuPanel.inert = !this.isExpanded;
230		}
231	}
232}
233MobileNavController.TYPE = 'MobileNav';
234
235class IsScrolledController extends Controller {
236	constructor(element) {
237        super(element);
238        this.isScrolled = false;
239    }
240
241	init() {
242		super.init();
243		this.scrollThreshold = this.getInitialPropNumber('scrollThreshold', 10);
244		this.isScrolledClass = this.getInitialProp('isScrolledClass', 'is-scrolled');
245		this.element.ownerDocument.addEventListener('scroll', this.onScroll.bind(this), {passive: true});
246	}
247
248	onScroll() {
249		let isScrolled = window.scrollY > this.scrollThreshold;
250		if (isScrolled == this.isScrolled) {
251			return;
252		}
253		this.isScrolled = isScrolled;
254		this.setClass(this.element, this.isScrolledClass, this.isScrolled);
255	}
256}
257IsScrolledController.TYPE = 'IsScrolled';
258
259class TabsController extends Controller {
260	init() {
261		super.init();
262		this.tabs = this.element.querySelectorAll('.tabs-menus [role="tab"]');
263		this.tabPanels = this.element.querySelectorAll('.restaurant-menu');
264
265		this.tabs.forEach(tab => {
266			tab.addEventListener('click', () => this.activateTab(tab));
267			tab.addEventListener('keydown', event => this.onKeydown(event, tab));
268		});
269
270		this.activateFirstTab();
271	}
272
273	onKeydown(event, tab) {
274		if (event.key === 'Enter' || event.key === ' ') {
275			event.preventDefault();
276			this.activateTab(tab);
277			return;
278		}
279
280		let tabs = Array.from(this.tabs);
281		let lastIndex = tabs.length - 1;
282		let index = tabs.indexOf(tab);
283		let nextIndex;
284		switch (event.key) {
285			case 'ArrowRight': nextIndex = index === lastIndex ? 0 : index + 1; break;
286			case 'ArrowLeft': nextIndex = index === 0 ? lastIndex : index - 1; break;
287			case 'Home': nextIndex = 0; break;
288			case 'End': nextIndex = lastIndex; break;
289			default: return;
290		}
291
292		event.preventDefault();
293		this.activateTab(tabs[nextIndex]);
294		tabs[nextIndex].focus();
295	}
296
297	activateFirstTab() {
298		if (this.tabs.length > 0) {
299			this.activateTab(this.tabs[0]);
300		}
301	}
302
303	activateTab(tab) {
304		// Deactivate all tabs and hide all tab panels
305		// Only the selected tab stays in the tab sequence; arrow keys move between the rest
306		this.tabs.forEach(t => {
307			t.setAttribute('aria-selected', 'false');
308			t.setAttribute('tabindex', '-1');
309			t.classList.remove('active');
310		});
311		this.tabPanels.forEach(panel => panel.style.display = 'none');
312
313		// Activate the clicked tab and show the corresponding tab panel
314		tab.setAttribute('aria-selected', 'true');
315		tab.setAttribute('tabindex', '0');
316		tab.classList.add('active');
317		const tabPanel = this.element.querySelector(`#${tab.getAttribute('aria-controls')}`);
318		tabPanel.style.display = 'block';
319	}
320}
321TabsController.TYPE = 'Tabs';
322
323class GalleryController extends Controller {
324
325	init() {
326		if (!('SimpleLightbox' in window)) {
327			console.error('SimpleLightbox library is not loaded');
328			return;
329		}
330		this.isExpanded = false;
331		this.isFullyVisible = false;
332		this.galleryExpandedClass = this.getInitialProp('galleryExpandedClass', '--expanded');
333		this.controlsExpandedClass = this.getInitialProp('controlsExpandedClass', '--expanded');
334		this.galleryElement = this.element.querySelector('[data-gallery]');
335		this.controlsElement = this.element.querySelector('[data-controls]');
336		this.toggleButtonElement = this.element.querySelector('[data-toggle-button]');
337		let onCollapseScrollToSelector = this.getInitialProp('onCollapseScrollTo', null);
338		if (onCollapseScrollToSelector) {
339			this.onCollapseScrollToElement = document.querySelector(onCollapseScrollToSelector);
340		}
341		this.toggleButtonElement.addEventListener('click', event => {
342			this.prevent(event);
343			this.toggleExpanded();
344		});
345		new SimpleLightbox(this.galleryElement.children, Object.assign({
346			fileExt: null,
347			animationSpeed: 10,
348			fadeSpeed: 100,
349		}, this.getInitialProps() || {}));
350		window.addEventListener('resize', () => this.calculateVisibility());
351		this.calculateVisibility();
352		for (let img of this.galleryElement.querySelectorAll('img')) {
353			// `complete` images have already fired `load` - on a warm cache that happens before
354			// `DOMContentLoaded`, when this controller is constructed, so a listener alone would
355			// never run and the gallery would stay measured at its pre-load height.
356			// `error` as well as `load`: a broken image never fires `load`, and would otherwise
357			// leave the height wrong permanently.
358			if (img.complete) {
359				this.calculateVisibility();
360			} else {
361				img.addEventListener('load', () => this.calculateVisibility(), { once: true });
362				img.addEventListener('error', () => this.calculateVisibility(), { once: true });
363			}
364		}
365	}
366
367	toggleExpanded() {
368		this.isExpanded = !this.isExpanded;
369		this.updateExpanded();
370		this.calculateVisibility();
371		requestAnimationFrame(() => {
372			if (!this.isExpanded) {
373				if (this.onCollapseScrollToElement) {
374					this.onCollapseScrollToElement.scrollIntoView();
375				} else {
376					this.galleryElement.querySelector('[data-gallery-item]').scrollIntoView({block: "center"});
377				}
378			}
379		});
380	}
381
382	updateExpanded() {
383		this.setClass(this.galleryElement, this.galleryExpandedClass, this.isExpanded);
384		this.setClass(this.controlsElement, this.controlsExpandedClass, this.isExpanded);
385		this.setVisible(this.controlsElement.querySelectorAll('[data-expand]'), !this.isExpanded);
386		this.setVisible(this.controlsElement.querySelectorAll('[data-collapse]'), this.isExpanded);
387	}
388
389	calculateVisibility() {
390		this.updateHeight();
391		requestAnimationFrame(() => {
392			this.isFullyVisible = !(this.galleryElement.scrollHeight > this.galleryElement.offsetHeight);
393			this.updateControlsVisibility();
394		});
395	}
396
397	updateHeight() {
398		if (this.isExpanded) {
399			this.galleryElement.style.maxHeight = null;
400		} else {
401			let height = this.calculateCollapsedHeight();
402			this.galleryElement.style.maxHeight = `${height}px`;
403		}
404	}
405
406	calculateCollapsedHeight() {
407		let rects = Array.from(this.galleryElement.querySelectorAll('[data-gallery-item]')).map(item => item.getBoundingClientRect());
408		if (!rects.length) {
409			return 0;
410		}
411		rects.sort((a, b) => a.y - b.y);
412		let top = rects[0].y;
413		let bottom = rects[0].y + rects[0].height;
414		let i = 1;
415		while (i < rects.length && Math.abs(rects[i].y - top) < 1) {
416			i++;
417		}
418		if (i < 3 && rects[i]) {
419			bottom = rects[i].y + rects[i].height;
420		}
421		return bottom - top + 1;
422	}
423
424	updateControlsVisibility() {
425		if (!this.isExpanded) {
426			if (this.isFullyVisible) {
427				this.hide(this.controlsElement);
428			} else {
429				this.show(this.controlsElement);
430			}
431		}
432	}
433
434}
435GalleryController.TYPE = 'Gallery';
436
437class AnnouncementController extends Controller {
438
439	init() {
440		if (!('Toastify' in window)) {
441			console.error('Toastify library is not loaded');
442			return;
443		}
444		let options = this.getInitialProps();
445		if (!options || typeof options !== 'object') {
446			options = {};
447		}
448		options.escapeMarkup = false;
449		options.text = this.element.innerHTML;
450		let timeout = options.timeout || 3000;
451		setTimeout(() => Toastify(options).showToast(), timeout);
452	}
453}
454AnnouncementController.TYPE = 'Announcement';
455
456function initComponents(controllers) {
457	let ctrlMap = controllers.reduce((map, ctor) => map.set(ctor.TYPE, ctor), new Map());
458	for (let child of document.documentElement.querySelectorAll('[data-' + Controller.DATASET_KEY + ']')) {
459		let type = child.dataset[Controller.DATASET_KEY];
460		type && ctrlMap.has(type) && new (ctrlMap.get(type))(child).init();
461	}
462}
463
464// The `.anchor-id` markers used for same-page navigation (e.g. `/#menu`) are invisible,
465// zero-content elements used only to offset the browser's native scroll position, so they
466// can't receive focus themselves. Native anchor navigation only moves focus for focusable
467// targets, so without this, focus is silently dropped on every same-page nav click.
468function focusAnchorTarget() {
469	let hash = decodeURIComponent(location.hash.slice(1));
470	let anchor = hash && document.getElementById(hash);
471	if (!anchor || !anchor.classList.contains('anchor-id')) {
472		return;
473	}
474	let target = anchor.nextElementSibling || anchor.parentElement.nextElementSibling;
475	if (!target) {
476		return;
477	}
478	if (!target.hasAttribute('tabindex')) {
479		target.setAttribute('tabindex', '-1');
480	}
481	target.focus({ preventScroll: true });
482}
483
484function init() {
485	initComponents([
486		IsScrolledController,
487		ScaledTextController,
488		MobileNavController,
489		GalleryController,
490		TabsController,
491		AnnouncementController,
492	]);
493	window.addEventListener('hashchange', focusAnchorTarget);
494	focusAnchorTarget();
495}
496document.addEventListener('DOMContentLoaded', init, false);

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.