1/* Contact form behaviour that CSS cannot express. 2 * 3 * Two jobs: 4 * 1. Format the phone field as it is typed, to the Israeli 05X-XXX-XXXX shape. 5 * 2. Mark the subject field as filled, so its floating label behaves like the text fields. 6 * A <select> has no :placeholder-shown, so the "has a value" half of that rule needs a class. 7 * 8 * Everything else â revealing the label on focus, hiding the placeholder â is CSS. 9 */ 10(function () { 11 'use strict'; 12 13 var PREFIX = '0'; // every Israeli number starts here, so the visitor need not type it 14 15 /** 16 * Grouping follows the prefix: mobile and VoIP (05X/07X) are 10 digits shaped 3-3-4, 17 * landline area codes (02/03/04/08/09) are 9 digits shaped 2-3-4. 18 */ 19 function groupsFor(digits) { 20 if (/^0[57]/.test(digits)) return {groups: [3, 3, 4], max: 10}; 21 if (/^0[23489]/.test(digits)) return {groups: [2, 3, 4], max: 9}; 22 return {groups: [3, 3, 4], max: 10}; // undecided or invalid prefix â mobile shape 23 } 24 25 /** Digits only, grouped with dashes. Anything the visitor pastes is normalised the same way. */ 26 function formatPhone(raw) { 27 var all = String(raw).replace(/\D/g, ''); 28 // The focus handler seeds a leading 0; visitors who type the full number anyway would 29 // produce 00⦠â no Israeli number starts that way, so duplicates collapse to one. 30 all = all.replace(/^0+/, '0'); 31 var spec = groupsFor(all); 32 var digits = all.slice(0, spec.max); 33 var out = '', at = 0; 34 for (var i = 0; i < spec.groups.length && at < digits.length; i++) { 35 if (out) out += '-'; 36 out += digits.substr(at, spec.groups[i]); 37 at += spec.groups[i]; 38 } 39 return out; 40 } 41 42 /** Digits before the caret â the stable reference point when dashes shift positions. */ 43 function digitsBefore(value, caret) { 44 return value.slice(0, caret).replace(/\D/g, '').length; 45 } 46 47 /** Caret position holding that many digits to its left. */ 48 function caretAfterDigits(value, count) { 49 if (count <= 0) return 0; 50 var seen = 0; 51 for (var i = 0; i < value.length; i++) { 52 if (/\d/.test(value[i])) seen++; 53 if (seen === count) return i + 1; 54 } 55 return value.length; 56 } 57 58 /** 59 * Keeps the caret to the right of the leading 0. 60 * 61 * Clicking into the field places the caret wherever the pointer landed, and the browser does 62 * that after the focus handler has seeded the prefix â so a click on the left edge left the 63 * caret at position 0 and the next digit landed before the zero, turning 05⦠into 50â¦. Every 64 * Israeli number starts with that 0, so no caret position in front of it is ever meaningful. 65 * Keyboard movement (Home, arrow-left) is clamped for the same reason. 66 */ 67 function keepCaretAfterPrefix(input) { 68 if (input.value.charAt(0) !== PREFIX) return; 69 // A deliberate selection that spans the prefix is left alone â the visitor is replacing it. 70 if (input.selectionStart !== input.selectionEnd) return; 71 if (input.selectionStart >= PREFIX.length) return; 72 try { input.setSelectionRange(PREFIX.length, PREFIX.length); } catch (e) {} 73 } 74 75 function bindPhone(input) { 76 // Seed the prefix on focus so typing starts at the third digit. 77 input.addEventListener('focus', function () { 78 if (this.value !== '') return; 79 this.value = PREFIX; 80 // Tell the floating label and the validator the field changed. 81 this.dispatchEvent(new Event('input', { bubbles: true })); 82 var end = this.value.length; 83 try { this.setSelectionRange(end, end); } catch (e) {} 84 }); 85 86 // Nothing beyond the seeded prefix means the visitor never really filled this in â clear it so 87 // the field reads as untouched: placeholder returns, label drops, no "invalid" complaint. 88 // 89 // The validator has already run by this point: its focusout handler is delegated on the form 90 // and fires before this, so it has judged the bare "05" and rendered a message. Clearing the 91 // value alone would leave that message stranded, so it is removed here too â unless the visitor 92 // has already tried to send, in which case an empty required field genuinely should complain, 93 // and the field is simply re-validated to get the right message. 94 input.addEventListener('blur', function () { 95 if (this.value.replace(/\D/g, '').length > PREFIX.length) return; 96 this.value = ''; 97 this.dispatchEvent(new Event('input', { bubbles: true })); 98 99 // Set by the form's validator on the first send attempt. validator.submitted is not a 100 // substitute: it fills up on any element check, including the one that just ran. 101 if (this.form && this.form.dataset.attempted === '1') { jQuery(this).val
101id(); return; } 102 103 var message = document.getElementById(this.id + '-error'); 104 if (message) message.parentNode.removeChild(message); 105 this.classList.remove('error'); 106 this.removeAttribute('aria-describedby'); 107 this.removeAttribute('aria-invalid'); 108 }); 109 110 // The click runs after the browser has placed the caret, which is the only point late enough 111 // to correct it. keyup covers Home and arrow-left reaching the same position. 112 input.addEventListener('click', function () { keepCaretAfterPrefix(this); }); 113 input.addEventListener('keyup', function () { keepCaretAfterPrefix(this); }); 114 115 input.addEventListener('input', function () { 116 // Re-anchor on digit count rather than raw offset, so inserted dashes do not drag the caret. 117 var wanted = digitsBefore(this.value, this.selectionStart); 118 var formatted = formatPhone(this.value); 119 if (formatted === this.value) { 120 keepCaretAfterPrefix(this); 121 return; 122 } 123 this.value = formatted; 124 var pos = caretAfterDigits(formatted, wanted); 125 try { this.setSelectionRange(Math.max(pos, PREFIX.length), Math.max(pos, PREFIX.length)); } catch (e) {} 126 }); 127 } 128 129 function bindSelect(select) { 130 var field = select.closest('.cf-field'); 131 if (!field) return; 132 var sync = function () { 133 // "0" is the prompt option, not a real choice. 134 field.classList.toggle('cf-field--filled', select.value !== '' && select.value !== '0'); 135 }; 136 select.addEventListener('change', sync); 137 sync(); 138 } 139 140 /* ----- Confirmation modal ------------------------------------------------ 141 * Opened by the form's submit handler with the result kind and message. 142 * The close button is the CPT popup modal's, so it gets the same device-pixel 143 * nudge measurement: the surface centre can fall on a half pixel, and a 36px 144 * circle drawn across it stops reading as round. 145 */ 146 window.cilContactModal = (function () { 147 var opener = null; 148 149 function el() { return document.getElementById('contactFormModal'); } 150 function surface() { return el() && el().querySelector('.cf-modal__surface'); } 151 function closeBtn(){ return el() && el().querySelector('.cf-modal__close a'); } 152 153 function nudgeClose() { 154 var modal = el(); 155 var wrap = modal.querySelector('.cf-modal__close'); 156 var box = surface(); 157 if (!wrap || !box) return; 158 var rect = box.getBoundingClientRect(); 159 var left = rect.left + rect.width / 2 - wrap.offsetWidth / 2; 160 var nudge = Math.round(left) - left; 161 wrap.style.setProperty('--cil-close-nudge', nudge.toFixed(3) + 'px'); 162 } 163 164 function onKeydown(e) { 165 if (e.key === 'Escape') close(); 166 } 167 168 function open(kind, title) { 169 var modal = el(); 170 if (!modal) return; 171 // document.activeElement is <body> by this point: submitting disables the send button, and 172 // disabling a focused element blurs it. Returning focus to <body> on close drops a keyboard 173 // or screen-reader visitor at the top of the document instead of back where they were, so 174 // the send button is named explicitly and only then falls back to whatever holds focus. 175 opener = document.getElementById('contactFormSubmit') || document.activeElement; 176 modal.querySelector('.cf-modal__title').textContent = title || ''; 177 modal.classList.remove('is-success', 'is-error'); 178 modal.classList.add(kind === 'success' ? 'is-success' : 'is-error'); 179 modal.hidden = false; 180 // Two frames so the browser commits display:flex before the transitions start. 181 requestAnimationFrame(function () { requestAnimationFrame(function () { 182 modal.classList.add('is-open'); 183 nudgeClose(); 184 var btn = closeBtn(); 185 if (btn) btn.focus(); 186 }); }); 187 document.addEventListener('keydown', onKeydown); 188 } 189 190 function close() { 191 var modal = el(); 192 if (!modal || modal.hidden) return; 193 modal.classList.remove('is-open'); 194 document.removeEventListener('keydown', onKeydown); 195 // Let the backdrop/box fade run before the element leaves the tree. 196 setTimeout(function () { 197 modal.hidden = true; 198 // A disabled control cannot take focus, and the send button is still disabled
198while the 199 // request settles â re-enabling is the submit path's job, so only focus what can accept it. 200 if (opener && document.contains(opener) && !opener.disabled) opener.focus(); 201 opener = null; 202 }, 320); 203 } 204 205 function bind() { 206 var modal = el(); 207 if (!modal) return; 208 [].forEach.call(modal.querySelectorAll('[data-cf-modal-close]'), function (t) { 209 t.addEventListener('click', function (e) { e.preventDefault(); close(); }); 210 }); 211 } 212 213 return { open: open, close: close, bind: bind }; 214 })(); 215 216 function init() { 217 var phone = document.getElementById('contact_form_tel'); 218 if (phone) bindPhone(phone); 219 var subject = document.getElementById('contact_subject'); 220 if (subject) bindSelect(subject); 221 window.cilContactModal.bind(); 222 } 223 224 if (document.readyState === 'loading') { 225 document.addEventListener('DOMContentLoaded', init); 226 } else { 227 init(); 228 } 229})();
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.