PageSourceSearch

https://www.swhd.org/wp-content/plugins/wp-easycart/design/theme/b…/ec-text-input-rules.js?ver=6_0_1

js swhd.org collected 2026-10-02 04:57:53 UTC 11,466 bytes, 331 lines download raw bytes

1/**
2 * WP EasyCart - input rules for text / textarea modifier options ( 6.0.0 ).
3 *
4 * Templates print data attributes from wp_easycart_text_input_rules::attributes():
5 *   data-ec-text-case="upper|lower|title"
6 *   data-ec-text-allowed="letters|letters_space|numbers|alnum|alnum_space"
7 *   data-ec-min-length="5" data-ec-min-length-error="..." ( checked by ec-store.js on add to cart )
8 * plus the native maxlength. This script applies them while the shopper types or
9 * pastes ( keeping the caret in place ), on focus, and before a form submits.
10 * The server applies the same rules again on add to cart.
11 *
12 * Order ( mirrors PHP ): case -> strip disallowed -> max length.
13 * No dependencies; events are delegated so late-loaded markup works too.
14 */
15( function() {
16	'use strict';
17
18	if ( window.wpeasycart_text_input_rules ) {
19		return;
20	}
21
22	var SELECTOR = 'input[data-ec-text-case],input[data-ec-text-allowed],textarea[data-ec-text-case],textarea[data-ec-text-allowed]';
23
24	var UNICODE = ( function() {
25		try {
26			return new RegExp( '\\p{L}', 'u' ).test( String.fromCharCode( 0xE9 ) );
27		} catch ( e ) {
28			return false;
29		}
30	} )();
31
32	/* Older browsers: common Latin, Greek, Cyrillic, Hebrew, Arabic, Devanagari, Thai, CJK and Hangul ranges. */
33	function range( a, b ) {
34		return String.fromCharCode( a ) + '-' + String.fromCharCode( b );
35	}
36	var LETTERS = UNICODE ? '\\p{L}\\p{M}' : 'A-Za-z' + String.fromCharCode( 0xAA, 0xB5, 0xBA ) +
37		range( 0xC0, 0xD6 ) + range( 0xD8, 0xF6 ) + range( 0xF8, 0x2FF ) + range( 0x300, 0x36F ) +
38		range( 0x370, 0x3FF ) + range( 0x400, 0x52F ) + range( 0x531, 0x58F ) + range( 0x5D0, 0x5EA ) +
39		range( 0x620, 0x64A ) + range( 0x900, 0x97F ) + range( 0xE00, 0xE7F ) + range( 0x1E00, 0x1FFF ) +
40		range( 0x3040, 0x30FF ) + range( 0x3400, 0x4DBF ) + range( 0x4E00, 0x9FFF ) + range( 0xAC00, 0xD7AF );
41	var FLAGS = UNICODE ? 'gu' : 'g';
42	var cache = {};
43
44	function disallowed( allowed, multiline ) {
45		var key = allowed + ( multiline ? '_m' : '' );
46		if ( Object.prototype.hasOwnProperty.call( cache, key ) ) {
47			return cache[ key ];
48		}
49		var re = null;
50		if ( 'letters' === allowed ) {
51			re = new RegExp( '[^' + LETTERS + ']', FLAGS );
52		} else if ( 'letters_space' === allowed ) {
53			re = new RegExp( '[^' + LETTERS + ' ' + ( multiline ? '\\r\\n' : '' ) + ']', FLAGS );
54		} else if ( 'numbers' === allowed ) {
55			re = /[^0-9]/g;
56		} else if ( 'alnum' === allowed ) {
57			re = new RegExp( '[^' + LETTERS + '0-9]', FLAGS );
58		} else if ( 'alnum_space' === allowed ) {
59			re = new RegExp( '[^' + LETTERS + '0-9 ' + ( multiline ? '\\r\\n' : '' ) + ']', FLAGS );
60		}
61		cache[ key ] = re;
62		return re;
63	}
64
65	function apply_case( value, c ) {
66		if ( 'upper' === c ) {
67			return value.toUpperCase();
68		}
69		if ( 'lower' === c ) {
70			return value.toLowerCase();
71		}
72		if ( 'title' === c ) {
73			return value.replace( /(^|\s)(\S)/g, function( m, space, ch ) {
74				return space + ch.toUpperCase();
75			} );
76		}
77		return value;
78	}
79
80	/* Case + allowed characters ( no length cap: used for the text before the caret too ). */
81	function clean( value, el ) {
82		var re = disallowed( el.getAttribute( 'data-ec-text-allowed' ) || 'any', 'TEXTAREA' === el.tagName );
83		value = apply_case( String( value ), el.getAttribute( 'data-ec-text-case' ) || 'none' );
84		return re ? value.replace( re, '' ) : value;
85	}
86
87	function max_length( el ) {
88		var max = parseInt( el.getAttribute( 'maxlength' ), 10 );
89		return max > 0 ? max : 0;
90	}
91
92	function fire( el, type ) {
93		var evt;
94		if ( 'function' === typeof window.Event ) {
95			evt = new Event( type, { bubbles: true } );
96		} else {
97			evt = document.createEvent( 'Event' );
98			evt.initEvent( type, true, false );
99		}
100		el.dispatchEvent( evt );
101	}
102
103	/* Normalise el.value in place; keeps the caret where the shopper expects it. */
104	function normalize( el ) {
105		var value = el.value;
106		var next = clean( value, el );
107		var max = max_length( el );
108		if ( max && next.length > max ) {
109			next = next.slice( 0, max );
110		}
111		if ( next === value ) {
112			return false;
113		}
114		var caret = null;
115		try {
116			if ( document.activeElement === el && 'number' === typeof el.selectionEnd ) {
117				caret = Math.min( clean( value.slice( 0, el.selectionEnd ), el ).length, next.length );
118			}
119		} catch ( e ) {
120			caret = null;
121		}
122		el.value = next;
123		if ( null !== caret ) {
124			try {
125				el.setSelectionRange( caret, caret );
126			} catch ( e2 ) {}
127		}
128		return true;
129	}
130
131	function matches( el ) {
132		if ( ! el || 1 !== el.nodeType ) {
133			return false;
134		}
135		var fn = el.matches || el.msMatchesSelector || el.webkitMatchesSelector;
136		return fn ? fn.call( el, SELECTOR ) : false;
137	}
138
139	var composing = false;
140
141	document.addEventListener( 'compositionstart', function( e ) {
142		if ( matches( e.target ) ) {
143			composing = true;
144		}
145	}, true );
146
147	document.addEventListener( 'compositionend', function( e ) {
148		if ( matches( e.target ) ) {
149			composing = false;
150			normalize( e.target );
151		}
152	}, true );
153
154	document.addEventListener( 'input', function( e ) {
155		if ( ! composing && ! e.isComposing && matches( e.target ) ) {
156			normalize( e.target );
157		}
158	}, true );
159
160	/*
161	 * Multi-character insertions ( paste, drop, autocomplete, a predictive-keyboard word ):
162	 * clean the text before it lands so the native maxlength doesn't cut it first and
163	 * then leave room unused once disallowed characters are removed.
164	 */
165	function insert_clean( el, text ) {
166		if ( 'string' !== typeof text || 'number' !== typeof el.selectionStart ) {
167			return false;
168		}
169		var start = el.selectionStart;
170		var end = el.selectionEnd;
171		var value = el.value;
172		if ( 'TEXTAREA' !== el.tagName ) {
173			text = text.replace( /[\r\n]+/g, ' ' );
174		}
175		var before = clean( value.slice( 0, start ), el );
176		var inserted = clean( value.slice( 0, start ) + text, el ).slice( before.length );
177		var after = value.slice( end );
178		var max = max_length( el );
179		if ( max ) {
180			inserted = inserted.slice( 0, Math.max( 0, max - before.length - after.length ) );
181		}
182		el.value = before + inserted + after;
183		normalize( el );
184		var caret = Math.min( before.length + inserted.length, el.value.length );
185		try {
186			el.setSelectionRange( caret, caret );
187		} catch ( e2 ) {}
188		el.setAttribute( 'data-ec-text-pending-change', '1' );
189		fire( el, 'input' );
190		return true;
191	}
192
193	/* Would the native insertion lose characters to the allowed-characters rule? */
194	function needs_clean_insert( el, text ) {
195		if ( 'string' !== typeof text || '' === text ) {
196			return false;
197		}
198		if ( 'TEXTAREA' !== el.tagName && /[\r\n]/.test( text ) ) {
199			return true;
200		}
201		var re = disallowed( el.getAttribute( 'data-ec-text-allowed' ) || 'any', 'TEXTAREA' === el.tagName );
202		return !! re && text.replace( re, '' ) !== text;
203	}
204
205	document.addEventListener( 'paste', function( e ) {
206		var el = e.target;
207		if ( ! matches( el ) ) {
208			return;
209		}
210		var data = e.clipboardData || window.clipboardData;
211		if ( ! data || ! data.getData ) {
212			return;
213		}
214		var text = data.getData( 'text' );
215		if ( needs_clean_insert( el, text ) && insert_clean( el, text ) ) {
216			e.preventDefault();
217		}
218	}, true );
219
220	document.addEventListener( 'beforeinput', function( e ) {
221		var el = e.target;
222		if ( composing || e.isComposing || ! matches( el ) ) {
223			return;
224		}
225		var text = null;
226		if ( 'insertText' === e.inputType || 'insertReplacementText' === e.inputType ) {
227			text = e.data;
228			if ( null === text && e.dataTransfer && e.dataTransfer.getData ) {
229				text = e.dataTransfer.getData( 'text/plain' );
230			}
231		} else if ( 'insertFromDrop' === e.inputType && e.dataTransfer && e.dataTransfer.getData ) {
232			text = e.dataTransfer.getData( 'text/plain' );
233		}
234		if ( e.cancelable && needs_clean_insert( el, text ) && insert_clean( el, text ) ) {
235			e.preventDefault();
236		}
237	}, true );
238
239	/* Scripted value changes don't always fire "change" on blur; ec-store.js recalculates prices on change. */
240	document.addEventListener( 'focusout', function( e ) {
241		var el = e.target;
242		if ( matches( el ) && el.getAttribute( 'data-ec-text-pending-change' ) ) {
243			el.removeAttribute( 'data-ec-text-pending-change' );
244			fire( el, 'change' );
245		}
246	}, true );
247
248	document.addEventListener( 'focusin', function( e ) {
249		if ( matches( e.target ) ) {
250			normalize( e.target );
251		}
252	}, true );
253
254	/* Autofill / scripted values that never fired an input event. */
255	document.addEventListener( 'submit', function( e ) {
256		var form = e.target;
257		if ( ! form || ! form.querySelectorAll ) {
258			return;
259		}
260		var fields = form.querySelectorAll( SELECTOR );
261		for ( var i = 0; i < fields.length; i++ ) {
262			normalize( fields[ i ] );
263		}
264	}, true );
265
266	function boot() {
267		var fields = document.querySelectorAll( SELECTOR );
268		for ( var i = 0; i < fields.length; i++ ) {
269			normalize( fields[ i ] );
270		}
271
272		if ( fields.length && ! document.getElementById( 'ec-text-input-rules-css' ) ) {
273			var style = document.createElement( 'style' );
274			style.id = 'ec-text-input-rules-css';
275			style.appendChild( document.createTextNode( '[data-ec-text-case]::placeholder{text-transform:none}[data-ec-text-case]::-webkit-input-placeholder{text-transform:none}' ) );
276			( document.head || document.documentElement ).appendChild( style );
277		}
278
279		/* Server rejected an add to cart because the rules removed everything typed into a required option. */
280		var match = /[?&]ec_option_error=(\d+)/.exec( window.location.search );
281		if ( match ) {
282			var rows = document.querySelectorAll( '[id^="ec_details_adv_option_row_error_' + match[1] + '_"]' );
283			/* Reason → the message attribute on the option's input ( text rules and file uploads ). */
284			var reason = /[?&]ec_option_error_reason=([a-z_]+)/.exec( window.location.search );
285			var message_attr = {
286				min_length: 'data-ec-min-length-error',
287				file_type: 'data-ec-error-file-type',
288				file_size: 'data-ec-error-file-size',
289				file_upload: 'data-ec-error-file-upload'
290			}[ reason ? reason[1] : '' ];
291			for ( var r = 0; r < rows.length; r++ ) {
292				if ( message_attr ) {
293					var input = document.getElementById( rows[ r ].id.replace( 'ec_details_adv_option_row_error_', 'ec_option_adv_' ) );
294					if ( input && input.getAttribute( message_attr ) ) {
295						rows[ r ].textContent = input.getAttribute( message_attr );
296					}
297				}
298				rows[ r ].style.display = 'block';
299			}
300			if ( rows.length && rows[0].scrollIntoView ) {
301				rows[0].scrollIntoView( { block: 'center' } );
302			}
303		}
304	}
305
306	if ( 'loading' === document.readyState ) {
307		document.addEventListener( 'DOMContentLoaded', boot );
308	} else {
309		boot();
310	}
311
312	/* Characters short of the minimum length ( 0 when empty or long enough ). Surrogate pairs count once, like PHP mb_strlen. */
313	function too_short( el ) {
314		if ( ! el ) {
315			return 0;
316		}
317		var min = parseInt( el.getAttribute( 'data-ec-min-length' ) || el.getAttribute( 'minlength' ), 10 );
318		var value = String( el.value || '' ).replace( /^\s+|\s+$/g, '' );
319		if ( ! ( min > 0 ) || '' === value ) {
320			return 0;
321		}
322		var length = value.replace( /[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '_' ).length;
323		return length < min ? min - length : 0;
324	}
325
326	window.wpeasycart_text_input_rules = {
327		normalize: normalize,
328		clean: clean,
329		too_short: too_short
330	};
331} )();

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.