PageSourceSearch

https://www.e-firstaidsupplies.com/store/json.mvc?Store_Code=FPI&F…JavaScriptResource_Code=mmx-base&T=168dfeec

js e-firstaidsupplies.com collected 2026-09-26 03:44:39 UTC 35,817 bytes, 1,522 lines download raw bytes

1/**
2 * MMX / BASE
3 */
4
5const MMX = {};
6
7MMX.variableType = (value) => {
8	const [input, type] = Object.prototype.toString.call(value).match(/\[object (\w+)\]/);
9	return type.toLowerCase();
10};
11
12MMX.isTruthy = (value) => {
13	return [true, 'true', 'yes', 'y', 'on', '1', 1].includes(typeof value === 'string' ? value.toLowerCase() : value);
14};
15
16MMX.isFalsy = (value) => {
17	return [null, undefined, false, 'false', 'no', 'n', 'off', '0', 0].includes(typeof value === 'string' ? value.toLowerCase() : value);
18};
19
20MMX.copy = (value) => {
21	if(typeof value === 'object') {
22		try {
23			return JSON.parse(JSON.stringify(value));
24		} catch(err) {
25			return null;
26		}
27	}
28
29	return value;
30};
31
32MMX.assign = (...args) => {
33	return Object.assign(...args.map(MMX.copy));
34};
35
36MMX.coerceNumber = (value, fallback = 0) => {
37	const valueType = MMX.variableType(value);
38
39	if(['null', 'undefined'].includes(valueType)) {
40		return fallback;
41	}
42
43	if(valueType === 'string' && !value.trim().length) {
44		return fallback;
45	}
46
47	return isNaN(value) ? fallback : Number(value);
48};
49
50MMX.coerceString = (value = '', {prefix = '', suffix = '', fallback = ''} = {}) => {
51	const isValidValue = (typeof value === 'string' && value.length) || typeof value === 'number';
52
53	if (isValidValue) {
54		return `${prefix}${value}${suffix}`;
55	}
56
57	return fallback;
58};
59
60MMX.splitString = (value, {separator = ',', trim = true} = {}) => {
61	return String(value).split(separator).map(part => trim ? part.trim() : part);
62};
63
64MMX.normalizeCode = (value) => {
65	const valueType = MMX.variableType(value);
66
67	if (['string', 'number', 'boolean'].includes(valueType)) {
68		return String(value).trim();
69	}
70
71	return '';
72};
73
74MMX.scriptSafeJSONStringify = (data) => {
75	return JSON.stringify(data).replace(/\//gi, '\\/');
76};
77
78MMX.getCustomFieldValueFromRecord = (customField = '', record = {}) => {
79	const customFieldParts = customField?.split?.(':');
80
81	if (customFieldParts?.length !== 2) {
82		return '';
83	}
84
85	const [moduleCode, fieldCode] = customFieldParts;
86
87	return record?.CustomField_Values?.[moduleCode]?.[fieldCode];
88};
89
90MMX.valueIsEmpty = (value) => {
91	if (value === null)											return true;
92	else if (typeof value === 'object')							return Object.keys(value).length === 0 && value.constructor === Object;
93	else if (typeof value === 'undefined')						return true;
94	else if (typeof value === 'string' && value.length == 0)	return true;
95
96	// type is boolean, number, function, non-zero length string, etc
97	return false;
98};
99
100MMX.arrayIsEmpty = (arr) => {
101	return !Array.isArray(arr) || arr.length === 0;
102};
103
104MMX.cacheKeysFilters = (entities) => {
105	const values = []
106		.concat(entities ?? [])
107		.map(entity => {
108			return MMX.coerceNumber(entity?.dt_updated ?? entity);
109		})
110		.filter(value => value > 0);
111
112	if (!values.length) {
113		return [];
114	}
115
116	return [{
117		name: 'cache_keys',
118		value: values
119	}];
120};
121
122MMX.keyToCssProp = (key = '') => {
123	return MMX.snakeToKebabCase(MMX.camelToKebabCase(key));
124};
125
126MMX.camelToKebabCase = (value = '') => {
127	return String(value)
128		.replace(/([A-Z])([A-Z][a-z])/g, '$1-$2') // withASingleCharacterWord
129		.replace(/([a-z0-9])([A-Z])/g, '$1-$2') // commonCamelCase
130		.toLowerCase();
131};
132
133MMX.snakeToKebabCase = (value = '') => {
134	return String(value).replace(/_/g, '-').toLowerCase();
135};
136
137MMX.objectToCssShorthand = (obj, {key = 'padding_%side%'} = {}) => {
138	const first = MMX.composeUnitValue(obj?.[key.replace('%side%', 'top').replace('%corner%', 'top_left')]);
139	const second = MMX.composeUnitValue(obj?.[key.replace('%side%', 'right').replace('%corner%', 'top_right')]);
140	const third = MMX.composeUnitValue(obj?.[key.replace('%side%', 'bottom').replace('%corner%', 'bottom_right')]);
141	const fourth = MMX.composeUnitValue(obj?.[key.replace('%side%', 'left').replace('%corner%', 'bottom_left')]);
142
143	if (first === second && first === third && first === fourth) {
144		return first;
145	}
146
147	return `${first ?? 0} ${second ?? 0} ${third ?? 0} ${fourth ?? 0}`;
148};
149
150MMX.composeUnitValue = (obj, {fallbackUnit = 'px'} = {}) => {
151	if (MMX.valueIsEmpty(obj?.value)) {
152		return undefined;
153	}
154
155	const value = MMX.coerceNumber(obj?.value);
156
157	if (value === 0) {
158		return `${value}`;
159	}
160
161	return `${value}${MMX.coerceString(obj?.unit, {fallback: fallbackUnit})}`;
162};
163
164MMX.boxShadowObjectToCssValue = (obj) => {
165	if (!obj?.box_shadow_enabled?.value) {
166		return undefined;
167	}
168
169	const inset = obj?.box_shadow_inset?.value;
170	const offsetX = MMX.composeUnitValue(obj?.box_shadow_offset_x) ?? 0;
171	const offsetY = MMX.composeUnitValue(obj?.box_shadow_offset_y) ?? 0;
172	const blur = MMX.composeUnitValue(obj?.box_shadow_blur) ?? 0;
173	const spread = MMX.composeUnitValue(obj?.box_shadow_spread) ?? 0;
174	const color = obj?.box_shadow_color?.value;
175
176	return `${inset} ${offsetX} ${offsetY} ${blur} ${spread} ${color}`;
177};
178
179MMX.objectToInlineStyles = (styles = {}) => {
180	return Object.entries(styles).reduce((inlineStyles, [key, value]) => {
181		const isValidValue = (typeof value === 'string' && value.length) || typeof value === 'number';
182
183		if (isValidValue) {
184			const prop = MMX.keyToCssProp(key);
185			inlineStyles.push(`${prop}: ${value}`);
186		}
187
188		return inlineStyles;
189	}, []).join(';');
190};
191
192MMX.renderBreakpointStyle = ({selector = ':root', breakpoint = {}, match = {}} = {}) => {
193	if (MMX.isFalsy(breakpoint?.breakpoint_available) || MMX.valueIsEmpty(match)) {
194		return '';
195	}
196
197	return /*html*/`
198		<style>
199			@media ${MMX.buildMediaQuery(breakpoint)} {
200				${selector} {
201					${MMX.objectToInlineStyles(match)}
202				}
203			}
204		</style>
205	`;
206};
207
208MMX.getBreakpoints = () => {
209	return structuredClone(window.MMThemeBreakpoints ?? []);
210};
211
212MMX.getBreakpoint = (code) => {
213	return MMX.getBreakpoints().find(breakpoint => String(breakpoint.code) === String(code));
214};
215
216MMX.buildMediaQuery = (rules = {}) => {
217	const parts = [];
218
219	const minWidth = MMX.composeUnitValue({
220		value: rules?.breakpoint?.start?.value,
221		unit: rules?.breakpoint?.start?.unit
222	});
223
224	const maxWidth = MMX.composeUnitValue({
225		value: rules?.breakpoint?.end?.value,
226		unit: rules?.breakpoint?.end?.unit
227	});
228
229	if (typeof minWidth !== 'undefined') {
230		parts.push(`(min-width: ${minWidth})`);
231	}
232
233	if (typeof maxWidth !== 'undefined') {
234		parts.push(`(max-width: ${maxWidth})`);
235	}
236
237	return parts.join(' and ');
238};
239
240MMX.watchData = (obj, callback) => {
241	return new Proxy(obj, MMX.proxyHandler(callback));
242};
243
244MMX.proxyHandler = (callback) => {
245	return {
246		get(obj, prop) {
247			// Deep-watch the object (i.e. recursively bind a watcher to objects & arrays with the same callback)
248			const valueType = MMX.variableType(obj[prop]);
249			if (['object', 'array'].includes(valueType)) {
250				return MMX.watchData(obj[prop], callback);
251			}
252
253			return obj[prop];
254		},
255		set(obj, prop, value) {
256			obj[prop] = value;
257			if (typeof callback === 'function') {
258				callback();
259			}
260			return true;
261		}
262	};
263};
264
265MMX.createElement = ({type, content, attributes, data, parent} = {}) => {
266	if (typeof type === 'undefined'){
267		return;
268	}
269	let element = document.createElement(type);
270
271	if (typeof content === 'string') {
272		element.innerHTML = content;
273	}
274
275	if (typeof attributes === 'object') {
276		MMX.setElementAttributes(element, attributes);
277	}
278
279	if (typeof data === 'object') {
280		element.data = MMX.watchData(data, () => {
281			element.forceUpdate?.();
282		});
283	}
284	else if (typeof data === 'string') {
285		element.data = JSON.stringify(data);
286	}
287
288	if (typeof parent !== 'undefined') {
289		if (typeof parent === 'string' && parent === 'currentScript') {
290			const currentScript = document.scripts[document.scripts.length - 1];
291			return currentScript.insertAdjacentElement('afterEnd', element);
292		} else if (typeof parent?.appendChild === 'function') {
293			return parent.appendChild(element);
294		}
295	}
296
297	return element;
298};
299
300MMX.setElementAttributes = (element, attributes) => {
301
302	if (!attributes){
303		return element;
304	}
305
306	for (const attribute in attributes) {
307		if (!Object.hasOwnProperty.call(attributes, attribute)) {
308			continue;
309		}
310
311		const value = attributes[attribute];
312
313		if (typeof value === 'undefined') {
314			element.removeAttribute(attribute);
315			continue;
316		}
317
318		if (attribute === 'name'){
319			element.name = value;
320		}  else if (attribute === 'class'){
321			element.className = value;
322		} else {
323			element.setAttribute(attribute, value);
324		}
325	}
326
327	return element;
328};
329
330MMX.setElementStyles = ({element, styles = {}, suffix = ''} = {}) => {
331	if (!element) {
332		return;
333	}
334
335	for (const prop in styles) {
336		const value = typeof styles?.[prop] === 'undefined' ? '' : styles?.[prop] + suffix;
337		element.style[prop] = value;
338	}
339};
340
341MMX.closestElement = (selector, node) => {
342	if (!node || node === document || node === window) {
343		return null;
344	}
345
346	return (
347		node.closest?.(selector) ||
348		node.parentElement?.closest?.(selector) ||
349		MMX.closestElement(selector, node?.getRootNode?.()?.host)
350	);
351};
352
353MMX.querySelector = (selector, root = document) => {
354	if (selector === ':shadow') {
355		return root?.shadowRoot;
356	}
357
358	if (typeof root.querySelector === 'function') {
359		return root.querySelector(selector);
360	}
361
362	return null;
363};
364
365MMX.querySelectorList = (selectors = [], root = document) => {
366	let lastElement = root;
367
368	if (!Array.isArray(selectors) || selectors.length === 0) {
369		return null;
370	}
371
372	for (const selector of selectors) {
373		const element = MMX.querySelector(selector, lastElement);
374
375		if (!element) {
376			lastElement = null;
377			break;
378		}
379
380		lastElement = element;
381	}
382
383	return lastElement;
384};
385
386MMX.on = (events, selector, handler, { root = document, eventOptions = {} } = {}) => {
387	events = MMX.splitString(events);
388
389	events.forEach(event => {
390		root.addEventListener(event, (e) => {
391			const matched = e.target?.closest?.(selector);
392
393			if (!matched || (root !== document && !root.contains(matched))) {
394				return;
395			}
396
397			handler(e, matched);
398		}, eventOptions);
399	});
400};
401
402MMX.encodeEntities = (input) => {
403	return String(input ?? '')
404			.replace(/&reg;/g, '®')
405			.replace(/&trade;/g, '™')
406			.replace(/&/g, '&amp;')
407			.replace(/"/g, '&quot;')
408			.replace(/</g, '&lt;')
409			.replace(/>/g, '&gt;')
410			.replace(/\(/g, '&#40;')
411			.replace(/\)/g, '&#41;')
412			.replace(/®/g, '&reg;')
413			.replace(/™/g, '&trade;');
414};
415
416MMX.encodeEntitiesURI = (input) => {
417	return MMX.encodeEntities(encodeURI(input));
418};
419
420MMX.encodeSrcset = (input) => {
421	return encodeURI(input);
422};
423
424MMX.isEqual = (a, b) => {
425	return JSON.stringify(a) === JSON.stringify(b);
426};
427
428MMX.setBooleanAttribute = (node, attribute_name, value, optional_enabled_values) => {
429	if (Array.isArray(optional_enabled_values) &&
430		 optional_enabled_values.indexOf(value) !== -1)	node.setAttribute(attribute_name, value);
431	else if (MMX.isTruthy(value))						node.setAttribute(attribute_name, '');
432	else												node.removeAttribute(attribute_name);
433};
434
435MMX.getBooleanAttribute = (node, attribute_name, optional_enabled_values) => {
436	var value;
437
438	if (!node.hasAttribute(attribute_name)) {
439		return false;
440	}
441	else {
442		value = node.getAttribute(attribute_name);
443
444		if (Array.isArray(optional_enabled_values) &&
445			 optional_enabled_values.indexOf(value) !== -1)	return true;
446		else if (value === null || value === '')			return true;
447		else												return MMX.isTruthy(value);
448	}
449};
450
451MMX.elementInViewport = (element) => {
452	var elementPosition = element.getBoundingClientRect();
453
454	return (
455		elementPosition.top >= 0 &&
456		elementPosition.left >= 0 &&
457		elementPosition.bottom <= window.innerHeight &&
458		elementPosition.right <= window.innerWidth
459	);
460};
461
462/**
463 * Update an element's DOM to match a provided HTML string
464 * @param {Node} element
465 * @param {String} template
466 */
467MMX.renderTemplate = (element, template) => {
468	element.innerHTML = template;
469};
470
471MMX.debounce = (func, timeout = 100) => {
472	let timer;
473	return (...args) => {
474		clearTimeout(timer);
475		timer = setTimeout(() => { func.apply(this, args); }, timeout);
476	};
477};
478
479MMX.concatenateSortBy = (sortBy, customField) => {
480	if (MMX.valueIsEmpty(customField)) {
481		return sortBy;
482	}
483	else {
484		return `${sortBy}:${customField}`;
485	}
486};
487
488class MMX_FetchQueue {
489	#id = 0;
490	#max = 3;
491	#todo = new Map();
492	#doing = new Map();
493	#cache = new Map();
494
495	constructor({max} = {}) {
496		this.#max = MMX.coerceNumber(max, this.#max);
497		this.#doNext();
498	}
499
500	request(...params) {
501		return new Promise((resolve, reject) => {
502			this.#todo.set(this.#id++, {
503				params,
504				resolve,
505				reject
506			});
507
508			this.#doNext();
509		});
510	}
511
512	#doNext() {
513		if (this.#todo.size === 0 || this.#doing.size >= this.#max) {
514			return;
515		}
516
517		const [nextId, next] = this.#todo.entries().next().value;
518		this.#doing.set(nextId, next);
519		this.#todo.delete(nextId);
520
521		fetch(...next.params)
522			.then(next.resolve)
523			.catch(next.reject)
524			.finally(() => {
525				this.#doing.delete(nextId);
526				this.#doNext();
527			});
528	}
529
530	requestCached(...params) {
531		const key = JSON.stringify(params);
532
533		if (!this.#cache.has(key)) {
534			this.#cache.set(key, this.request(...params).catch(error => {
535				this.#cache.delete(key);
536				throw error;
537			}));
538		}
539
540		return this.#cache.get(key)
541			.then(response => {
542				return response?.clone?.() ?? response;
543			});
544	}
545}
546
547MMX.fetchQueue = new MMX_FetchQueue();
548
549class MMX_StyleManager {
550	#cache = new Map();
551
552	// Main Functions
553	adoptStyleSheets(shadowRoot, resourceCodes = []) {
554		if (!shadowRoot || !(shadowRoot instanceof ShadowRoot)) {
555			return Promise.resolve(null);
556		}
557
558		const styleSheets = this.#resolveStyleSheets(resourceCodes);
559		const linkSheets = this.#resolveLinkSheets(resourceCodes);
560
561		return Promise.all([...styleSheets, ...linkSheets])
562			.then((sheets) => {
563				shadowRoot.adoptedStyleSheets = sheets.filter(sheet => this.#isAdoptableSheet(sheet));
564			});
565	}
566
567	// Cache
568	#addCssToCache(key, css) {
569		if (!this.#cache.has(key)) {
570			this.#cache.set(key, this.#createSheet(css, key));
571		}
572
573		return this.#cache.get(key);
574	}
575
576	#createSheet(css, layer = 'mmx-adopted') {
577		css = /*css*/`
578			@layer ${layer} {
579				${css}
580			}
581		`;
582
583		const sheet = new CSSStyleSheet();
584		return sheet.replace(css);
585	}
586
587	// Links
588	#resolveLinkSheets(resourceCodes = []) {
589		return this.#getIncludedLinkElements(resourceCodes).map((link) => {
590			return this.#getStyleSheetFromLink(link);
591		});
592	}
593
594	#getIncludedLinkElements(resourceCodes) {
595		return this.#getAllLinkElements().filter(link => {
596			return this.#shouldIncludeElement(link, resourceCodes);
597		});
598	}
599
600	#getAllLinkElements() {
601		const selector = 'link[href][data-resource-code]';
602		const links = [...document.querySelectorAll(selector)];
603
604		[...document.querySelectorAll('template.mmx-resources')].forEac
604h(template => {
605			const templateLinks = template.content.querySelectorAll(selector);
606			if (templateLinks.length) {
607				links.push(...templateLinks);
608			}
609		});
610
611		return links;
612	}
613
614	#shouldIncludeElement(element, resourceCodes = []) {
615		return this.isAdoptableResource(element) && resourceCodes.includes(this.#getStyleSheetKey(element));
616	}
617
618	// Styles
619	#resolveStyleSheets(resourceCodes = []) {
620		return this.#getIncludedStyleElements(resourceCodes).map((style) => {
621			return this.#getStyleSheetFromStyle(style);
622		});
623	}
624
625	#getIncludedStyleElements(resourceCodes = []) {
626		return this.#getAllStyleElements().filter(style => {
627			return this.#shouldIncludeElement(style, resourceCodes);
628		});
629	}
630
631	#getAllStyleElements() {
632		return [...document.querySelectorAll('style[data-resource-code]')];
633	}
634
635	// CSSStyleSheet Building and Caching
636	isAdoptableResource(element) {
637		return element instanceof Element && element.matches?.('[data-mmx-resource-type~="adoptable"]');
638	};
639
640	#isAdoptableSheet(sheet) {
641		return sheet instanceof CSSStyleSheet;
642	}
643
644	#getStyleSheetKey(element) {
645		return element.dataset.resourceCode;
646	}
647
648	#getStyleSheetFromStyle(style) {
649		const key = this.#getStyleSheetKey(style);
650
651		if (!key) {
652			return null;
653		}
654
655		return this.#addCssToCache(key, style.textContent);
656	}
657
658	#getStyleSheetFromLink(link) {
659		const key = this.#getStyleSheetKey(link);
660
661		if (!(link instanceof HTMLElement) || !key) {
662			return Promise.resolve(null);
663		}
664
665		if (!this.#cache.has(key)) {
666			this.#cache.set(key, this.#fetchCssFromLink(link)
667				.then(css => this.#createSheet(css, key))
668				.catch(error => {
669					this.#cache.delete(key);
670					throw error;
671				}));
672		}
673
674		return this.#cache.get(key);
675	}
676
677	#fetchCssFromLink(link) {
678		const options = {};
679
680		if (link.integrity) {
681			options.integrity = link.integrity;
682		}
683
684		if (link.hasAttribute('crossorigin')) {
685			options.credentials = link.crossOrigin === 'use-credentials' ? 'include' : 'same-origin';
686		}
687
688		return fetch(link.href, options)
689			.then(response => {
690				if (!response.ok) {
691					throw new Error(`Failed to fetch stylesheet: ${link.href} (${response.status})`);
692				}
693				return response.text();
694			});
695	}
696}
697
698MMX.styleManager = new MMX_StyleManager();
699
700MMX.Runtime_JSON_API_Call = ({jsonUrl, storeCode, params = {}, cached = false} = {}) => {
701	jsonUrl = jsonUrl ?? window?.json_url;
702	storeCode = storeCode ?? window?.Store_Code;
703
704	if (!jsonUrl?.length || MMX.valueIsEmpty(storeCode)) {
705		return Promise.reject({
706			success: false,
707			error_code: 'MMX-Runtime_JSON_API_Call-00001',
708			error_message: 'Runtime_JSON_API_Call halted. Cannot call Miva JSON API. jsonUrl and/or storeCode is not provided'
709		});
710	}
711
712	const fetchParams = {
713		method: 'POST',
714		headers: {
715			'Content-Type': 'application/json'
716		},
717		body: JSON.stringify(MMX.assign({Session_Type: 'runtime', Store_Code: storeCode}, params))
718	};
719
720	const request = cached ? MMX.fetchQueue.requestCached(jsonUrl, fetchParams) : MMX.fetchQueue.request(jsonUrl, fetchParams);
721
722	return request
723		.then(async (response) => {
724			const text = await response.text();
725			try {
726				return JSON.parse(text);
727			} catch(err) {
728				return Promise.reject({
729					success: false,
730					error_code: 'MMX-Runtime_JSON_API_Call-00002',
731					error_message: `Did not receive valid JSON from API. Received: "${text}"`
732				});
733			}
734		}).then(data => {
735			if (data?.success) {
736				return Promise.resolve(data);
737			}
738
739			return Promise.reject(data);
740		});
741};
742
743MMX.fetchForm = (form, fetchOptions = {}) => {
744	if (!(form instanceof HTMLFormElement) || MMX.variableType(fetchOptions) !== 'object') {
745		return Promise.reject(new TypeError());
746	}
747
748	return MMX.fetchQueue.request(form.action, {
749		method: 'POST',
750		body: new FormData(form),
751		...fetchOptions
752	});
753};
754
755MMX.longMerchantUrl = (searchParams = {}, {merchantUrl, storeCode = window.Store_Code} = {}) => {
756	let url;
757
758	if (typeof merchantUrl !== 'string') {
759		if (typeof window.json_url === 'string') {
760			merchantUrl = window.json_url.replace('json.mvc', 'merchant.mvc');
761		} else {
762			return;
763		}
764	}
765
766	try {
767		url = new URL(merchantUrl);
768	}
769	catch (error) {
770		throw new TypeError(`${error.message} when parsing 'merchantUrl'`);
771	}
772
773	if (MMX.variableType(searchParams) !== 'object') {
774		throw new TypeError("'searchParams' must be an 'Object'");
775	}
776
777	if (['string', 'number'].includes(typeof storeCode) && !('Store_Code' in searchParams)) {
778		searchParams = {
779			Store_Code: storeCode,
780			...searchParams
781		};
782	}
783
784	Object.entries(searchParams).forEach(([key, value]) => {
785		url.searchParams.append(key, value);
786	});
787
788	return url.toString();
789};
790
791MMX.pluralize = (singular, count, plural) => {
792	return count === 1 ? `${singular}` : plural ?? `${singular}s`;
793};
794
795MMX.setWindowLocation = (href) => {
796	window.location.href = href;
797};
798
799class MMX_Element extends HTMLElement {
800
801	themeResourcePattern;
802	styleResourceCodes = ['mmx-base'];
803	hideOnEmpty = false;
804	renderUniquely = false;
805	lifecycleReady = true;
806
807	static baseProps = {
808		init: {
809			isObject: true,
810			isJson: true,
811			options: [
812				'script',
813				'innerHTML'
814			],
815			default: null
816		}
817	};
818
819	createElement = MMX.createElement;
820	data = this.data || {};
821
822	/**
823	 * Web Component Lifecycle Hooks
824	 */
825
826	constructor() {
827		super();
828	}
829
830	makeComponent() {
831		this.makeShadow();
832		this.makeStates();
833	}
834
835	makeShadow() {
836		this.attachShadow({
837			mode: 'open'
838		});
839	}
840
841	makeStates() {
842		this.makeInternals();
843		this.setInitialStates();
844	}
845
846	connectedCallback() {
847		// Render Immediately
848		this.#adoptStyleSheets();
849		this.renderTemplate();
850		this.connected?.();
851
852		// Re-Render onload to catch when component's inner DOM is not completely available at connectedCallback()
853		this.ownerDocument.addEventListener('DOMContentLoaded', () => {
854			this.renderOnFrame();
855		});
856
857		// Re-Render when the innerHTML / child-nodes are updated
858		this.observer = new MutationObserver((records) => {
859			const validRecords = records.filter(record => {
860				if (MMX.closestElement('.mmx-skip-mutation', record.target)) {
861					return false;
862				}
863
864				return ![...record.addedNodes, ...record.removedNodes].find(node => {
865					return MMX.closestElement('.mmx-skip-mutation', node);
866				});
867			});
868
869			if (!validRecords.length) {
870				return;
871			}
872
873			this.renderOnFrame();
874		});
875
876		this.observer.observe(this, {
877			subtree: true,
878			characterData: true,
879			childList: true
880		});
881	}
882
883	disconnectedCallback() {
884		this.disconnected?.();
885		this.observer?.disconnect();
886	}
887
888	// Re-Render when attributes are modified
889	attributeChangedCallback() {
890		this.attributeChanged?.(...arguments);
891		this.debouncedRender();
892	}
893
894	static get observedAttributes() {
895		return this.propsToAttributeNames;
896	}
897
898	static get propsToAttributeNames() {
899		if (typeof this.props !== 'object') {
900			return [];
901		}
902
903		return Object.keys(this.props).map(prop => 'data-' + prop);
904	}
905
906	/**
907	 * Internals
908	 */
909
910	#internals;
911
912	makeInternals() {
913		try {
914			this.#internals = this.attachInternals();
915		}
916		catch {
917			// already attached
918		}
919	}
920
921	/**
922	 * Form
923	 */
924
925	setFormValue(...args) {
926		return this.constructor.formAssociated ? this.#internals?.setFormValue(...args) : undefined;
927	}
928
929	setValidity(...args) {
930		return this.constructor.formAssociated ? this.#internals?.setValidity(...args) : undefined;
931	}
932
933	checkValidity() {
934		return this.constructor.formAssociated ? this.#internals?.checkValidity() : undefined;
935	}
936
937	reportValidity() {
938		return this.constructor.formAssociated ? this.#internals?.reportValidity() : undefined;
939	}
940
941	get validity() {
942		return this.constructor.formAssociated ? this.#internals?.validity : undefined;
943	}
944
945	get validationMessage() {
946		return this.constructor.formAssociated ? this.#internals?.validationMessage : undefined;
947	}
948
949	/**
950	 * States
951	 */
952
953	setInitialStates() {
954		this.addStates(['mmx', 'initial']);
955	}
956
957	addState(state) {
958		this.#internals?.states?.add?.(state);
959	}
960
961	addStates(states = []) {
962		states.forEach(state => {
963			this.#internals?.states?.add?.(state);
964		});
965	}
966
967	removeState(state) {
968		this.#internals?.states?.delete?.(state);
969	}
970
971	clearStates() {
972		this.#internals?.states?.clear?.();
973	}
974
975	/**
976	 * Render Functions
977	 */
978	renderOnFrame() {
979		if (this.requestId) {
980			window.cancelAnimationFrame(this.requestId);
981		}
982
983		this.requestId = window.requestAnimationFrame(() => {
984			this.renderTemplate();
985		});
986	}
987
988	debouncedRender = MMX.debounce(() => {
989		this.renderOnFrame();
990	}, 50);
991
992	renderTemplate() {
993		this.initData();
994		this.checkDataChange();
995
996		const element = this.shadowRoot || this;
997		const template =  this.getTemplate();
998		const renderRequired = template !== this?.lastTemplate;
999
1000		if (this.renderUniquely && !renderRequired) {
1001			return;
1002		}
1003
1004		this.beforeRender?.();
1005		MMX.renderTemplate(element, template);
1006		this.lastTemplate = template;
1007
1008		this.addState('rendered');
1009
1010		if (this.isLifecycleReady()) {
1011			this.addState('ready');
1012		}
1013
1014		this.afterRender?.();
1015		this.debouncedDispatchContentUpdated();
1016	}
1017
1018	isLifecycleReady() {
1019		return this.lifecycleReady;
1020	}
1021
1022	setLifecycleReady(value = true) {
1023		this.lifecycleReady = Boolean(value);
1024	}
1025
1026	debouncedDispatchContentUpdated = MMX.debounce(() => {
1027		this.dispatchContentUpdated();
1028	}, 100);
1029
1030	dispatchContentUpdated() {
1031		this.dispatchEvent(new CustomEvent('contentUpdated'));
1032	}
1033
1034	getTemplate() {
1035		if (this.hideOnEmpty && this.isEmpty() && this.slotsAreEmpty()) {
1036			return '';
1037		}
1038		return this.renderStyles() + this.output('render');
1039	}
1040
1041	isEmpty() {
1042		return this.textContent.trim().length === 0;
1043	}
1044
1045	slotsAreEmpty() {
1046		return !this.slotsHaveContent();
1047	}
1048
1049	slotsHaveContent() {
1050		return Array.from(this.querySelectorAll('slot')).some(slot => {
1051			return Array.from(slot.assignedElements()).some(element => {
1052				return this.hasTextContent(element);
1053			});
1054		});
1055	}
1056
1057	hasTextContent(element = this) {
1058		return element?.textContent?.trim?.()?.length > 0;
1059	}
1060
1061	forceUpdate() {
1062		this.setLifecycleReady(true);
1063
1064		if (!this.lastTemplate) {
1065			return;
1066		}
1067
1068		this.lastTemplate = null;
1069		this.renderTemplate();
1070	}
1071
1072	checkDataChange() {
1073		const currentDataString = JSON.stringify(this.data);
1074		const sameData = currentDataString === this.lastDataString;
1075		if (currentDataString !== '{}' && !sameData) {
1076			this.lastDataString = currentDataString;
1077			this.onDataChange?.();
1078		}
1079	}
1080
1081	initData() {
1082		const init = this.loadPropertyData('init');
1083
1084		if (MMX.variableType(init) !== 'object' || MMX.isEqual(this.data, init)) {
1085			return;
1086		}
1087
1088		this.data = init;
1089	}
1090
1091	/**
1092	 * Utility / Helper Functions
1093	 */
1094
1095	on(events, selector, handler, options = {}) {
1096		return MMX.on(events, selector, handler, {
1097			root: this.shadowRoot || this,
1098			...options
1099		});
1100	}
1101
1102	output(name) {
1103		return typeof this[name] === 'function' ? this[name]() : '';
1104	}
1105
1106	getPropValueWithoutDefault(name) {
1107		const prop = this.constructor.props[name] || MMX_Element.baseProps[name];
1108		const value = name in this.data ? this.data[name] : (this.getAttribute('data-' + name) || '').trim();
1109
1110		if (typeof prop === 'undefined' || !value?.length) {
1111			return null;
1112		}
1113
1114		if (prop?.options?.length && prop.options.includes(value)) {
1115			return value;
1116		}
1117
1118		if (prop?.isBoolean) {
1119			return MMX.isTruthy(value);
1120		}
1121
1122		if (prop?.isObject && typeof value === 'object') {
1123			return value;
1124		}
1125
1126		if (prop?.isJson && typeof value === 'string') {
1127			try {
1128				return JSON.parse(value);
1129			} catch(err) {
1130				return null;
1131			}
1132		}
1133
1134		if (prop.allowAny) {
1135			if (prop.isNumeric && !isNaN(value)) {
1136				return Number(value);
1137			}
1138
1139			if (prop.isPercentage && !isNaN(value)) {
1140				return value + '%';
1141			}
1142
1143			return value;
1144		}
1145
1146		return null;
1147	}
1148
1149	hasPropValue(name) {
1150		const propValue = this.getPropValueWithoutDefault(name);
1151		const propValueType = MMX.variableType(propValue);
1152		return propValueType !== 'null' && propValueType !== 'undefined';
1153	}
1154
1155	getPropValue(name) {
1156		return this.getPropValueWithoutDefault(name) ?? this.constructor.props[name]?.default;
1157	}
1158
1159	inheritAttrs() {
1160		const attributeNames = this.getAttributeNames();
1161
1162		if (!attributeNames.length) {
1163			return '';
1164		}
1165
1166		return attributeNames.reduce((attributes, attributeName) => {
1167			const attributeValue = this.getAttribute(attributeName);
1168
1169			if (attributeName === 'part' || attributeName.indexOf('data-') === 0) {
1170				return attributes;
1171			}
1172
1173			if (attributeValue) {
1174				attributes.push(`${attributeName}="${MMX.encodeEntities(attributeValue)}"`);
1175			} else {
1176				attributes.push(attributeName);
1177			}
1178
1179			return attributes;
1180		}, []).join(' ');
1181	}
1182
1183	propExists() {
1184		return propKey in this.constructor.props || propKey in MMX_Element.baseProps;
1185	}
1186
1187	loadPropertyData(propKey) {
1188		const data = this.getPropValue(propKey);
1189
1190		if (!data) {
1191			return;
1192		}
1193
1194		if (data === 'innerHTML' ){
1195			return this.parseData(this.innerHTML);
1196		}
1197
1198		if (data === 'script'){
1199			let script = this.querySelector('script');
1200			return script ? this.parseData(script.innerHTML) : {};
1201		}
1202
1203		return this.parseData(data);
1204	}
1205
1206	parseData(data) {
1207		if (typeof data === 'object') {
1208			return data;
1209		}
1210
1211		try {
1212			return JSON.parse(data);
1213		} catch(err) {
1214			return null;
1215		}
1216	}
1217
1218	/**
1219	 * Visibility Helpers
1220	 */
1221	getLoadingAttributeString() {
1222		const {loading, fetchpriority} = this.getLoadingAttributes();
1223		return `loading="${loading}" fetchpriority="${fetchpriority}"`;
1224	}
1225
1226	getLoadingAttributes() {
1227		const isInViewport = MMX.elementInViewport(this);
1228
1229		return {
1230			loading: this.getLoading(isInViewport),
1231			fetchpriority: this.getFetchPriority(isInViewport)
1232		};
1233	}
1234
1235	getFetchPriority(isInViewport) {
1236		return (isInViewport || MMX.elementInViewport(this)) ? 'high' : 'low';
1237	}
1238
1239	getLoading(isInViewport) {
1240		return (isInViewport || MMX.elementInViewport(this)) ? 'eager' : 'lazy';
1241	}
1242
1243	/**
1244	 * Rendering Helpers
1245	 */
1246	renderTextProperty(property = {}, {className = '', prefix = '', field = 'normal', defaultStyle = 'paragraph-s', defaultTag = 'div'}) {
1247		if (MMX.valueIsEmpty(property?.value)) {
1248			return '';
1249		}
1250
1251		const theme_available = property?.textsettings?.fields?.[field]?.typography_theme?.theme_available ?? false;
1252
1253		const text = MMX.createElement({
1254			type: 'mmx-text',
1255			attributes: {
1256				class: className,
1257				'data-source': property.source,
1258				'data-theme': theme_available,
1259				'data-theme-class': property?.textsettings?.fields?.[field]?.typography_theme?.classname ?? '',
1260				'data-style': property?.textsettings?.fields?.[field]?.[`${prefix}style`]?.value || defaultStyle,
1261				'data-tag': property?.textsettings?.fields?.[field]?.[`${prefix}tag`]?.value || defaultTag,
1262			},
1263			content: `
1264				${this.renderLegacyStylesTemplate(theme_available, property?.textsettings?.styles?.[field] ?? '')}
1265				${this.renderThemeStylesheetTemplate(theme_available)}
1266				${property.source === 'markdown' ? property.value : MMX.encodeEntities(property.value)}
1267			`
1268		});
1269
1270		return text.outerHTML;
1271	}
1272
1273	renderButtonProperty(property = {}, {className = '', field = 'normal', prefix = 'button_', defaultStyle = 'primary', defaultSize = 's'}) {
1274		if (MMX.valueIsEmpty(property?.value)) {
1275			return '';
1276		}
1277
1278		const button = MMX.createElement({
1279			type: 'mmx-button',
1280			attributes: {
1281				class: className,
1282				'data-style': property?.textsettings?.fields?.[field]?.[`${prefix}style`]?.value || defaultStyle,
1283				'data-size': property?.textsettings?.fields?.[field]?.[`${prefix}size`]?.value || defaultSize,
1284				'data-theme': property?.textsettings?.fields?.[field]?.[`${prefix}theme`]?.theme_available ?? false,
1285				'data-theme-class': property?.textsettings?.fields?.[field]?.[`${prefix}theme`]?.classname || ''
1286			},
1287			content: `
1288				${this.renderThemeStylesheetTemplate(property?.textsettings?.fields?.[field]?.[`${prefix}theme`]?.theme_available || false)}
1289				${MMX.encodeEntities(property.value)}
1290			`
1291		});
1292
1293		return button.outerHTML;
1294	}
1295
1296	renderThemeStylesheetTemplate(theme_available) {
1297		if (!theme_available) {
1298			return '';
1299		}
1300
1301		return /*html*/`
1302			<template data-theme-stylesheet>
1303				${this.querySelector(':scope > template[data-theme-stylesheet]')?.content?.textContent ?? ''}
1304			</template>
1305		`;
1306	}
1307
1308	renderLegacyStylesTemplate(theme_available, styles) {
1309		if (theme_available || !styles?.length) {
1310			return '';
1311		}
1312
1313		return /*html*/`
1314			<template data-legacy-styles>
1315				${styles}
1316			</template>
1317		`;
1318	}
1319
1320	getStylesFromGroup(group = {}) {
1321		return Object.keys(group).reduce((styles, key) => {
1322			if (key === 'style' || key === 'button_theme' || key === 'typography_theme') {
1323				return styles;
1324			}
1325
1326			let newStyles = [];
1327
1328			if (key === 'font') {
1329				if (!MMX.valueIsEmpty(group[key]?.font?.family))	newStyles.push(`font-family: ${group[key]?.font.family}`);
1330				if (!MMX.valueIsEmpty(group[key]?.weight))			newStyles.push(`font-weight: ${group[key]?.weight}`);
1331				if (!MMX.valueIsEmpty(group[key]?.style))			newStyles.push(`font-style: ${group[key]?.style}`);
1332			}
1333			else {
1334				let value = group[key]?.value;
1335
1336				if (!MMX.valueIsEmpty(value)) {
1337					if (key === 'font_size') {
1338						value += 'px';
1339					}
1340
1341					if (key === 'font_color') {
1342						key = 'color';
1343					}
1344
1345					newStyles.push(`${MMX.snakeToKebabCase(key)}: ${value}`);
1346				}
1347			}
1348
1349			if (newStyles.length === 0) {
1350				return styles;
1351			}
1352
1353			if (styles.length) {
1354				styles += '; ';
1355			}
1356
1357			return `${styles}${newStyles.join(';')}`;
1358		}, '');
1359	}
1360
1361	/**
1362	 * Fragment Helpers
1363	 */
1364	renderContentIntoLightDomSlot({slotName, content} = {}) {
1365		if (typeof slotName !== 'string' || typeof content !== 'string') {
1366			return '';
1367		}
1368
1369		const existingLightElement = this.querySelector(`[slot="${MMX.encodeEntities(slotName)}"]`);
1370		existingLightElement?.remove();
1371
1372		const contentFragment = document.createRange().createContextualFragment(content);
1373		const lightElement = MMX.createElement({
1374			type: 'div',
1375			attributes: {
1376				slot: slotName,
1377				class: 'mmx-skip-mutation',
1378			}
1379		});
1380		lightElement.appendChild(contentFragment);
1381		this.appendChild(lightElement);
1382
1383		return /*html*/`
1384			<slot name="${MMX.encodeEntities(slotName)}"></slot>
1385		`;
1386	}
1387
1388	renderProductFragment({product, fragmentCode, slotName} = {}) {
1389		const fragmentContent = product?.fragments?.[fragmentCode]?.trim?.();
1390		slotName = slotName ?? `fragment__${fragmentCode}--${product?.code}`;
1391
1392		if (MMX.valueIsEmpty(fragmentContent)) {
1393			return '';
1394		}
1395
1396		return this.renderContentIntoLightDomSlot({
1397			slotName,
1398			content: fragmentContent
1399		});
1400	}
1401
1402	/**
1403	 * Responsive Helper
1404	 */
1405	getResponsiveImageHeight(img) {
1406		const widthRatio = img.width / img.naturalWidth;
1407		return img.naturalHeight * widthRatio;
1408	}
1409
1410	/**
1411	 * CSS Helper Functions
1412	 */
1413
1414	setSpacing(settings = {}) {
1415		MMX.setElementStyles({
1416			element: this,
1417			styles: {
1418				marginTop: settings?.top?.value,
1419				marginBottom: settings?.bottom?.value,
1420				marginRight: settings?.right?.value,
1421				marginLeft: settings?.left?.value
1422			},
1423			suffix: 'px'
1424		});
1425	}
1426
1427	/**
1428	 * Stylesheet Functions
1429	 */
1430
1431	#adoptStyleSheets() {
1432		return MMX.styleManager.adoptStyleSheets(this.shadowRoot, this.styleResourceCodes)
1433			.finally(() => {
1434				this.addStates(['styled', 'adopted-styles']);
1435			});
1436	}
1437
1438	renderStyles() {
1439		return /*html*/`
1440			${this.renderStyleElements()}
1441			${this.renderStylesheetLinks()}
1442			<style>
1443				${this.output('styles')}
1444			</style>
1445		`;
1446	}
1447
1448	renderStyleElements() {
1449		const mmxStyleElements = [...document.querySelectorAll('style[data-resource-code]')].filter(style => {
1450			return this.#shouldRenderStyleElement(style);
1451		});
1452
1453		return mmxStyleElements.map(this.renderStyleElement).join('\n');
1454	}
1455
1456	#shouldRenderStyleElement(style) {
1457		return this.shouldIncludeStyleElement(style) && !MMX.styleManager.isAdoptableResource(style);
1458	}
1459
1460	shouldIncludeStyleElement(style) {
1461		return (style.hasAttribute('data-resource-code') && this.styleResourceCodes.indexOf(style.getAttribute('data-resource-code')) !== -1);
1462	}
1463
1464	renderStyleElement(style) {
1465		return style.outerHTML;
1466	}
1467
1468	renderStylesheetLinks() {
1469		const availableSheets = [...document.querySelectorAll('link[rel="stylesheet"][href]')];
1470		[...document.querySelectorAll('template.mmx-resources')].forEach(template => {
1471			const templateLinks = template.content.querySelectorAll('link[rel="stylesheet"][href]');
1472			if (templateLinks.length) {
1473				availableSheets.push(...templateLinks);
1474			}
1475		});
1476
1477		const mmxStyleSheets = availableSheets.filter(sheet => {
1478			return this.#shouldRenderStylesheetLink(sheet);
1479		});
1480		return mmxStyleSheets.map(this.renderStylesheetLink).join('\n');
1481	}
1482
1483	#shouldRenderStylesheetLink(sheet) {
1484		return this.shouldIncludeSheet(sheet) && !MMX.styleManager.isAdoptableResource(sheet);
1485	}
1486
1487	shouldIncludeSheet(sheet) {
1488		return ((MMX.variableType(this.themeResourcePattern) === 'regexp' && sheet.href && (this.themeResourcePattern).test(sheet.href)) || (sheet.hasAttribute('data-resource-code') && this.styleResourceCodes.indexOf(sheet.getAttribute('data-resource-code')) !== -1));
1489	}
1490
1491	renderStylesheetLink(sheet) {
1492		return sheet.outerHTML;
1493	}
1494
1495	/**
1496	 * Reveal Element Functions
1497	 */
1498	bindRevealElement() {
1499		this.addEventListener('revealElement', (e) => {
1500			this.onRevealElement(e);
1501		});
1502	}
1503
1504	onRevealElement(e) {
1505		this.revealElementFromSelectorList(e.detail?.preview_property_selector);
1506	}
1507
1508	revealElementFromSelectorList(selectors) {
1509		const element = MMX.querySelectorList(selectors, this);
1510
1511		if (!element) {
1512			return;
1513		}
1514
1515		this.revealElement?.(element);
1516	}
1517}
1518
1519window.MMX = MMX;
1520window.MMX_FetchQueue = MMX_FetchQueue;
1521window.MMX_StyleManager = MMX_StyleManager;
1522window.MMX_Element = MMX_Element;

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.