PageSourceSearch

https://porpatas.es/wp-content/themes/xpatas-theme/assets/js/main.js?ver=1.0.0.1791442270

js porpatas.es collected 2026-10-09 21:59:56 UTC 12,062 bytes, 258 lines download raw bytes

1/* XPATAS — interacciones. Sin dependencias salvo jQuery (lo exige WooCommerce para sus eventos). */
2(function ($) {
3	'use strict';
4
5	var cfg = window.XPATAS || {};
6	var body = document.body;
7	var scrim = document.querySelector('.scrim');
8	var nav = document.getElementById('site-nav');
9	var drawer = document.getElementById('cart-drawer');
10
11	/* ---------- Paneles (menú móvil y carrito) ---------- */
12	function openPanel(el) {
13		if (!el) return;
14		closePanels();
15		el.classList.add('is-open');
16		el.setAttribute('aria-hidden', 'false');
17		if (scrim) scrim.classList.add('is-on');
18		// El menú vive dentro de la cabecera: hay que subirla por encima del fondo oscuro o no se puede tocar.
19		body.classList.toggle('nav-open', el === nav);
20		body.style.overflow = 'hidden';
21		var focusable = el.querySelector('button, a, input');
22		if (focusable) focusable.focus({ preventScroll: true });
23	}
24	function closePanels() {
25		[nav, drawer].forEach(function (el) {
26			if (el) { el.classList.remove('is-open'); }
27		});
28		if (drawer) drawer.setAttribute('aria-hidden', 'true');
29		if (scrim) scrim.classList.remove('is-on');
30		body.classList.remove('nav-open');
31		body.style.overflow = '';
32		document.querySelectorAll('[data-open="nav"]').forEach(function (b) { b.setAttribute('aria-expanded', 'false'); });
33	}
34
35	document.addEventListener('click', function (e) {
36		var opener = e.target.closest('[data-open]');
37		if (opener) {
38			var which = opener.getAttribute('data-open');
39			if (which === 'cart' && drawer) { e.preventDefault(); openPanel(drawer); }
40			if (which === 'nav' && nav) { opener.setAttribute('aria-expanded', 'true'); openPanel(nav); }
41			return;
42		}
43		if (e.target.closest('[data-close]')) { closePanels(); return; }
44
45		var st = e.target.closest('[data-toggle-search]');
46		if (st) {
47			var panel = document.getElementById('header-search');
48			var open = panel.classList.toggle('is-open');
49			st.setAttribute('aria-expanded', open ? 'true' : 'false');
50			if (open) panel.querySelector('input[type=search]').focus();
51		}
52	});
53	document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closePanels(); });
54
55	/* ---------- Mega menú ----------
56	   Escritorio: se abre al pasar el ratón o con el teclado (CSS :hover/:focus-within) y también al pulsar
57	   la flecha (pantallas táctiles). Móvil: acordeón. */
58	document.querySelectorAll('.submenu-toggle').forEach(function (btn) {
59		btn.addEventListener('click', function () {
60			var li = btn.closest('li');
61			var open = !li.classList.contains('is-open');
62			li.parentElement.querySelectorAll(':scope > li.is-open').forEach(function (o) { if (o !== li) { o.classList.remove('is-open'); o.querySelector('.submenu-toggle').setAttribute('aria-expanded', 'false'); } });
63			li.classList.toggle('is-open', open);
64			btn.setAttribute('aria-expanded', open ? 'true' : 'false');
65		});
66	});
67	document.addEventListener('click', function (e) {
68		if (!e.target.closest('.has-mega')) document.querySelectorAll('.has-mega.is-open').forEach(function (o) { o.classList.remove('is-open'); });
69	});
70
71	/* ---------- Carrito: abrir al añadir ---------- */
72	if ($ && drawer) {
73		$(document.body).on('added_to_cart', function () { openPanel(drawer); });
74	}
75
76	/* ---------- Añadir al carrito sin recargar en la ficha ----------
77	   Enviamos el formulario tal cual a la misma URL (lo procesa WooCommerce como siempre,
78	   con todas sus validaciones y extensiones) y refrescamos los fragments. Si algo falla,
79	   se hace el envío normal. */
80	document.addEventListener('submit', function (e) {
81		var form = e.target;
82		if (!form.matches || !form.matches('form.cart') || !drawer || !window.fetch || form.hasAttribute('data-no-ajax')) return;
83		if (form.closest('.product-type-external') || form.querySelector('input[type=file]')) return;
84		var btn = form.querySelector('.single_add_to_cart_button');
85		if (!btn || btn.classList.contains('disabled')) return;
86
87		e.preventDefault();
88		var data = new FormData(form);
89		if (!data.has('add-to-cart') && btn.name === 'add-to-cart') data.append('add-to-cart', btn.value);
90		var label = btn.innerHTML;
91		btn.classList.add('loading');
92		btn.innerHTML = cfg.i18n ? cfg.i18n.adding : '…';
93
94		fetch(form.action || window.location.href, { method: 'POST', body: data, credentials: 'same-origin' })
95			.then(function (r) { return r.text(); })
96			.then(function (html) {
97				var doc = new DOMParser().parseFromString(html, 'text/html');
98				var err = doc.querySelector('.woocommerce-error, .wc-block-components-notice-banner.is-error');
99				if (err) {
100					var holder = document.querySelector('.woocommerce-notices-wrapper') || form.parentElement;
101					holder.innerHTML = err.outerHTML;
102					holder.scrollIntoView({ behavior: 'smooth', block: 'center' });
103					return;
104				}
105				$(document.body).one('wc_fragments_refreshed', function () { openPanel
105(drawer); });
106				$(document.body).trigger('wc_fragment_refresh');
107				if (window.dataLayer) window.dataLayer.push({ event: 'xpatas_add_to_cart' });
108			})
109			.catch(function () { form.setAttribute('data-no-ajax', ''); form.submit(); })
110			.finally(function () { btn.classList.remove('loading'); btn.innerHTML = label; });
111	});
112
113	/* ---------- Cantidad +/- ---------- */
114	document.querySelectorAll('form.cart .quantity').forEach(function (q) {
115		var input = q.querySelector('.qty');
116		if (!input || input.type === 'hidden') return;
117		function mk(txt, delta, label) {
118			var b = document.createElement('button');
119			b.type = 'button'; b.className = 'qty-btn'; b.textContent = txt; b.setAttribute('aria-label', label);
120			b.addEventListener('click', function () {
121				var min = parseFloat(input.min) || 1, max = parseFloat(input.max) || Infinity, step = parseFloat(input.step) || 1;
122				var v = Math.min(max, Math.max(min, (parseFloat(input.value) || min) + delta * step));
123				input.value = v;
124				input.dispatchEvent(new Event('change', { bubbles: true }));
125			});
126			return b;
127		}
128		q.insertBefore(mk('−', -1, 'Quitar uno'), input);
129		q.appendChild(mk('+', 1, 'Añadir uno'));
130	});
131
132	/* ---------- Variaciones como botones ---------- */
133	function buildSwatches(form) {
134		form.querySelectorAll('.variations select').forEach(function (select) {
135			var td = select.closest('td');
136			if (!td || td.querySelector('.swatches') || select.options.length > 13) return;
137			td.classList.add('has-swatches');
138			var wrap = document.createElement('div');
139			wrap.className = 'swatches';
140			wrap.setAttribute('role', 'radiogroup');
141			Array.prototype.forEach.call(select.options, function (opt) {
142				if (!opt.value) return;
143				var b = document.createElement('button');
144				b.type = 'button'; b.className = 'swatch'; b.textContent = opt.text; b.dataset.value = opt.value;
145				b.setAttribute('role', 'radio');
146				b.addEventListener('click', function () {
147					if (b.classList.contains('is-disabled')) return;
148					$(select).val(select.value === opt.value ? '' : opt.value).trigger('change');
149				});
150				wrap.appendChild(b);
151			});
152			td.insertBefore(wrap, select);
153		});
154		syncSwatches(form);
155	}
156	function syncSwatches(form) {
157		form.querySelectorAll('.variations select').forEach(function (select) {
158			var wrap = select.closest('td').querySelector('.swatches');
159			if (!wrap) return;
160			var enabled = {};
161			Array.prototype.forEach.call(select.options, function (o) { if (o.value && !o.disabled) enabled[o.value] = true; });
162			wrap.querySelectorAll('.swatch').forEach(function (b) {
163				var sel = b.dataset.value === select.value;
164				b.classList.toggle('is-selected', sel);
165				b.setAttribute('aria-checked', sel ? 'true' : 'false');
166				b.classList.toggle('is-disabled', !enabled[b.dataset.value]);
167			});
168		});
169	}
170	if ($) {
171		$('form.variations_form').each(function () { buildSwatches(this); });
172		$(document.body).on('woocommerce_update_variation_values check_variations reset_data found_variation', 'form.variations_form', function () { syncSwatches(this); });
173		$(document.body).on('change', 'form.variations_form .variations select', function () { syncSwatches(this.form); });
174	}
175
176	/* ---------- Fecha estimada de entrega ----------
177	   Se calcula en el navegador para que la caché de página no muestre fechas viejas. */
178	function madridNow() {
179		try {
180			var s = new Date().toLocaleString('en-US', { timeZone: 'Europe/Madrid' });
181			return new Date(s);
182		} catch (e) { return new Date(); }
183	}
184	function isWorkday(d) {
185		var iso = d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
186		return d.getDay() !== 0 && d.getDay() !== 6 && (cfg.holidays || []).indexOf(iso) === -1;
187	}
188	function addWorkdays(d, n) {
189		var r = new Date(d);
190		while (n > 0) { r.setDate(r.getDate() + 1); if (isWorkday(r)) n--; }
191		return r;
192	}
193	/* Cada producto trae su plazo (data-min/max en días laborables) según su clase de envío.
194	   La hora de corte solo se anuncia en los de entrega rápida. */
195	document.querySelectorAll('[data-eta]').forEach(function (el) {
196		var min = parseInt(el.dataset.min, 10), max = parseInt(el.dataset.max, 10);
197		if (!min || !max) return;
198		var fast = el.dataset.national === '1';
199		var days = (el.dataset.days || '1,2,3,4,5').split(',').map(Number);
200		var now = madridNow();
201		var cutoff = parseInt(cfg.cutoffHour, 10) || 13;
202		var isShipDay = function (d) { return isWorkday(d) && days.indexOf(d.getDay()) !== -1; };
203		var shipsToday = isShipDay(now) && now.getHours() < cutoff;
204		var start = new Date(now);
205		if (!shipsToday) { do { start.setDate(start.getDate() + 1); }
205 while (!isShipDay(start)); }
206		var everyDay = days.length >= 5;
207		var from = addWorkdays(start, min), to = addWorkdays(start, max);
208		var fmt = new Intl.DateTimeFormat('es-ES', fast ? { weekday: 'long', day: 'numeric', month: 'long' } : { day: 'numeric', month: 'long' });
209		var when = from.toDateString() === to.toDateString() ? 'el ' + fmt.format(from) : 'entre el ' + fmt.format(from) + ' y el ' + fmt.format(to);
210		var dayFmt = new Intl.DateTimeFormat('es-ES', { weekday: 'long', day: 'numeric' });
211		var txt;
212		if (fast && shipsToday) txt = 'Pídelo antes de las ' + cutoff + ':00 y recíbelo ' + when;
213		else if (fast && !everyDay) txt = 'Sale el ' + dayFmt.format(start) + ' · recíbelo ' + when;
214		else txt = 'Recíbelo ' + when;
215		el.innerHTML = '<span class="eta"></span>';
216		el.firstChild.textContent = txt;
217		if (!fast) el.firstChild.classList.add('eta--slow');
218	});
219
220	/* ---------- Barra fija de compra (móvil) ---------- */
221	var sticky = document.querySelector('.sticky-atc');
222	var mainBtn = document.querySelector('.summary .single_add_to_cart_button');
223	if (sticky && mainBtn && 'IntersectionObserver' in window) {
224		new IntersectionObserver(function (entries) {
225			var e = entries[0];
226			var past = !e.isIntersecting && e.boundingClientRect.top < 0;
227			sticky.classList.toggle('is-visible', past);
228			sticky.setAttribute('aria-hidden', past ? 'false' : 'true');
229		}).observe(mainBtn);
230		sticky.querySelector('[data-sticky-atc]').addEventListener('click', function () {
231			var form = mainBtn.form;
232			if (form && form.classList.contains('variations_form') && mainBtn.classList.contains('disabled')) {
233				form.scrollIntoView({ behavior: 'smooth', block: 'center' });
234			} else {
235				mainBtn.click();
236			}
237		});
238	}
239	document.querySelectorAll('[data-scroll-buy]').forEach(function (a) {
240		a.addEventListener('click', function (ev) {
241			var target = mainBtn && (mainBtn.form || mainBtn);
242			if (!target) return;
243			ev.preventDefault();
244			target.scrollIntoView({ behavior: 'smooth', block: 'center' });
245		});
246	});
247})(window.jQuery);
248
249/* Checkout: añadir un producto de "Completa tu pedido" sin salir de la página */
250(function ($) {
251	if (!$ || typeof wc_checkout_params === 'undefined') return;
252	$(document.body).on('click', '[data-co-add]', function () {
253		var btn = $(this);
254		btn.prop('disabled', true).text('Añadiendo…');
255		$.post(wc_checkout_params.wc_ajax_url.toString().replace('%%endpoint%%', 'add_to_cart'), { product_id: btn.data('co-add'), quantity: 1 })
256			.always(function () { $(document.body).trigger('update_checkout'); });
257	});
258})(window.jQuery);

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.