PageSourceSearch

https://torresdelpaine.com/wp-content/plugins/greatchile-core/js/gc-stepper.js?ver=0.5

js torresdelpaine.com collected 2026-10-01 11:58:24 UTC 6,876 bytes, 165 lines download raw bytes

1/**
2 * Front-end number-input stepper: wraps a number input in a Bootstrap
3 * input-group with "-" (before) and "+" (after) buttons.
4 *
5 * Progressive enhancement — the native input keeps working if this
6 * script fails, and it covers every source (PHP templates, CF7
7 * form-tags, and JS-injected DOM like the reprice-refreshed variation
8 * table / cart) via a MutationObserver, with no per-form wiring.
9 *
10 * Targets:
11 *   - input[type=number].gc-stepper   (marked template / cart inputs)
12 *   - input[type=number].wpcf7-number (all CF7 pax/number fields)
13 * Opt out per input with class `gc-no-stepper`.
14 *
15 * CRITICAL: the +/- buttons dispatch native `input` + `change` events,
16 * so live-pricing subscribers (cart reprice, 2-step recompute, etc.)
17 * fire exactly as if the operator typed — never set `.value` silently.
18 */
19(function () {
20	'use strict';
21
22	var SELECTOR = 'input[type="number"].gc-stepper, input[type="number"].wpcf7-number';
23	var READY_ATTR = 'data-gc-stepper-ready';
24	var i18n = window.gcStepper || {};
25
26	function toNum(v, fallback) {
27		var n = parseFloat(v);
28		return isNaN(n) ? fallback : n;
29	}
30
31	// The value an EMPTY field should start on: its explicit default (the HTML
32	// `value` attribute) if any, else its `min` WHEN that min is a positive,
33	// required floor (e.g. min:1). Returns NaN for optional fields (no default,
34	// min 0 / none) — those stay blank so we never invent a "0"/"1" the form
35	// treats as unset (e.g. the multi-option pax fields, "at least one ≥ 1").
36	function fieldStart(input) {
37		var def = toNum(input.defaultValue, NaN);
38		if (!isNaN(def)) return def;
39		var min = toNum(input.getAttribute('min'), NaN);
40		return (!isNaN(min) && min > 0) ? min : NaN;
41	}
42
43	function stepValue(input, dir) {
44		var stepSize = toNum(input.getAttribute('step'), 1) || 1;
45		var min = toNum(input.getAttribute('min'), NaN);
46		var max = toNum(input.getAttribute('max'), NaN);
47		var raw = String(input.value);
48		var hasVal = raw.trim() !== '' && !isNaN(parseFloat(raw));
49		var next;
50		if (hasVal) {
51			next = parseFloat(raw) + dir * stepSize;
52		} else {
53			// Empty: the first press lands ON the starting value, never a step
54			// PAST it — so "+" on an empty min:1 field shows 1, not 2. Optional
55			// fields (fieldStart = NaN): "+" adds one unit, "−" goes to the floor.
56			var start = fieldStart(input);
57			if (!isNaN(start)) next = start;
58			else next = (dir > 0) ? stepSize : (isNaN(min) ? 0 : min);
59		}
60		if (!isNaN(min) && next < min) next = min;
61		if (!isNaN(max) && next > max) next = max;
62		if (hasVal && next === parseFloat(raw)) return;
63		input.value = String(next);
64		// Re-dispatch so dependent widgets (repricing, validity) react.
65		input.dispatchEvent(new Event('input', { bubbles: true }));
66		input.dispatchEvent(new Event('change', { bubbles: true }));
67	}
68
69	function button(dir) {
70		var b = document.createElement('button');
71		b.type = 'button';
72		b.className = 'btn btn-outline-secondary gc-stepper-btn gc-stepper-' + (dir < 0 ? 'down' : 'up');
73		// FontAwesome glyph (core-enqueued site-wide) so the buttons match the
74		// rest of the theme's icon-based nav/controls. Icon is decorative — the
75		// button carries the accessible label.
76		var icon = document.createElement('i');
77		icon.className = dir < 0 ? 'fa fa-minus' : 'fa fa-plus';
78		icon.setAttribute('aria-hidden', 'true');
79		b.appendChild(icon);
80		b.setAttribute('aria-label', dir < 0 ? (i18n.dec || 'Decrease') : (i18n.inc || 'Increase'));
81		b.tabIndex = -1; // keyboard users use the input's native arrows
82		return b;
83	}
84
85	function enhance(input) {
86		if (!input || input.getAttribute(READY_ATTR) || input.classList.contains('gc-no-stepper')) return;
87		var parent = input.parentNode;
88		if (!parent) return;
89		input.setAttribute(READY_ATTR, '1');
90
91		var minus = button(-1);
92		var plus = button(1);
93
94		if (parent.classList && parent.classList.contains('input-group')) {
95			// The input already lives in a Bootstrap input-group — e.g. a field
96			// rendered with a unit suffix (`.input-group-text` showing m/kg/cm).
97			// Inject the buttons INTO that group around the input instead of
98			// nesting a second input-group, so the +/- and the suffix share one
99			// clean row ([-] value [+] unit) rather than fighting over it.
100			parent.classList.add('gc-stepper-group');
101			parent.insertBefore(minus, input);
102			parent.insertBefore(plus, input.nextSibling); // after input, before any suffix
103			// The host (e.g. CF7's inline .wpcf7-form-control-wrap span) still
104			// needs promoting to block so the group can size to the column.
105			if (parent.parentNode && parent.parentNode.tagName === 'SPAN') {
106				parent.parentNode.classList.add('gc-stepper-host');
107			}
108		} else {
109			var group = document.createElement('div');
110			group.className = 'input-group gc-stepper-group';
111			parent.insertBefore(group, input);
112			group.appendChild(minus);
113			group.appendChild(input);     // moves the input; id/name/listeners preserved
114			group.appendChild(plus);
115			// CF7 (and some templates) wrap the input in an inline <span>
116			// (.wpcf7-form-control-wrap). An inline host shrinks to content, so
117			// the full-width group can't size to the column — promote it to block.
118			if (parent.tagName === 'SPAN') parent.classList.add('gc-stepper-host');
119		}
120		input.classList.add('gc-stepper-input', 'text-center');
121
122		minus.addEventListener('click', function () { stepValue(input, -1); });
123		plus.addEventListener('click', function () { stepValue(input, 1); });
124
125		// Show the field's starting value on first load: its default, or a
126		// positive required min (fieldStart). Optional fields stay blank. No
127		// event dispatch — this is the field's initial state, not an edit.
128		if (String(input.value).trim() === '') {
129			var start = fieldStart(input);
130			if (!isNaN(start)) input.value = String(start);
131		}
132	}
133
134	function scan(root) {
135		var scope = root && root.querySelectorAll ? root : document;
136		Array.prototype.forEach.call(scope.querySelectorAll(SELECTOR), enhance);
137	}
138
139	function ready(fn) {
140		if (document.readyState !== 'loading') { fn(); }
141		else { document.addEventListener('DOMContentLoaded', fn); }
142	}
143
144	ready(function () {
145		scan(document);
146		// Re-enhance inputs injected after load (variation-table reprice,
147		// cart re-render, step-2 modal, …).
148		if (window.MutationObserver && document.body) {
149			new MutationObserver(function (mutations) {
150				for (var i = 0; i < mutations.length; i++) {
151					var added = mutations[i].addedNodes;
152					for (var j = 0; j < added.length; j++) {
153						var node = added[j];
154						if (node.nodeType !== 1) continue;
155						if (node.matches && node.matches(SELECTOR)) enhance(node);
156						if (node.querySelectorAll) scan(node);
157					}
158				}
159			}).observe(document.body, { childList: true, subtree: true });
160		}
161	});
162
163	// Explicit hook for scripts that want to enhance a freshly-built region.
164	window.gcInitSteppers = scan;
165})();

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.