PageSourceSearch

https://www.westernsydney.edu.au/etc.clientlibs/wsu-international/…-17d7a0bbfe6384838368ed3f329eed3a-lc.min.js

js westernsydney.edu.au collected 2026-10-01 08:07:30 UTC 88,090 bytes, 2,692 lines download raw bytes

1const select = document.getElementsByName("areasofstudy_dd")[0];
2
3let previousSelected = [];
4select.addEventListener("change", function () {
5    const allOptions = Array.from(this.options);
6    let selectedOptions = Array.from(this.selectedOptions);
7    const currentValues = selectedOptions.map(o => o.value);
8    const newlySelected = currentValues.find(v => !previousSelected.includes(v));
9
10    // Prevent selecting more than 3
11    if (selectedOptions.length > 3 && newlySelected) {
12        const optionToUnselect = allOptions.find(o => o.value === newlySelected);
13        if (optionToUnselect) optionToUnselect.selected = false;
14        selectedOptions = Array.from(this.selectedOptions);
15    }
16
17    // Limit to 3 selections
18    if (selectedOptions.length >= 3) {
19        allOptions.forEach(option => {
20            if (!option.selected && option.value !== "") {
21                option.disabled = true;
22            }
23        });
24    } else {
25        allOptions.forEach(option => option.disabled = false);
26    }
27
28    // Save current state
29    previousSelected = selectedOptions.map(o => o.value);
30
31    // Get display text
32    const values = selectedOptions
33        .map(option => option.text)
34        .filter(v => v !== "");
35
36    const inputs = [
37        document.getElementsByName("selection1")[0],
38        document.getElementsByName("selection2")[0],
39        document.getElementsByName("selection3")[0]
40    ];
41
42    const fields = inputs.map(input => ({
43        input,
44        container: input ? input.closest('.cmp-adaptiveform-textinput') : null
45    }));
46
47    fields.forEach((field, index) => {
48        if (!field.input || !field.container) return;
49
50        const value = values[index] || "";
51
52        // Set value
53        field.input.value = value;
54
55        // Show/Hide
56        field.container.style.display = value ? "block" : "none";
57
58        // AEM sync
59        field.container.setAttribute("data-cmp-visible", value ? "true" : "false");
60
61        field.input.dispatchEvent(new Event("change", { bubbles: true }));
62    });
63
64    // Update summary (count only)
65    const summaryDiv = document.getElementById("text-d875f1687c");
66    if (summaryDiv && summaryDiv.querySelector("p")) {
67        summaryDiv.querySelector("p").textContent =
68            `Your Selection Summary (${values.length})`;
69    }
70
71    console.log("Selected:", values.join(", "));
72});
73
74
75select.dispatchEvent(new Event("change"));
76
77const firstNameInput = document.getElementsByName("firstName")[0];
78const targetInput = document.getElementsByName("displayOpenday")[0];
79
80
81function formatName(name) {
82  return name.endsWith("s") ? `${name}' Open Day` : `${name}'s Open Day`;
83}
84
85  firstNameInput.addEventListener("input", function (e) {
86    const firstName = e.target.value.trim();
87
88    const result = firstName ? formatName(firstName) : "";
89
90    console.log("Updating field:", result);
91
92    targetInput.value = result;
93  });
94
95/**
96 * Collects filled guest name/email pairs, updates the guestList summary field,
97 * and sets numberOfGuests to the valid guest count plus 1 for the primary registrant.
98 * @name updateGuestSummary
99 * @return {void}
100 */
101function updateGuestSummary() {
102  const nameFields = document.getElementsByName("guestName");
103  const emailFields = document.getElementsByName("guestEmail");
104  const targetField = document.getElementsByName("guestList")[0];
105
106  if (!targetField) return;
107
108  let result = [];
109  let hasValidGuest = false;
110  for (let i = 0; i < nameFields.length; i++) {
111    const name = nameFields[i].value.trim();
112    const email = emailFields[i]?.value.trim();
113
114    if (name && email) {
115      hasValidGuest = true;
116      result.push(`${name} ${email}`);
117    }
118  }
119
120  targetField.value = result.join("\n");
121  const container = targetField.closest(".cmp-adaptiveform-textinput");
122  if (container) {
123    container.style.display = hasValidGuest ? "block" : "none";
124  }
125  const infoText = document.getElementById("text-e4a199b6fa");
126  if (infoText) {
127    infoText.style.display = hasValidGuest ? "block" : "none";
128  }
129}
130
131function getSelectedSessionsData() {
132  const data = localStorage.getItem("selectedSessions");
133
134  if (!data) {
135    return null;
136  }
137
138  try {
139    // If it's already a stringified JSON, parse & re-stringify (safe normalization)
140    const parsed = JSON.parse(data);
141    return JSON.stringify(parsed);
142  } catch (e) {
143    // If it's not valid JSON, just return as-is
144    return data;
145  }
146}
147
148/**
149 * Reloads the current page.
150 * @name refreshPage
151 * @return {void}
152 */
153function refreshPage() {
154  location.reload();
155}
156
157/**
158 * Sends guest invite emails via the AJO unitary execute endpoint for all valid guests.
159 * @name submitGuestInvites
160 * @param {string} primaryEmail - Email address of the primary registrant.
161 * @param {string} primaryName - First name of the primary registrant.
162 * @return {void}
163 */
164async function submitGuestInvites(primaryEmail, primaryName) {
165  const nameFields = document.getElementsByName("guestName");
166  const emailFields = document.getElementsByName("guestEmail");
167
168  let recipients = [];
169
170  for (let i = 0; i < nameFields.length; i++) {
171    const name = nameFields[i].value.trim();
172    const email = emailFields[i]?.value.trim();
173
174    if (name && email) {
175      recipients.push({
176        emailAddress: email,
177        firstName: name
178      });
179    }
180  }
181
182  if (recipients.length === 0) {
183    console.warn("No valid guests found.");
184    return false;
185  }
186
187  try {
188    for (const guest of recipients) {
189
190      // Get signature per guest
191      const sigResponse = await fetch("/bin/wsu/ajo/signature.json", {
192        method: "POST",
193        headers: { "Content-Type": "application/json" },
194        credentials: "include",
195        body: JSON.stringify({ email: primaryEmail })
196      });
197
198      const sigData = await sigResponse.json();
199
200      if (!sigData?.requestSignature || !sigData?.requestTimestamp) {
201        console.error("Signature failed for:", guest.emailAddress);
202        continue;
203      }
204
205      // Execute call
206      const execResponse = await fetch("/bin/wsu/ajo/unitary/execute.json", {
207        method: "POST",
208        headers: {
209          "Content-Type": "application/json",
210          "x-ajo-origin": window.location.host
211        },
212        credentials: "include",
213        body: JSON.stringify({
214          primaryUser: {
215            emailAddress: primaryEmail,
216            firstName: primaryName
217          },
218          recipients: [guest],
219          requestTimestamp: sigData.requestTimestamp,
220          requestSignature: sigData.requestSignature
221        })
222      });
223
224      const data = await execResponse.json();
225      console.log("Sent to:", guest.emailAddress, data);
226    }
227
228    return true;
229
230  } catch (error) {
231    console.error("Error sending invites:", error);
232    return false;
233  }
234}
235
236/**
237 * Returns the total number of attendees (valid guests + 1 for the primary registrant).
238 * Use in a form rule: Set Value of numberOfGuests to getGuestCount()
239 * @name getGuestCount
240 * @return {string} Total attendee count as a string.
241 */
242function getGuestCount() {
243  var count = 0;
244  document.querySelectorAll('.guest-details-panel').forEach(function (panel) {
245    var nameInput = panel.querySelector('input[name="guestName"]');
246    var emailInput = panel.querySelector('input[name="guestEmail"]');
247    var nameContainer = nameInput && nameInput.closest('.cmp-adaptiveform-textinput');
248    var emailContainer = emailInput && emailInput.closest('.cmp-adaptiveform-textinput');
249    if (nameContainer && nameContainer.classList.contains('cmp-adaptiveform-textinput--filled') &&
250        emailContainer && emailContainer.classList.contains('cmp-adaptiveform-textinput--filled')) {
251      count++;
252    }
253  });
254  return String(count + 1);
255}
256
257/**
258 * Validates all guest email fields for format, duplicates, and match against the primary email.
259 * Also sets the numberOfGuests field value via the AEM Forms rule engine globals.
260 * @name validateGuestEmails
261 * @param {string} primaryEmail - The primary registrant's email to check against.
262 * @param {scope} globals - AEM Forms globals injected by the rule engine.
263 * @return {boolean} True if all guest emails are valid, false otherwise.
264 */
265function validateGuestEmails(primaryEmail) {
266  const emailFields = Array.from(document.getElementsByName("guestEmail"));
267  const nameFields = Array.from(document.getElementsByName("guestName"));
268
269  const normalizedPrimary = primaryEmail?.trim().toLowerCase();
270  const seen = new Set();
271  let isValid = true;
272
273  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/;
274
275  emailFields.forEach((field, index) => {
276    const rawValue = field.value.trim();
277    const email = rawValue.toLowerCase();
278    const guestName = nameFields[index]?.value.trim();
279
280    const container = field.closest(".cmp-adaptiveform-textinput");
281    const errorContainer = container
282      ? container.querySelector(".cmp-adaptiveform-textinput__errormessage")
283      : null;
284
285    let message = "";
286
287    // Email is empty
288    if (!rawValue) {
289      if (guestName) {
290        message = "This field is required.";
291      }
292      // else no validation
293    }
294
295    // Format validation
296    else if (!emailRegex.test(email)) {
297      message = "Invalid email format.";
298    }
299
300    // Custom rules
301    else {
302      if (seen.has(email)) {
303        message = "Duplicate guest email not allowed.";
304      } else {
305        seen.add(email);
306      }
307
308      if (normalizedPrimary && email === normalizedPrimary) {
309        message += (message ? " " : "") + "Cannot use primary email as guest.";
310      }
311    }
312
313    // UI handling
314    if (message) {
315      isValid = false;
316
317      if (errorContainer) {
318        errorContainer.textContent = message;
319        errorContainer.style.display = "block";
320      }
321
322      field.setAttribute("aria-invalid", "true");
323
324    } else {
325      if (errorContainer) {
326        errorContainer.textContent = "";
327        errorContainer.style.display = "none";
328      }
329
330      field.setAttribute("aria-invalid", "false");
331    }
332  });
333
334  return isValid;
335}
336/**
337 * Returns the pathname of the referring page, or empty string if no referrer.
338 * @name captureSourcePath
339 * @return {string} The source pathname.
340 */
341function captureSourcePath() {
342  var referrer = document.referrer;
343  var sourcePath = "";
344  if (referrer) {
345    try {
346      var url = new URL(referrer);
347      sourcePath = url.pathname;
348    } catch (e) {
349      sourcePath = referrer;
350    }
351  }
352  return sourcePath;
353}
354
355/**
356 * Full international mobile number for a phone field: its data-country-code
357 * (set by the calling-code picker) + the national digits, with no leading "+"
358 * and a single leading trunk "0" dropped. Idempotent; does not alter the field.
359 * Intended for a form rule that populates a hidden field.
360 *
361 * @param {string} fieldName Element name of the phone field, e.g. "phoneNumber".
362 * @return {string} e.g. "61412345678" (empty if the field has no digits).
363 */
364function getInternationalPhoneNumber(fieldName) {
365  var input = document.querySelector('[name="' + fieldName + '"]');
366  if (!input) {
367    return "";
368  }
369  var code = input.getAttribute("data-country-code") || "";
370  var codeDigits = code.replace(/\D/g, "");
371  var digits = String(input.value || "").replace(/\D/g, "");
372  if (!digits) {
373    return "";
374  }
375  // If the value is already prefixed with the code, don't add it again.
376  var national = (codeDigits && digits.indexOf(codeDigits) === 0 &&
377    digits.length > codeDigits.length)
378    ? digits.slice(codeDigits.length)
379    : digits;
380  national = national.replace(/^0/, "");
381  return codeDigits + national;
382}
383function _forms_escapeHtml(text) {
384  if (text == null || text === '') {
385    return '';
386  }
387  return String(text).replace(/[&<>"']/g, function (c) {
388    return ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' })[c];
389  });
390}
391
392/** Attribute values in quoted HTML; same character set as text escape for this project. */
393function _forms_escapeAttr(text) {
394  return _forms_escapeHtml(text);
395}
396
397function _forms_normalizeAreaOfStudy(areaOfStudy) {
398  if (Array.isArray(areaOfStudy)) {
399    return areaOfStudy;
400  }
401  if (typeof areaOfStudy === 'string') {
402    var s = areaOfStudy.trim();
403    if (s.charAt(0) === '[') {
404      try {
405        var parsed = JSON.parse(s);
406        return Array.isArray(parsed) ? parsed : [];
407      } catch (e) {
408        return s ? [s] : [];
409      }
410    }
411    if (s.indexOf(',') !== -1) {
412      return s.split(',').map(function (x) {
413        return x.trim();
414      }).filter(Boolean);
415    }
416    return s ? [s] : [];
417  }
418  return [];
419}
420
421function _forms_buildOpendaySessionsGraphqlPath(areaOfStudy, studyOption) {
422  var labels = ['areaA', 'areaB', 'areaC'];
423  var raw = _forms_normalizeAreaOfStudy(areaOfStudy);
424  var segment = 'fetchOpendaySessions';
425  var i;
426  for (i = 0; i < 3; i++) {
427    var v = raw[i];
428    var val =
429      v !== undefined && v !== null && String(v).trim() !== ''
430        ? String(v).trim()
431        : 'null';
432    segment += ';' + labels[i] + '=' + encodeURIComponent(val);
433  }
434  segment += ';studyOption=' + encodeURIComponent(studyOption || '');
435  return segment.replace(/;/g, '%3B');
436}
437
438function _forms_getOrigin() {
439  try {
440    if (typeof window !== 'undefined' && window.location && window.location.origin) {
441      return window.location.origin;
442    }
443  } catch (e) {
444    /* ignore */
445  }
446  return '';
447}
448
449/**
450 * Root-relative media URLs: prefix on Author (current origin); on publish, leave as-is.
451 */
452function _forms_needsPublishDeliveryBaseForMedia() {
453  try {
454    var h = window.location.hostname;
455    return h.indexOf('author') !== -1;
456  } catch (e) {
457    return false;
458  }
459}
460
461/**
462 * Prefix root-relative _dynamicUrl / _path when needed on Author (window.location.origin).
463 * _publishUrl is already absolute and is not modified in _forms_sessionImageUrl.
464 */
465function _forms_absolutePublishImageUrlIfLocal(url, sourceKind) {
466  if (!url) {
467    return '';
468  }
469  var s = String(url).trim();
470  if (!_forms_needsPublishDeliveryBaseForMedia() || (sourceKind !== 'dynamic' && sourceKind !== 'path')) {
471    return s;
472  }
473  if (/^https?:\/\//i.test(s)) {
474    return s;
475  }
476  var base = _forms_getOrigin().replace(/\/+$/, '');
477  if (!base) {
478    return s;
479  }
480  var path = s.charAt(0) === '/' ? s : '/' + s;
481  return base + path;
482}
483
484/**
485 * Rich text from GraphQL (e.g. session.summary.html); empty if missing.
486 */
487function _forms_sessionSummaryHtml(s) {
488  if (!s || !s.summary) {
489    return '';
490  }
491  var html = s.summary.html;
492  if (typeof html !== 'string') {
493    return '';
494  }
495  return html.trim();
496}
497
498/**
499 * Plain text from rich summary HTML (for localStorage; no tags).
500 */
501function _forms_htmlToPlainText(html) {
502  if (html == null || html === '') {
503    return '';
504  }
505  var s = String(html);
506  try {
507    if (typeof document !== 'undefined') {
508      var d = document.createElement('div');
509      d.innerHTML = s;
510      var t = d.textContent || d.innerText || '';
511      return String(t).replace(/\s+/g, ' ').trim();
512    }
513  } catch (e) {
514    /* fall through */
515  }
516  return s.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
517}
518
519function _forms_sessionImageUrl(session) {
520  if (!session || !session.image) {
521    return '';
522  }
523  var im = session.image;
524  if (im._dynamicUrl) {
525    return _forms_absolutePublishImageUrlIfLocal(im._dynamicUrl, 'dynamic');
526  }
527  if (im._publishUrl) {
528    return im._publishUrl;
529  }
530  if (im._path) {
531    return _forms_absolutePublishImageUrlIfLocal(im._path, 'path');
532  }
533  return '';
534}
535
536function _forms_formatSessionTime(iso) {
537  if (!iso) {
538    return '';
539  }
540  var d = new Date(iso);
541  if (isNaN(d.getTime())) {
542    return '';
543  }
544  return d.toLocaleTimeString(undefined, {
545    hour: 'numeric',
546    minute: '2-digit',
547    hour12: true
548  });
549}
550
551/**
552 * Groups sessions by clock time only (HH:MM:SS); date and zone are ignored.
553 * Accepts common ISO-8601 time forms, e.g. with or without seconds, fractional seconds.
554 */
555function _forms_startTimeGroupKey(iso) {
556  if (!iso || typeof iso !== 'string') {
557    return '';
558  }
559  var m = iso.match(/T(\d{2}:\d{2})(?::(\d{2}))?/);
560  if (!m) {
561    return '';
562  }
563  var sec = m[2] != null && m[2] !== '' ? m[2] : '00';
564  return m[1] + ':' + sec;
565}
566
567/** Milliseconds for ascending sort; missing/invalid ISO sorts last. */
568function _forms_startDateTimeToSortMs(iso) {
569  if (iso == null || iso === '') {
570    return Number.POSITIVE_INFINITY;
571  }
572  var t = new Date(iso).getTime();
573  return isNaN(t) ? Number.POSITIVE_INFINITY : t;
574}
575
576/** Safe sort comparator for ISO start strings (handles invalid dates and ties). */
577function _forms_compareStartMsAscending(isoA, isoB) {
578  var ta = _forms_startDateTimeToSortMs(isoA);
579  var tb = _forms_startDateTimeToSortMs(isoB);
580  return (ta > tb) - (ta < tb);
581}
582
583function _forms_minStartMsInSessions(sessions) {
584  var min = Number.POSITIVE_INFINITY;
585  var i;
586  for (i = 0; i < sessions.length; i++) {
587    var m = _forms_startDateTimeToSortMs(sessions[i].startDateTime);
588    if (m < min) {
589      min = m;
590    }
591  }
592  return min;
593}
594
595/**
596 * Priority sessions first (lower numeric priority first); remaining sessions by startDateTime ascending.
597 */
598function _forms_sortSessionsForTimeSlot(sessions) {
599  var withPri = [];
600  var withoutPri = [];
601  sessions.forEach(function (s) {
602    if (s.priority != null && s.priority !== '') {
603      withPri.push(s);
604    } else {
605      withoutPri.push(s);
606    }
607  });
608  withPri.sort(function (a, b) {
609    var pa = Number(a.priority);
610    var pb = Number(b.priority);
611    var aNa = isNaN(pa);
612    var bNa = isNaN(pb);
613    if (aNa && bNa) {
614      return 0;
615    }
616    if (aNa) {
617      return 1;
618    }
619    if (bNa) {
620      return -1;
621    }
622    return pa - pb;
623  });
624  withoutPri.sort(function (a, b) {
625    return _forms_compareStartMsAscending(a.startDateTime, b.startDateTime);
626  });
627  return withPri.concat(withoutPri);
628}
629
630function _forms_sessionCode(s) {
631  if (!s) {
632    return '';
633  }
634  if (s.code != null && s.code !== '') {
635    return String(s.code);
636  }
637  if (s.sessionCode != null && s.sessionCode !== '') {
638    return String(s.sessionCode);
639  }
640  return '';
641}
642
643function _forms_storageString(v) {
644  if (v == null) {
645    return '';
646  }
647  return String(v);
648}
649
650function _forms_sessionCampus(s) {
651  if (!s || s.campus == null) {
652    return '';
653  }
654  return typeof s.campus === 'string' ? s.campus : '';
655}
656
657function _forms_sessionToActivity(s) {
658  var img = _forms_sessionImageUrl(s);
659  var htmlSumm = _forms_sessionSummaryHtml(s);
660  return {
661    code: _forms_sessionCode(s),
662    title: s.title || '',
663    areaOfStudy: _forms_storageString(s.areaOfStudy),
664    studyOption: _forms_storageString(s.studyOption),
665    startDateTime: s.startDateTime || '',
666    duration: _forms_storageString(s.duration),
667    location: s.location || '',
668    campus: _forms_sessionCampus(s),
669    description: _forms_htmlToPlainText(htmlSumm),
670    descriptionHtml: htmlSumm,
671    image: img
672  };
673}
674
675function _forms_sessionCollapsedMeta(s) {
676  var img = _forms_sessionImageUrl(s);
677  return {
678    image: img,
679    title: s.title || '',
680    descriptionHtml: _forms_sessionSummaryHtml(s),
681    location: s.location || ''
682  };
683}
684
685/**
686 * Flattens GraphQL execute response (sessionsA/B/C) into activity picker row payload for the open-day picker.
687 * Sessions with the same start time (time of day only) become one row; carousel lists all.
688 * areaOfStudy and studyOption on each session come from the API (SessionFields fragment), not from fetch() params.
689 */
690function _forms_graphqlPayloadToActivityPickerItems(payload) {
691  if (!payload || !payload.data) {
692    return [];
693  }
694  var d = payload.data;
695  var keys = ['sessionsA', 'sessionsB', 'sessionsC'];
696  var all = [];
697  keys.forEach(function (k) {
698    if (d[k] && Array.isArray(d[k].items)) {
699      d[k].items.forEach(function (item) {
700        all.push(item);
701      });
702    }
703  });
704
705  var groups = {};
706  all.forEach(function (item) {
707    var gk = _forms_startTimeGroupKey(item.startDateTime);
708    if (!gk) {
709      gk = '_unknown';
710    }
711    if (!groups[gk]) {
712      groups[gk] = [];
713    }
714    groups[gk].push(item);
715  });
716
717  var groupKeys = Object.keys(groups);
718  var minMsByGroupKey = {};
719  var gi;
720  for (gi = 0; gi < groupKeys.length; gi++) {
721    var gKey = groupKeys[gi];
722    minMsByGroupKey[gKey] = _forms_minStartMsInSessions(groups[gKey]);
723  }
724  groupKeys.sort(function (a, b) {
725    var ma = minMsByGroupKey[a];
726    var mb = minMsByGroupKey[b];
727    return (ma > mb) - (ma < mb);
728  });
729  return groupKeys.map(function (gk) {
730    var slot = _forms_sortSessionsForTimeSlot(groups[gk]);
731    var primary = slot[0];
732    var timeStr = _forms_formatSessionTime(primary.startDateTime);
733    var collapsed = _forms_sessionCollapsedMeta(primary);
734    return {
735      time: timeStr,
736      duration: primary.duration || '',
737      startDateTime: primary.startDateTime || '',
738      image: collapsed.image,
739      title: collapsed.title,
740      descriptionHtml: collapsed.descriptionHtml,
741      location: collapsed.location,
742      expandHeader: timeStr,
743      activities: slot.map(_forms_sessionToActivity)
744    };
745  });
746}
747
748var FORMS_SELECTED_SESSIONS_KEY = 'selectedSessions';
749
750/** Row model for the open-day picker only (modify/remove/carousel). */
751var _forms_opendayActivityPickerRows = null;
752
753function _forms_getOpenDayActivityPicker() {
754  return document.querySelector('.openday-activity-picker .wsu-activity-picker');
755}
756
757/**
758 * Shape written to localStorage "selectedSessions" (no HTML in description).
759 */
760function _forms_sessionToStoragePlain(activity) {
761  if (!activity) {
762    return {
763      code: '',
764      title: '',
765      areaOfStudy: '',
766      studyOption: '',
767      startDateTime: '',
768      duration: '',
769      location: '',
770      campus: '',
771      description: '',
772      image: ''
773    };
774  }
775  var descPlain =
776    activity.description != null && String(activity.description).trim() !== ''
777      ? String(activity.description).trim()
778      : _forms_htmlToPlainText(activity.descriptionHtml || '');
779  return {
780    code: _forms_storageString(activity.code),
781    title: _forms_storageString(activity.title),
782    areaOfStudy: _forms_storageString(activity.areaOfStudy),
783    studyOption: _forms_storageString(activity.studyOption),
784    startDateTime: _forms_storageString(activity.startDateTime),
785    duration: _forms_storageString(activity.duration),
786    location: _forms_storageString(activity.location),
787    campus: _forms_storageString(activity.campus),
788    description: descPlain,
789    image: _forms_storageString(activity.image)
790  };
791}
792
793function _forms_getSelectedSessionsArray() {
794  try {
795    var raw = localStorage.getItem(FORMS_SELECTED_SESSIONS_KEY);
796    if (!raw) {
797      return [];
798    }
799    var parsed = JSON.parse(raw);
800    return Array.isArray(parsed) ? parsed : [];
801  } catch (e) {
802    return [];
803  }
804}
805
806function _forms_setSelectedSessionsArray(arr) {
807  try {
808    localStorage.setItem(FORMS_SELECTED_SESSIONS_KEY, JSON.stringify(arr));
809  } catch (e) {
810    /* quota / private mode */
811  }
812}
813
814function _forms_clearSelectedSessionsOnLoad() {
815  try {
816    if (typeof localStorage !== 'undefined' && localStorage.getItem(FORMS_SELECTED_SESSIONS_KEY) !== null) {
817      localStorage.removeItem(FORMS_SELECTED_SESSIONS_KEY);
818    }
819  } catch (e) {
820    /* quota / private mode */
821  }
822}
823
824function _forms_cloneActivityPickerRow(row) {
825  return {
826    time: row.time,
827    duration: row.duration,
828    startDateTime: row.startDateTime,
829    image: row.image,
830    title: row.title,
831    descriptionHtml: row.descriptionHtml,
832    location: row.location,
833    expandHeader: row.expandHeader,
834    startTime: row.startTime,
835    activities: Array.isArray(row.activities) ? row.activities.slice() : []
836  };
837}
838
839function _forms_findActivityIndexInRow(activities, sel) {
840  if (!sel || !activities || !activities.length) {
841    return 0;
842  }
843  var i;
844  var selCode = _forms_storageString(sel.code);
845  if (selCode !== '') {
846    for (i = 0; i < activities.length; i++) {
847      var ac = _forms_storageString(activities[i].code);
848      if (ac !== '' && ac === selCode) {
849        return i;
850      }
851    }
852  }
853  for (i = 0; i < activities.length; i++) {
854    var a = activities[i];
855    if ((a.title || '') === (sel.title || '') && (a.location || '') 
855=== (sel.location || '')) {
856      return i;
857    }
858  }
859  return 0;
860}
861
862function _forms_selectedActivityIndexForRow(activities, preselectedSessions, itemsLength, rowIndex) {
863  if (preselectedSessions && preselectedSessions.length === itemsLength && preselectedSessions[rowIndex]) {
864    return _forms_findActivityIndexInRow(activities, preselectedSessions[rowIndex]);
865  }
866  return 0;
867}
868
869/**
870 * One localStorage plain session → one picker row (single activity) for summary / final screen.
871 */
872function _forms_plainSessionToActivityPickerRow(plain) {
873  if (!plain || typeof plain !== 'object') {
874    plain = {};
875  }
876  var timeStr = _forms_formatSessionTime(plain.startDateTime);
877  var descPlain = _forms_storageString(plain.description).trim();
878  var imageRaw = _forms_storageString(plain.image);
879  var act = {
880    code: _forms_storageString(plain.code),
881    title: _forms_storageString(plain.title),
882    areaOfStudy: _forms_storageString(plain.areaOfStudy),
883    studyOption: _forms_storageString(plain.studyOption),
884    startDateTime: _forms_storageString(plain.startDateTime),
885    duration: _forms_storageString(plain.duration),
886    location: _forms_storageString(plain.location),
887    campus: _forms_storageString(plain.campus),
888    description: descPlain,
889    descriptionHtml: '',
890    image: _forms_absolutePublishImageUrlIfLocal(imageRaw, 'path')
891  };
892  return {
893    time: timeStr,
894    duration: act.duration,
895    startDateTime: act.startDateTime,
896    image: act.image,
897    title: act.title,
898    description: descPlain,
899    descriptionHtml: '',
900    location: act.location,
901    expandHeader: timeStr,
902    activities: [act]
903  };
904}
905
906function _forms_getSelectedSessionsAligned() {
907  var rows = _forms_opendayActivityPickerRows;
908  if (!rows || !rows.length) {
909    return [];
910  }
911  var n = rows.length;
912  var arr = _forms_getSelectedSessionsArray();
913  if (arr.length !== n) {
914    arr = rows.map(function (row) {
915      var acts = row.activities || [];
916      return acts.length ? _forms_sessionToStoragePlain(acts[0]) : _forms_sessionToStoragePlain({});
917    });
918    _forms_setSelectedSessionsArray(arr);
919  }
920  return arr;
921}
922
923function _forms_updateSelectedSessionAtSlot(slotIdx, plain) {
924  var arr = _forms_getSelectedSessionsAligned();
925  if (slotIdx >= 0 && slotIdx < arr.length) {
926    arr[slotIdx] = plain;
927    _forms_setSelectedSessionsArray(arr);
928  }
929}
930
931var _forms_activityPickerDocCloseBound = false;
932
933function _forms_bindActivityPickerDocumentClose() {
934  if (_forms_activityPickerDocCloseBound) {
935    return;
936  }
937  _forms_activityPickerDocCloseBound = true;
938  document.addEventListener('click', function () {
939    var expanded = document.querySelectorAll('.wsu-activity-picker .activity-item.expanded');
940    if (!expanded.length) {
941      return;
942    }
943    expanded.forEach(function (el) {
944      el.classList.remove('expanded');
945    });
946  });
947}
948
949/**
950 * When approval checkbox input's value is the string "true", programmatically click it (e.g. after Modify/Remove).
951 */
952function _forms_clickApprovalCheckboxIfValueTrue() {
953  var approvalInput = document.querySelector('input[name*="approvalcheckbox"]');
954  if (approvalInput && approvalInput.value === 'true') {
955    approvalInput.click();
956  }
957}
958
959function _forms_syncActivityCarouselAriaPressed(carousel) {
960  if (!carousel || !carousel.querySelectorAll) {
961    return;
962  }
963  carousel.querySelectorAll('.activity-carousel-item').forEach(function (a) {
964    a.setAttribute('aria-pressed', a.classList.contains('active') ? 'true' : 'false');
965  });
966}
967
968/**
969 * Applies a carousel choice: updates summary, localStorage, aria state, and collapses the row.
970 */
971function _forms_selectActivityCarouselItem(activityItem, activityEl, carousel) {
972  var carouselItems = carousel.querySelectorAll('.activity-carousel-item');
973  carouselItems.forEach(function (a) {
974    a.classList.remove('active');
975  });
976  activityEl.classList.add('active');
977  _forms_syncActivityCarouselAriaPressed(carousel);
978
979  var collapsedActivityTitleEl = activityItem.querySelector(
980    '.activity-collapsed .activity-collapsed-content .activity-title'
981  );
982  var collapsedActivityDescEl = activityItem.querySelector(
983    '.activity-collapsed .activity-collapsed-content .activity-summary'
984  );
985  var collapsedActivityImgEl = activityItem.querySelector('.activity-collapsed > img');
986  var collapsedLocationEl = activityItem.querySelector('.activity-collapsed .activity-location');
987  var activityItemTitleEl = activityEl.querySelector('.activity-title');
988  var activityItemDescEl = activityEl.querySelector('.activity-description');
989  var activityItemImgEl = activityEl.querySelector('img');
990  var loc = activityEl.getAttribute('data-location') || '';
991
992  if (collapsedActivityTitleEl && activityItemTitleEl) {
993    collapsedActivityTitleEl.textContent = activityItemTitleEl.textContent;
994  }
995  if (collapsedActivityDescEl && activityItemDescEl) {
996    collapsedActivityDescEl.innerHTML = activityItemDescEl.innerHTML;
997  }
998  if (collapsedActivityImgEl && activityItemImgEl) {
999    collapsedActivityImgEl.src = activityItemImgEl.src;
1000    collapsedActivityImgEl.alt = activityItemImgEl.getAttribute('alt') || '';
1001  }
1002  if (collapsedLocationEl) {
1003    var collapsedLocationTextEl = collapsedLocationEl.querySelector('.activity-location-text');
1004    if (collapsedLocationTextEl) {
1005      collapsedLocationTextEl.textContent = loc;
1006    } else {
1007      collapsedLocationEl.textContent = '';
1008      var locIconEl = document.createElement('div');
1009      locIconEl.className = 'activity-location-icon';
1010      var locTextEl = document.createElement('div');
1011      locTextEl.className = 'activity-location-text';
1012      locTextEl.textContent = loc;
1013      collapsedLocationEl.appendChild(locIconEl);
1014      collapsedLocationEl.appendChild(locTextEl);
1015    }
1016  }
1017
1018  var slotIdx = parseInt(activityItem.getAttribute('data-activity-slot-index'), 10);
1019  var rowsPick = _forms_opendayActivityPickerRows;
1020  if (!isNaN(slotIdx) && rowsPick && rowsPick[slotIdx]) {
1021    var actIndex = Array.prototype.indexOf.call(carouselItems, activityEl);
1022    var act =
1023      actIndex >= 0 && rowsPick[slotIdx].activities
1024        ? rowsPick[slotIdx].activities[actIndex]
1025        : null;
1026    if (act) {
1027      _forms_updateSelectedSessionAtSlot(slotIdx, _forms_sessionToStoragePlain(act));
1028    }
1029  }
1030
1031  activityItem.classList.remove('expanded');
1032}
1033
1034/**
1035 * Modify visibility: root must allow it (data-modify); each row must have more than one .activity-carousel-item
1036 * under .activity-carousel. Strips Modify when the parsed DOM has at most one carousel item for that row.
1037 */
1038function _forms_applyActivityPickerModifyVisibility(container) {
1039  if (!container || !container.querySelectorAll) {
1040    return;
1041  }
1042  var modifyAllowed = container.getAttribute('data-modify') === 'true';
1043  container.querySelectorAll('.activity-item').forEach(function (activityItem) {
1044    var carousel = activityItem.querySelector('.activity-carousel');
1045    if (!carousel) {
1046      return;
1047    }
1048    var itemCount = carousel.querySelectorAll('.activity-carousel-item').length;
1049    var modifyBtn = activityItem.querySelector('.activity-modify');
1050    if (!modifyBtn) {
1051      return;
1052    }
1053    if (!modifyAllowed || itemCount <= 1) {
1054      var actions = modifyBtn.parentNode;
1055      if (modifyBtn.parentNode) {
1056        modifyBtn.parentNode.removeChild(modifyBtn);
1057      }
1058      if (
1059        actions &&
1060        actions.classList &&
1061        actions.classList.contains('activity-actions') &&
1062        !actions.querySelector('button')
1063      ) {
1064        actions.parentNode.removeChild(actions);
1065      }
1066    }
1067  });
1068}
1069
1070/**
1071 * Expand on Modify tap, close on outside click / activity select;
1071 carousel updates collapsed summary.
1072 * Binds only the open-day root (.openday-activity-picker).
1073 */
1074function _forms_bindOpenDayActivityPickerInteractions() {
1075  var container = _forms_getOpenDayActivityPicker();
1076  if (!container) {
1077    return;
1078  }
1079  _forms_bindActivityPickerDocumentClose();
1080
1081  container.querySelectorAll('.activity-item').forEach(function (activityItem) {
1082    var activityCollapsedEl = activityItem.querySelector('.activity-collapsed');
1083    if (!activityCollapsedEl) {
1084      return;
1085    }
1086
1087    var modifyBtn = activityCollapsedEl.querySelector('.activity-modify');
1088    if (modifyBtn) {
1089      modifyBtn.addEventListener('click', function (e) {
1090        e.stopPropagation();
1091        _forms_clickApprovalCheckboxIfValueTrue();
1092        activityItem.classList.add('expanded');
1093        var expandCarousel = activityItem.querySelector('.activity-carousel');
1094        if (expandCarousel) {
1095          var focusTarget =
1096            expandCarousel.querySelector('.activity-carousel-item.active') ||
1097            expandCarousel.querySelector('.activity-carousel-item');
1098          if (focusTarget && typeof focusTarget.focus === 'function') {
1099            focusTarget.focus();
1100          }
1101        }
1102      });
1103    }
1104
1105    var removeBtn = activityCollapsedEl.querySelector('.activity-remove');
1106    if (removeBtn) {
1107      removeBtn.addEventListener('click', function (e) {
1108        e.stopPropagation();
1109        _forms_clickApprovalCheckboxIfValueTrue();
1110        var slotIdx = parseInt(activityItem.getAttribute('data-activity-slot-index'), 10);
1111        var rows = _forms_opendayActivityPickerRows;
1112        if (isNaN(slotIdx) || !rows) {
1113          return;
1114        }
1115        if (slotIdx < 0 || slotIdx >= rows.length) {
1116          return;
1117        }
1118        var arr = _forms_getSelectedSessionsAligned();
1119        rows.splice(slotIdx, 1);
1120        arr.splice(slotIdx, 1);
1121        _forms_renderOpenDayActivityPickerItems(rows, arr);
1122      });
1123    }
1124
1125    activityItem.addEventListener('click', function (e) {
1126      e.stopPropagation();
1127    });
1128
1129    var carousel = activityItem.querySelector('.activity-carousel');
1130    if (!carousel) {
1131      return;
1132    }
1133
1134    carousel.querySelectorAll('.activity-carousel-item').forEach(function (activityEl) {
1135      activityEl.addEventListener('click', function (e) {
1136        e.stopPropagation();
1137        _forms_selectActivityCarouselItem(activityItem, activityEl, carousel);
1138      });
1139      activityEl.addEventListener('keydown', function (e) {
1140        var key = e.key;
1141        var code = e.keyCode;
1142        var activate =
1143          key === 'Enter' ||
1144          key === ' ' ||
1145          key === 'Spacebar' ||
1146          (!key && (code === 13 || code === 32));
1147        if (!activate) {
1148          return;
1149        }
1150        if (key === ' ' || key === 'Spacebar' || code === 32) {
1151          e.preventDefault();
1152        }
1153        e.stopPropagation();
1154        _forms_selectActivityCarouselItem(activityItem, activityEl, carousel);
1155      });
1156    });
1157  });
1158}
1159
1160function _forms_compareActivityPickerRowsByTime(a, b) {
1161  return _forms_compareStartMsAscending(a && a.startDateTime, b && b.startDateTime);
1162}
1163
1164/**
1165 * @param {'openDay'|'confirm'} mode openDay: Remove uses data-remove. Modify only when data-modify is true and
1166 * each row's .activity-carousel has more than one .activity-carousel-item (enforced on the parsed DOM after render).
1167 * confirm: always read-only, no localStorage writes
1168 */
1169function _forms_renderActivityPickerIntoElement(container, data, preselectedSessions, mode) {
1170  if (!container || !container.classList || !container.classList.contains('wsu-activity-picker')) {
1171    return;
1172  }
1173
1174  var forceReadonly = mode === 'confirm';
1175  var modifyEnabledOnRoot = !forceReadonly && container.getAttribute('data-modify') === 'true';
1176  var showRemoveBtn = !forceReadonly && container.getAttribute('data-remove') === 'true';
1177  var isReadonlyPicker = forceReadonly || (!modifyEnabledOnRoot && !showRemoveBtn);
1178
1179  var items = Array.isArray(data) ? data : [];
1180  if (mode === 'openDay') {
1181    if (isReadonlyPicker) {
1182      _forms_opendayActivityPickerRows = null;
1183    } else {
1184      _forms_opendayActivityPickerRows = items.map(_forms_cloneActivityPickerRow);
1185    }
1186  }
1187
1188  var html = items.map(function (item, index) {
1189    var time = _forms_escapeHtml(item.time || item.startTime || '');
1190    var duration = _forms_escapeHtml(item.duration || '');
1191    var expandHeader = _forms_escapeHtml(
1192      item.expandHeader || item.expandTime || item.time || item.startTime || ''
1193    );
1194
1195    var activities = Array.isArray(item.activities) ? item.activities : [];
1196    var showModifyBtn = modifyEnabledOnRoot && activities.length > 1;
1197    var actionButtons = '';
1198    if (showModifyBtn) {
1199      actionButtons += '\n          <button type="button" class="activity-modify">Modify</button>';
1200    }
1201    if (showRemoveBtn) {
1202      actionButtons += '\n          <button type="button" class="activity-remove">Remove</button>';
1203    }
1204    var actionsBlock =
1205      actionButtons !== ''
1206        ? '\n        <div class="activity-actions">' + actionButtons + '\n        </div>'
1207        : '';
1208
1209    var selectedIdx = _forms_selectedActivityIndexForRow(
1210      activities,
1211      preselectedSessions,
1212      items.length,
1213      index
1214    );
1215    var collapsedAct = activities[selectedIdx] || {};
1216    var img = _forms_escapeAttr(
1217      collapsedAct.image || item.image || item.selectedImage || item.imgSrc || ''
1218    );
1219    var title = _forms_escapeHtml(collapsedAct.title || item.title || '');
1220    var collapsedAlt = _forms_escapeAttr(
1221      String(collapsedAct.title || item.title || '').trim()
1222    );
1223    var desc =
1224      collapsedAct.descriptionHtml && String(collapsedAct.descriptionHtml).trim() !== ''
1225        ? collapsedAct.descriptionHtml
1226        : item.descriptionHtml && String(item.descriptionHtml).trim() !== ''
1227          ? item.descriptionHtml
1228          : _forms_escapeHtml(
1229            _forms_storageString(collapsedAct.description).trim() ||
1230            _forms_storageString(item.description).trim()
1231          );
1232    var location = _forms_escapeHtml(collapsedAct.location || item.location || '');
1233
1234    var activitiesHtml = activities.map(function (act, actIndex) {
1235      var activeClass = actIndex === selectedIdx ? ' active' : '';
1236      var loc = _forms_escapeAttr(act.location || act.dataLocation || '');
1237      var aImg = _forms_escapeAttr(act.image || act.imgSrc || '');
1238      var aTitle = _forms_escapeHtml(act.title || '');
1239      var aAlt = _forms_escapeAttr(String(act.title || '').trim());
1240      var aDesc =
1241        act.descriptionHtml && String(act.descriptionHtml).trim() !== ''
1242          ? act.descriptionHtml
1243          : _forms_escapeHtml(_forms_storageString(act.description).trim());
1244      var a11yAttrs = '';
1245      if (!isReadonlyPicker) {
1246        var titlePlain = String(act.title || '').trim();
1247        var pickLabel =
1248          titlePlain !== '' ? 'Select session: ' + titlePlain : 'Select this session';
1249        a11yAttrs =
1250          ' tabindex="0" role="button" aria-pressed="' +
1251          (actIndex === selectedIdx ? 'true' : 'false') +
1252          '" aria-label="' +
1253          _forms_escapeAttr(pickLabel) +
1254          '"';
1255      }
1256      return (
1257        '            <div class="activity-carousel-item' +
1258        activeClass +
1259        '"' +
1260        a11yAttrs +
1261        ' data-location="' +
1262        loc +
1263        '">' +
1264        '\n              <img src="' + aImg + '" alt="' + aAlt + '" />' +
1265        '\n              <p class="activity-title">' + aTitle + '</p>' +
1266        '\n              <div class="activity-description">' + aDesc + '</div>' +
1267        '\n            </div>'
1268      );
1269    }).join('\n');
1270
1271    var expandedBlock = isReadonlyPicker
1272      ? ''
1273      : '\n      <div class="activity-expanded">' +
1274      '\n' +
1275      '\n        <div class="activity-expanded-header" id="activity-expanded-header-' +
1276      index +
1277      '">' +
1278      expandHeader +
1279      '</div>' +
1280      '\n' +
1281      '\n        <div class="activity-carousel-wrapper">' +
1282      '\n          <div class="activity-carousel" role="group" aria-labelledby="activity-expanded-header-' +
1283      index +
1284      '">' +
1285      '\n' +
1286      '\n' + activitiesHtml +
1287      '\n          </div>' +
1288      '\n        </div>' +
1289      '\n' +
1290      '\n      </div>';
1291
1292    return (
1293      '  <div class="activity-item" id="activityItem-' + index + '" data-activity-slot-index="' + index + '">' +
1294      '\n    <div class="activity-item-time">' +
1295      '\n      ' + time +
1296      '\n      <span>' + duration + '</span>' +
1297      '\n    </div>' +
1298      '\n' +
1299      '\n    <div class="activity-card">' +
1300      '\n' +
1301      '\n      <div class="activity-collapsed" id="activityCollapsed-' + index + '">' +
1302      '\n        <img id="selectedImg-' + index + '" src="' + img + '" alt="' + collapsedAlt + '" />' +
1303      '\n' +
1304      '\n        <div class="activity-collapsed-content">' +
1305      '\n          <p class="activity-title" id="selectedActivityTitle-' + index + '">' + title + '</p>' +
1306      '\n          <div class="activity-summary" id="selectedActivitySummary-' + index + '">' + desc + '</div>' +
1307      '\n          <div class="activity-location" id="selectedActivityLocation-' + index + '"><div class="activity-location-icon"></div><div class="activity-location-text">' + location + '</div></div>' +
1308      '\n        </div>' +
1309      actionsBlock +
1310      '\n      </div>' +
1311      '\n' +
1312      expandedBlock +
1313      '\n' +
1314      '\n    </div>' +
1315      '\n  </div>'
1316    );
1317  }).join('\n\n');
1318
1319  container.innerHTML = html;
1320  _forms_applyActivityPickerModifyVisibility(container);
1321
1322  if (!isReadonlyPicker && mode === 'openDay') {
1323    var selectedSessionsForStorage = items.map(function (item, index) {
1324      var activities = Array.isArray(item.activities) ? item.activities : [];
1325      var selectedIdx = _forms_selectedActivityIndexForRow(
1326        activities,
1327        preselectedSessions,
1328        items.length,
1329        index
1330      );
1331      if (activities[selectedIdx]) {
1332        return _forms_sessionToStoragePlain(activities[selectedIdx]);
1333      }
1334      return _forms_sessionToStoragePlain({});
1335    });
1336    _forms_setSelectedSessionsArray(selectedSessionsForStorage);
1337    _forms_bindOpenDayActivityPickerInteractions();
1338  }
1339}
1340
1341/** GraphQL / open-day root: .wsu-activity-picker.openday-activity-picker */
1342function _forms_renderOpenDayActivityPickerItems(data, preselectedSessions) {
1343  var root = _forms_getOpenDayActivityPicker();
1344  if (!root) {
1345    return;
1346  }
1347  _forms_renderActivityPickerIntoElement(root, data, preselectedSessions, 'openDay');
1348}
1349
1350/**Scroll to top of the page on each step of the form */
1351function _forms_scrollToTop() {
1352  window.scrollTo({
1353    top: 0,
1354    behavior: 'smooth'
1355  });
1356}
1357
1358/**
1359 * Confirmation panel: render .confirm-activity-picker from localStorage key "selectedSessions".
1360 * Expects a JSON array of plain session objects: code, title, areaOfStudy, studyOption, startDateTime,
1361 * duration, location, campus, description, image (root-relative or absolute URLs).
1362 * Call when that panel becomes visible (e.g. adaptive form navigation).
1363 */
1364function renderSelectedSessions() {
1365  _forms_scrollToTop();
1366  var root = document.querySelector('.confirm-activity-picker .wsu-activity-picker');
1367  if (!root) {
1368    return;
1369  }
1370  var stored = _forms_getSelectedSessionsArray();
1371  var ordered =
1372    stored.length > 1 ? stored.slice().sort(_forms_compareActivityPickerRowsByTime) : stored;
1373  var rows = ordered.map(_forms_plainSessionToActivityPickerRow);
1374  _forms_renderActivityPickerIntoElement(root, rows, ordered, 'confirm');
1375}
1376
1377function fetchAvailableSessions(areaOfStudy, studyOption) {
1378  _forms_scrollToTop();
1379  var pathSegment = _forms_buildOpendaySessionsGraphqlPath(areaOfStudy, studyOption);
1380  var base = _forms_getOrigin().replace(/\/+$/, '');
1381  var url = base + '/graphql/execute.json/global/' + pathSegment;
1382
1383  return fetch(url)
1384    .then(function (response) {
1385      if (!response.ok) {
1386        throw new Error('HTTP ' + response.status + ': ' + (response.statusText || 'request failed'));
1387      }
1388      return response.json();
1389    })
1390    .then(function (payload) {
1391      var activityPickerItems = _forms_graphqlPayloadToActivityPickerItems(payload);
1392      var itemsToRender;
1393      if (activityPickerItems.length > 0) {
1394        itemsToRender = activityPickerItems;
1395      } else if (payload && Array.isArray(payload)) {
1396        itemsToRender =
1397          payload.length > 1
1398            ? payload.slice().sort(_forms_compareActivityPickerRowsByTime)
1399            : payload;
1400      } else {
1401        itemsToRender = [];
1402      }
1403      _forms_renderOpenDayActivityPickerItems(itemsToRender, undefined);
1404    })
1405    .catch(function (err) {
1406      console.error('fetchActivityPickerSessions failed', err);
1407      _forms_renderOpenDayActivityPickerItems([], undefined);
1408    });
1409
1410}
1411
1412if (typeof document !== 'undefined') {
1413  if (document.readyState === 'loading') {
1414    document.addEventListener('DOMContentLoaded', _forms_clearSelectedSessionsOnLoad);
1415  } else {
1416    _forms_clearSelectedSessionsOnLoad();
1417  }
1418}
1419
1420
1421(function () {
1422  function summaryHtmlToPlainText(raw) {
1423    if (!raw) {
1424      return "";
1425    }
1426    var doc = new DOMParser().parseFromString(raw, "text/html");
1427    var text = doc.body.textContent || "";
1428    // &nbsp; and other space-like characters → normal space; strip zero-width / BOM
1429    text = text.replace(/[\u00a0\u2007\u202f\u2000-\u2006\u2008-\u200a\u3000]/g, " ");
1430    text = text.replace(/[\u200b\ufeff]/g, "");
1431    text = text.replace(/\s+/g, " ").replace(/^\s+|\s+$/g, "");
1432    return text;
1433  }
1434
1435  function sanitizeCardSummaries(scope) {
1436    var nodes = scope.querySelectorAll(".card-carousel__card-summary");
1437    var n;
1438    var raw;
1439    var plain;
1440
1441    for (n = 0; n < nodes.length; n++) {
1442      if (nodes[n].getAttribute("data-card-summary-sanitized") === "true") {
1443        continue;
1444      }
1445      raw = nodes[n].textContent.replace(/^\s+|\s+$/g, "");
1446      if (!raw) {
1447        nodes[n].setAttribute("data-card-summary-sanitized", "true");
1448        continue;
1449      }
1450
1451      plain = summaryHtmlToPlainText(raw);
1452      nodes[n].textContent = plain;
1453      nodes[n].setAttribute("data-card-summary-sanitized", "true");
1454    }
1455  }
1456
1457  function scrollStep(track) {
1458    var cards = track.querySelectorAll(".card-carousel__card");
1459    if (cards.length < 2) {
1460      return Math.max(1, Math.round(track.clientWidth * 0.85));
1461    }
1462    return cards[1].offsetLeft - cards[0].offsetLeft;
1463  }
1464
1465  function updateNav(track, prev, next) {
1466    var maxScroll = track.scrollWidth - track.clientWidth;
1467    var epsilon = 2;
1468    var left = track.scrollLeft;
1469
1470    if (maxScroll <= epsilon) {
1471      prev.disabled = true;
1472      next.disabled = true;
1473      return;
1474    }
1475
1476    prev.disabled = left <= epsilon;
1477    next.disabled = left >= maxScroll - epsilon;
1478  }
1479
1480  function initViewport(viewport) {
1481    sanitizeCardSummaries(viewport);
1482
1483    var track = viewport.querySelector(".js-card-carousel-track");
1484    var prev = viewport.querySelector(".js-card-carousel-prev");
1485    var next = viewport.querySelector(".js-card-carousel-next");
1486
1487    if (!track || !prev || !next) {
1488      return;
1489    }
1490
1491    function syncNav() {
1492      updateNav(track, prev, next);
1493    }
1494
1495    prev.addEventListener("click", function () {
1496      track.scrollBy({ left: -scrollStep(track), behavior: "smooth" });
1497    });
1498
1499    next.addEventListener("click", function () {
1500      track.scrollBy({ left: scrollStep(track), behavior: "smooth" });
1501    });
1502
1503    track.addEventListener("scroll", syncNav, { passive: true });
1504    window.addEventListener("resize", syncNav);
1505    window.addEventListener("load", syncNav);
1506
1507    // scrollWidth often wrong on first frame (fonts, lazy images, % widths).
1508    // Resize on the inner row catches when horizontal content size is known.
1509    var row = track.querySelector(".card-carousel__container");
1510    if (row && typeof ResizeObserver !== "undefined") {
1511      var ro = new ResizeObserver(function () {
1512        syncNav();
1513      });
1514      ro.observe(row);
1515      ro.observe(track);
1516    }
1517
1518    var imgs = track.querySelectorAll("img");
1519    for (var j = 0; j < imgs.length; j++) {
1520      if (!imgs[j].complete) {
1521        imgs[j].addEventListener("load", syncNav);
1522        imgs[j].addEventListener("error", syncNav);
1523      }
1524    }
1525
1526    if (typeof requestAnimationFrame === "function") {
1527      requestAnimationFrame(function () {
1528        requestAnimationFrame(syncNav);
1529      });
1530    } else {
1531      syncNav();
1532    }
1533  }
1534
1535  function initAll() {
1536    var viewports = document.querySelectorAll(".js-card-carousel");
1537    for (var i = 0; i < viewports.length; i++) {
1538      initViewport(viewports[i]);
1539    }
1540  }
1541
1542  if (document.readyState === "loading") {
1543    document.addEventListener("DOMContentLoaded", initAll);
1544  } else {
1545    initAll();
1546  }
1547})();
1548
1549var ADD_TO_CALENDAR_STORAGE_KEY = 'selectedSessions';
1550
1551function _addToCal_escapeIcsText(text) {
1552  if (text == null || text === '') {
1553    return '';
1554  }
1555  return String(text)
1556    .replace(/\\/g, '\\\\')
1557    .replace(/\n/g, '\\n')
1558    .replace(/;/g, '\\;')
1559    .replace(/,/g, '\\,');
1560}
1561
1562function _addToCal_pad2(n) {
1563  return n < 10 ? '0' + n : String(n);
1564}
1565
1566function _addToCal_formatUtc(dt) {
1567  return (
1568    dt.getUTCFullYear() +
1569    _addToCal_pad2(dt.getUTCMonth() + 1) +
1570    _addToCal_pad2(dt.getUTCDate()) +
1571    'T' +
1572    _addToCal_pad2(dt.getUTCHours()) +
1573    _addToCal_pad2(dt.getUTCMinutes()) +
1574    _addToCal_pad2(dt.getUTCSeconds()) +
1575    'Z'
1576  );
1577}
1578
1579/**
1580 * Returns end Date from start and duration string (best-effort). Fallback: +1 hour.
1581 */
1582function _addToCal_endFromDuration(startMs, durationStr) {
1583  var fallback = startMs + 60 * 60 * 1000;
1584  if (durationStr == null || String(durationStr).trim() === '') {
1585    return new Date(fallback);
1586  }
1587  var s = String(durationStr).trim();
1588  var m;
1589
1590  if (/^P/i.test(s)) {
1591    try {
1592      var iso = s.match(/^P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/i);
1593      if (iso) {
1594        var d = 0;
1595        var h = 0;
1596        var min = 0;
1597        var sec = 0;
1598        if (iso[1]) {
1599          d = parseInt(iso[1], 10) || 0;
1600        }
1601        if (iso[2]) {
1602          h = parseInt(iso[2], 10) || 0;
1603        }
1604        if (iso[3]) {
1605          min = parseInt(iso[3], 10) || 0;
1606        }
1607        if (iso[4]) {
1608          sec = parseInt(iso[4], 10) || 0;
1609        }
1610        var ms = (((d * 24 + h) * 60 + min) * 60 + sec) * 1000;
1611        if (ms > 0) {
1612          return new Date(startMs + ms);
1613        }
1614      }
1615    } catch (e) {
1616      /* fall through */
1617    }
1618  }
1619
1620  if (/^\d+$/.test(s)) {
1621    var mins = parseInt(s, 10);
1622    if (!isNaN(mins) && mins > 0) {
1623      return new Date(startMs + mins * 60 * 1000);
1624    }
1625  }
1626
1627  m = s.match(/(\d+(?:\.\d+)?)\s*(h|hr|hrs|hour|hours)\b/i);
1628  if (m) {
1629    var hours = parseFloat(m[1]);
1630    if (!isNaN(hours) && hours > 0) {
1631      return new Date(startMs + Math.round(hours * 60 * 60 * 1000));
1632    }
1633  }
1634
1635  m = s.match(/(\d+(?:\.\d+)?)\s*(m|min|mins|minute|minutes)\b/i);
1636  if (m) {
1637    var minutes = parseFloat(m[1]);
1638    if (!isNaN(minutes) && minutes > 0) {
1639      return new Date(startMs + Math.round(minutes * 60 * 1000));
1640    }
1641  }
1642
1643  m = s.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
1644  if (m) {
1645    var hh = parseInt(m[1], 10);
1646    var mm = parseInt(m[2], 10);
1647    var ss = m[3] ? parseInt(m[3], 10) : 0;
1648    if (!isNaN(hh) && !isNaN(mm)) {
1649      return new Date(startMs + ((hh * 60 + mm) * 60 + ss) * 1000);
1650    }
1651  }
1652
1653  return new Date(fallback);
1654}
1655
1656function _addToCal_foldLine(line) {
1657  var max = 75;
1658  if (line.length <= max) {
1659    return line;
1660  }
1661  var out = [];
1662  var rest = line;
1663  out.push(rest.substring(0, max));
1664  rest = rest.substring(max);
1665  while (rest.length > 0) {
1666    out.push(' ' + rest.substring(0, max - 1));
1667    rest = rest.substring(max - 1);
1668  }
1669  return out.join('\r\n');
1670}
1671
1672function _addToCal_buildVevent(session, index, formUrl) {
1673  var startRaw = session && session.startDateTime;
1674  if (!startRaw) {
1675    return '';
1676  }
1677  var start = new Date(startRaw);
1678  if (isNaN(start.getTime())) {
1679    return '';
1680  }
1681  var startMs = start.getTime();
1682  var end = _addToCal_endFromDuration(startMs, session.duration);
1683  var uidBase =
1684    (session.code || 'session') +
1685    '-' +
1686    _addToCal_formatUtc(start) +
1687    '-' +
1688    index +
1689    '@wsu-openday';
1690  var uid = _addToCal_escapeIcsText(uidBase.replace(/[^a-zA-Z0-9@-]/g, '-'));
1691
1692  var rawTitle = (session.title || 'Session').trim();
1693  var title = _addToCal_escapeIcsText('WSU Open Day: ' + rawTitle);
1694  var locParts = [];
1695  if (session.location) {
1696    locParts.push(String(session.location).trim());
1697  }
1698  if (session.campus) {
1699    locParts.push(String(session.campus).trim());
1700  }
1701  var location = _addToCal_escapeIcsText(locParts.filter(Boolean).join(' — '));
1702
1703  var lines = [
1704    'BEGIN:VEVENT',
1705    'UID:' + uid,
1706    'DTSTAMP:' + _addToCal_formatUtc(new Date()),
1707    'DTSTART:' + _addToCal_formatUtc(start),
1708    'DTEND:' + _addToCal_formatUtc(end),
1709    _addToCal_foldLine('SUMMARY:' + title)
1710  ];
1711  if (location) {
1712    lines.push(_addToCal_foldLine('LOCATION:' + location));
1713  }
1714  var descriptionParts = [];
1715  if (session.description) {
1716    descriptionParts.push(String(session.description).trim());
1717  }
1718  if (formUrl && String(formUrl).trim() !== '') {
1719    descriptionParts.push(String(formUrl).trim());
1720  }
1721  if (descriptionParts.length) {
1722    lines.push(
1723      _addToCal_foldLine('DESCRIPTION:' + _addToCal_escapeIcsText(descriptionParts.join('\n')))
1724    );
1725  }
1726  lines.push('END:VEVENT');
1727  return lines.join('\r\n');
1728}
1729
1730/**
1731 * Builds an iCalendar (.ics) from localStorage "selectedSessions" (same array shape as forms.js) and triggers download.
1732 * @param {string} [filename] optional download filename (default: openday-sessions.ics)
1733 * @returns {boolean} true if a file was generated and download started
1734 */
1735function addToCalendar(filename, formUrl) {
1736  if (typeof localStorage === 'undefined') {
1737    return false;
1738  }
1739  var raw;
1740  try {
1741    raw = localStorage.getItem(ADD_TO_CALENDAR_STORAGE_KEY);
1742  } catch (e) {
1743    return false;
1744  }
1745  if (!raw) {
1746    return false;
1747  }
1748  var sessions;
1749  try {
1750    sessions = JSON.parse(raw);
1751  } catch (e) {
1752    return false;
1753  }
1754  if (!Array.isArray(sessions) || !sessions.length) {
1755    return false;
1756  }
1757
1758  var events = [];
1759  var i;
1760  for (i = 0; i < sessions.length; i++) {
1761    var block = _addToCal_buildVevent(sessions[i], i, formUrl);
1762    if (block) {
1763      events.push(block);
1764    }
1765  }
1766  if (!events.length) {
1767    return false;
1768  }
1769
1770  var icsBody =
1771    'BEGIN:VCALENDAR\r\n' +
1772    'VERSION:2.0\r\n' +
1773    'PRODID:-//WSU//Open Day Form//EN\r\n' +
1774    'CALSCALE:GREGORIAN\r\n' +
1775    'METHOD:PUBLISH\r\n' +
1776    events.join('\r\n') +
1777    '\r\nEND:VCALENDAR\r\n';
1778
1779  var blob;
1780  try {
1781    blob = new Blob([icsBody], { type: 'text/calendar;charset=utf-8' });
1782  } catch (e) {
1783    return false;
1784  }
1785
1786  var name =
1787    filename && String(filename).trim() !== ''
1788      ? String(filename).trim()
1789      : 'openday-sessions.ics';
1790  if (name.indexOf('.ics') === -1) {
1791    name += '.ics';
1792  }
1793
1794  var url = URL.createObjectURL(blob);
1795  var a = document.createElement('a');
1796  a.href = url;
1797  a.download = name;
1798  a.rel = 'noopener';
1799  document.body.appendChild(a);
1800  a.click();
1801  document.body.removeChild(a);
1802  setTimeout(function () {
1803    URL.revokeObjectURL(url);
1804  }, 0);
1805
1806  return true;
1807}
1808
1809/**
1810 * Parses form payload strings into a plain object.
1811 * Examples:
1812 * - formName:Open Day Form,email:[email protected]
1813 * - formName:Open Day Form
1814 * Outer wrapping single/double quotes are stripped; each segment uses the first ':' as key/value separator (values may contain ':').
1815 */
1816function parseStringToObject(str) {
1817  const result = {};
1818
1819  if (!str || typeof str !== 'string') {
1820    return result;
1821  }
1822
1823  let s = str.trim();
1824  while (
1825    (s.charAt(0) === '"' && s.charAt(s.length - 1) === '"') ||
1826    (s.charAt(0) === "'" && s.charAt(s.length - 1) === "'")
1827  ) {
1828    s = s.slice(1, -1).trim();
1829  }
1830
1831  function addPair(partRaw) {
1832    let part = String(partRaw).trim();
1833    if (!part) {
1834      return;
1835    }
1836    const colon = part.indexOf(':');
1837    if (colon === -1) {
1838      return;
1839    }
1840    const key = part.slice(0, colon).trim();
1841    const value = part.slice(colon + 1).trim();
1842    if (key) {
1843      result[key] = value;
1844    }
1845  }
1846
1847  s.split(',').forEach(function (segment) {
1848    addPair(segment);
1849  });
1850
1851  return result;
1852}
1853
1854function ensureDigitalDataEventData() {
1855  window.digitalData = window.digitalData || {};
1856  window.digitalData.event = window.digitalData.event || {};
1857  window.digitalData.event.data = window.digitalData.event.data || {};
1858  return window.digitalData.event.data;
1859}
1860
1861function trackAnalytics(event, formData) {
1862  let formDataObject = parseStringToObject(formData);
1863  ensureDigitalDataEventData();
1864  window.digitalData.event.data.form = formDataObject.formName;
1865
1866  switch (event) {
1867    case 'form_start_AA':
1868      _satellite.track(event, {
1869        form_name: window.digitalData.event.data.form
1870      });
1871      break;
1872    case 'form_complete_AA':
1873      window.digitalData.event.data.email = formDataObject.email;
1874      _satellite.track('form_complete_AA', {
1875        form_name: window.digitalData.event.data.form,
1876        email: window.digitalData.event.data.email
1877      });
1878      break;
1879  }
1880}
1881
1882/**
1883 * Country reference data shared by intl-tel.js.
1884 *
1885 * Entry: { n: display name, c: ISO 3166-1 alpha-2, d: dialling prefix }.
1886 * NANP territories carry their full prefix (e.g. "1876"); US/Canada stay "1".
1887 * WSU_COUNTRY_ALIASES maps an ISO code to alternative drop-down option names.
1888 */
1889(function (global) {
1890  "use strict";
1891
1892  var DATA = [
1893    ["Afghanistan", "AF", "93"], ["Albania", "AL", "355"], ["Algeria", "DZ", "213"],
1894    ["Andorra", "AD", "376"], ["Angola", "AO", "244"], ["Antigua and Barbuda", "AG", "1268"],
1895    ["Argentina", "AR", "54"], ["Armenia", "AM", "374"], ["Aruba", "AW", "297"],
1896    ["Australia", "AU", "61"], ["Austria", "AT", "43"], ["Azerbaijan", "AZ", "994"],
1897    ["Bahamas", "BS", "1242"], ["Bahrain", "BH", "973"], ["Bangladesh", "BD", "880"],
1898    ["Barbados", "BB", "1246"], ["Belarus", "BY", "375"], ["Belgium", "BE", "32"],
1899    ["Belize", "BZ", "501"], ["Benin", "BJ", "229"], ["Bermuda", "BM", "1441"],
1900    ["Bhutan", "BT", "975"], ["Bolivia", "BO", "591"], ["Bosnia and Herzegovina", "BA", "387"],
1901    ["Botswana", "BW", "267"], ["Brazil", "BR", "55"], ["Brunei", "BN", "673"],
1902    ["Bulgaria", "BG", "359"], ["Burkina Faso", "BF", "226"], ["Burundi", "BI", "257"],
1903    ["Cambodia", "KH", "855"], ["Cameroon", "CM", "237"], ["Canada", "CA", "1"],
1904    ["Cape Verde", "CV", "238"], ["Cayman Islands", "KY", "1345"],
1905    ["Central African Republic", "CF", "236"], ["Chad", "TD", "235"], ["Chile", "CL", "56"],
1906    ["China", "CN", "86"], ["Colombia", "CO", "57"], ["Comoros", "KM", "269"],
1907    ["Congo (Brazzaville)", "CG", "242"], ["Congo (Kinshasa)", "CD", "243"],
1908    ["Costa Rica", "CR", "506"], ["Cote d'Ivoire", "CI", "225"], ["Croatia", "HR", "385"],
1909    ["Cuba", "CU", "53"], ["Cyprus", "CY", "357"], ["Czechia", "CZ", "420"],
1910    ["Denmark", "DK", "45"], ["Djibouti", "DJ", "253"], ["Dominica", "DM", "1767"],
1911    ["Dominican Republic", "DO", "1809"], ["Ecuador", "EC", "593"], ["Egypt", "EG", "20"],
1912    ["El Salvador", "SV", "503"], ["Equatorial Guinea", "GQ", "240"], ["
1912Eritrea", "ER", "291"],
1913    ["Estonia", "EE", "372"], ["Eswatini", "SZ", "268"], ["Ethiopia", "ET", "251"],
1914    ["Fiji", "FJ", "679"], ["Finland", "FI", "358"], ["France", "FR", "33"],
1915    ["French Guiana", "GF", "594"], ["French Polynesia", "PF", "689"], ["Gabon", "GA", "241"],
1916    ["Gambia", "GM", "220"], ["Georgia", "GE", "995"], ["Germany", "DE", "49"],
1917    ["Ghana", "GH", "233"], ["Gibraltar", "GI", "350"], ["Greece", "GR", "30"],
1918    ["Greenland", "GL", "299"], ["Grenada", "GD", "1473"], ["Guadeloupe", "GP", "590"],
1919    ["Guam", "GU", "1671"], ["Guatemala", "GT", "502"], ["Guinea", "GN", "224"],
1920    ["Guinea-Bissau", "GW", "245"], ["Guyana", "GY", "592"], ["Haiti", "HT", "509"],
1921    ["Honduras", "HN", "504"], ["Hong Kong", "HK", "852"], ["Hungary", "HU", "36"],
1922    ["Iceland", "IS", "354"], ["India", "IN", "91"], ["Indonesia", "ID", "62"],
1923    ["Iran", "IR", "98"], ["Iraq", "IQ", "964"], ["Ireland", "IE", "353"],
1924    ["Israel", "IL", "972"], ["Italy", "IT", "39"], ["Jamaica", "JM", "1876"],
1925    ["Japan", "JP", "81"], ["Jordan", "JO", "962"], ["Kazakhstan", "KZ", "7"],
1926    ["Kenya", "KE", "254"], ["Kiribati", "KI", "686"], ["Kosovo", "XK", "383"],
1927    ["Kuwait", "KW", "965"], ["Kyrgyzstan", "KG", "996"], ["Laos", "LA", "856"],
1928    ["Latvia", "LV", "371"], ["Lebanon", "LB", "961"], ["Lesotho", "LS", "266"],
1929    ["Liberia", "LR", "231"], ["Libya", "LY", "218"], ["Liechtenstein", "LI", "423"],
1930    ["Lithuania", "LT", "370"], ["Luxembourg", "LU", "352"], ["Macau", "MO", "853"],
1931    ["Madagascar", "MG", "261"], ["Malawi", "MW", "265"], ["Malaysia", "MY", "60"],
1932    ["Maldives", "MV", "960"], ["Mali", "ML", "223"], ["Malta", "MT", "356"],
1933    ["Marshall Islands", "MH", "692"], ["Martinique", "MQ", "596"], ["Mauritania", "MR", "222"],
1934    ["Mauritius", "MU", "230"], ["Mexico", "MX", "52"], ["Micronesia", "FM", "691"],
1935    ["Moldova", "MD", "373"], ["Monaco", "MC", "377"], ["Mongolia", "MN", "976"],
1936    ["Montenegro", "ME", "382"], ["Montserrat", "MS", "1664"], ["Morocco", "MA", "212"],
1937    ["Mozambique", "MZ", "258"], ["Myanmar", "MM", "95"], ["Namibia", "NA", "264"],
1938    ["Nauru", "NR", "674"], ["Nepal", "NP", "977"], ["Netherlands", "NL", "31"],
1939    ["New Caledonia", "NC", "687"], ["New Zealand", "NZ", "64"], ["Nicaragua", "NI", "505"],
1940    ["Niger", "NE", "227"], ["Nigeria", "NG", "234"], ["North Korea", "KP", "850"],
1941    ["North Macedonia", "MK", "389"], ["Norway", "NO", "47"], ["Oman", "OM", "968"],
1942    ["Pakistan", "PK", "92"], ["Palau", "PW", "680"], ["Palestine", "PS", "970"],
1943    ["Panama", "PA", "507"], ["Papua New Guinea", "PG", "675"], ["Paraguay", "PY", "595"],
1944    ["Peru", "PE", "51"], ["Philippines", "PH", "63"], ["Poland", "PL", "48"],
1945    ["Portugal", "PT", "351"], ["Puerto Rico", "PR", "1787"], ["Qatar", "QA", "974"],
1946    ["Reunion", "RE", "262"], ["Romania", "RO", "40"], ["Russia", "RU", "7"],
1947    ["Rwanda", "RW", "250"], ["Saint Kitts and Nevis", "KN", "1869"], ["Saint Lucia", "LC", "1758"],
1948    ["Saint Vincent and the Grenadines", "VC", "1784"], ["Samoa", "WS", "685"],
1949    ["San Marino", "SM", "378"], ["Sao Tome and Principe", "ST", "239"],
1950    ["Saudi Arabia", "SA", "966"], ["Senegal", "SN", "221"], ["Serbia", "RS", "381"],
1951    ["Seychelles", "SC", "248"], ["Sierra Leone", "SL", "232"], ["Singapore", "SG", "65"],
1952    ["Slovakia", "SK", "421"], ["Slovenia", "SI", "386"], ["Solomon Islands", "SB", "677"],
1953    ["Somalia", "SO", "252"], ["South Africa", "ZA", "27"], ["South Korea", "KR", "82"],
1954    ["South Sudan", "SS", "211"], ["Spain", "ES", "34"], ["Sri Lanka", "LK", "94"],
1955    ["Sudan", "SD", "249"], ["Suriname", "SR", "597"], ["Sweden", "SE", "46"],
1956    ["Switzerland", "CH", "41"], ["Syria", "SY", "963"], ["Taiwan", "TW", "886"],
1957    ["Tajikistan", "TJ", "992"], ["Tanzania", "TZ", "255"], ["Thailand", "TH", "66"],
1958    ["Timor-Leste", "TL", "670"], ["Togo", "TG", "228"], ["Tonga", "TO", "676"],
1959    ["Trinidad and Tobago", "TT", "1868"], ["Tunisia", "TN", "216"], ["Turkey", "TR", "90"],
1960    ["Turkmenistan", "TM", "993"], ["Turks and Caicos Islands", "TC", "1649"],
1961    ["Tuvalu", "TV", "688"], ["Uganda", "UG", "256"], ["Ukraine", "UA", "380"],
1962    ["United Arab Emirates", "AE", "971"], ["United Kingdom", "GB", "44"],
1963    ["United States", "US", "1"], ["Uruguay", "UY", "598"], ["Uzbekistan", "UZ", "998"],
1964    ["Vanuatu", "VU", "678"], ["Vatican City", "VA", "379"], ["Venezuela", "VE", "58"],
1965    ["Vietnam", "VN", "84"], ["Yemen", "YE", "967"], ["Zambia", "ZM", "260"],
1966    ["Zimbabwe", "ZW", "263"]
1967  ];
1968
1969  var ALIASES = {
1970    US: ["usa", "u s a", "united states of america", "america"],
1971    GB: ["uk", "u k", "great britain", "britain", "england", "scotland", "wales", "northern ireland"],
1972    KR: ["korea", "republic of korea", "korea republic of", "korea south", "south korea"],
1973    KP: ["korea democratic peoples republic of", "korea north", "dprk"],
1974    CD: ["democratic republic of the congo", "congo democratic republic of the", "drc", "congo drc", "zaire"],
1975    CG: ["republic of the congo", "congo republic of the", "congo"],
1976    CI: ["ivory coast", "cote divoire"],
1977    CZ: ["czech republic"],
1978    SZ: ["swaziland"],
1979    MK: ["macedonia", "former yugoslav republic of macedonia"],
1980    MM: ["burma"],
1981    TL: ["east timor"],
1982    CV: ["cabo verde"],
1983    VA: ["holy see", "holy see vatican city state"],
1984    RU: ["russian federation"],
1985    SY: ["syrian arab republic"],
1986    IR: ["iran islamic republic of"],
1987    LA: ["lao peoples democratic republic"],
1988    MD: ["moldova republic of"],
1989    TZ: ["tanzania united republic of"],
1990    VN: ["viet nam"],
1991    BN: ["brunei darussalam"],
1992    BO: ["bolivia plurinational state of"],
1993    VE: ["venezuela bolivarian republic of"],
1994    PS: ["palestinian territory", "palestinian territory occupied", "state of palestine"],
1995    HK: ["hong kong sar", "hong kong sar china", "hong kong special administrative region"],
1996    MO: ["macao", "macao sar", "macau sar"]
1997  };
1998
1999  global.WSU_COUNTRY_DATA = DATA.map(function (row) {
2000    return { n: row[0], c: row[1], d: row[2] };
2001  });
2002
2003  global.WSU_COUNTRY_ALIASES = ALIASES;
2004
2005  /* Preferred ISO codes, most relevant first. */
2006  global.WSU_COUNTRY_PREFERRED = ["AU", "NZ", "CN", "IN", "GB", "US"];
2007
2008  /* ISO alpha-2 -> regional-indicator flag emoji. */
2009  global.wsuCountryFlag = function (iso2) {
2010    if (!iso2 || iso2.length !== 2) {
2011      return "";
2012    }
2013    var base = 127397;
2014    return String.fromCodePoint(base + iso2.toUpperCase().charCo
2014deAt(0)) +
2015      String.fromCodePoint(base + iso2.toUpperCase().charCodeAt(1));
2016  };
2017})(typeof window !== "undefined" ? window : this);
2018
2019/**
2020 * Country calling-code picker for an Adaptive Form phone field.
2021 *
2022 * Progressive enhancement: attaches a flag + calling-code picker to the phone
2023 * field and, for a non-default country, pre-selects the "country of residence"
2024 * drop-down. The visible input keeps the national number only; the picked code
2025 * is exposed as data-country-code on the field input and wrapper.
2026 *
2027 * Activation: field Element name "phoneNumber" (extend via
2028 * window.WSU_INTL_TEL_FIELDS), or class "intl-tel" / attribute data-intl-tel on
2029 * the field or an ancestor. Force off with class "no-intl-tel" or
2030 * data-intl-tel="false". Optional field attributes: data-intl-tel-residence,
2031 * data-intl-tel-default (ISO-2, defaults to AU).
2032 *
2033 * API: window.wsuIntlTel.getCountryCode(name) / getFullNumber(name).
2034 */
2035(function (global) {
2036  "use strict";
2037
2038  var doc = global.document;
2039  if (!doc) {
2040    return;
2041  }
2042
2043  var FIELD_SELECTOR = ".cmp-adaptiveform-telephoneinput, .cmp-adaptiveform-textinput";
2044  var WIDGET_SELECTOR =
2045    ".cmp-adaptiveform-telephoneinput__widget, .cmp-adaptiveform-textinput__widget";
2046  var DEFAULT_ISO = "AU";
2047  var AU_ISO = "AU";
2048  var FONT_URL =
2049    "/etc.clientlibs/wsu-international/clientlibs/clientlib-forms/resources/fonts/TwemojiCountryFlags.woff2";
2050
2051  var COUNTRIES = (global.WSU_COUNTRY_DATA || []).slice().sort(function (a, b) {
2052    return a.n.localeCompare(b.n);
2053  });
2054  var ALIASES = global.WSU_COUNTRY_ALIASES || {};
2055  var flagFor = global.wsuCountryFlag || function () { return ""; };
2056
2057  /* Field Element names picked up by name (lower-cased for comparison). */
2058  var NAME_MATCHES = (global.WSU_INTL_TEL_FIELDS || ["phoneNumber"])
2059    .map(function (s) { return String(s).toLowerCase(); });
2060
2061  var internalResidenceChange = false;
2062  var widgetSeq = 0;
2063
2064  /* -------------------------------------------------------------- lookups -- */
2065
2066  var byIso = {};
2067  COUNTRIES.forEach(function (c) {
2068    byIso[c.c] = c;
2069  });
2070
2071  /* Longest dial code first so "+1876" beats "+1", "+61" is unambiguous, etc. */
2072  var DIAL_MATCH_ORDER = COUNTRIES.slice().sort(function (a, b) {
2073    if (b.d.length !== a.d.length) {
2074      return b.d.length - a.d.length;
2075    }
2076    return a.n.localeCompare(b.n);
2077  });
2078
2079  function countryByDialDigits(digits) {
2080    if (!digits) {
2081      return null;
2082    }
2083    for (var i = 0; i < DIAL_MATCH_ORDER.length; i++) {
2084      if (digits.indexOf(DIAL_MATCH_ORDER[i].d) === 0) {
2085        return DIAL_MATCH_ORDER[i];
2086      }
2087    }
2088    return null;
2089  }
2090
2091  function defaultCountryFor(field) {
2092    var iso = (field.getAttribute("data-intl-tel-default") || DEFAULT_ISO).toUpperCase();
2093    return byIso[iso] || byIso[DEFAULT_ISO] || COUNTRIES[0];
2094  }
2095
2096  /* ---------------------------------------------------------- text helpers -- */
2097
2098  function normalize(str) {
2099    var s = String(str == null ? "" : str).toLowerCase();
2100    if (typeof s.normalize === "function") {
2101      s = s.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
2102    }
2103    return s.replace(/[^a-z0-9]+/g, " ").trim();
2104  }
2105
2106  function digitsOnly(str) {
2107    return String(str == null ? "" : str).replace(/\D/g, "");
2108  }
2109
2110  function nationalPart(raw, prevDialDigits) {
2111    var v = String(raw == null ? "" : raw).trim();
2112    if (v.charAt(0) !== "+") {
2113      return digitsOnly(v);
2114    }
2115    var digits = digitsOnly(v);
2116    var prev = digitsOnly(prevDialDigits || "");
2117    if (prev && digits.indexOf(prev) === 0) {
2118      return digits.slice(prev.length);
2119    }
2120    var match = countryByDialDigits(digits);
2121    return match ? digits.slice(match.d.length) : digits;
2122  }
2123
2124  /* ------------------------------------------------ country of residence -- */
2125
2126  function cssEscape(value) {
2127    if (global.CSS && typeof global.CSS.escape === "function") {
2128      return global.CSS.escape(value);
2129    }
2130    return String(value).replace(/["\\]/g, "\\$&");
2131  }
2132
2133  function residenceSelectFor(field) {
2134    var explicit = field.getAttribute("data-intl-tel-residence");
2135    if (explicit) {
2136      return doc.querySelector('select[name="' + cssEscape(explicit) + '"]');
2137    }
2138    var selects = doc.querySelectorAll("select");
2139    for (var i = 0; i < selects.length; i++) {
2140      var s = selects[i];
2141      if (/residen/i.test((s.getAttribute("name") || "") + " " + (s.id || ""))) {
2142        return s;
2143      }
2144      var wrap = s.closest("[class*='adaptiveform']");
2145      var label = wrap && wrap.querySelector("label, [class*='__label']");
2146      if (label && /country of residence/i.test(label.textContent || "")) {
2147        return s;
2148      }
2149    }
2150    return null;
2151  }
2152
2153  function matchResidenceOption(select, country) {
2154    var wanted = [normalize(country.n)];
2155    (ALIASES[country.c] || []).forEach(function (a) {
2156      wanted.push(normalize(a));
2157    });
2158    var iso = country.c.toLowerCase();
2159    for (var i = 0; i < select.options.length; i++) {
2160      var opt = select.options[i];
2161      if (opt.value === "") {
2162        continue;
2163      }
2164      var text = normalize(opt.textContent).replace(/^the /, "");
2165      if (wanted.indexOf(text) !== -1 ||
2166        wanted.indexOf(normalize(opt.value)) !== -1 ||
2167        String(opt.value).toLowerCase() === iso) {
2168        return opt;
2169      }
2170    }
2171    return null;
2172  }
2173
2174  function bindResidenceUserTouch(select) {
2175    if (select.dataset.intlTelResidenceBound === "true") {
2176      return;
2177    }
2178    select.dataset.intlTelResidenceBound = "true";
2179    select.addEventListener("change", function () {
2180      if (internalResidenceChange) {
2181        return;
2182      }
2183      select.dataset.intlTelUserTouched = "true";
2184      select.dataset.intlTelAutofilled = "false";
2185    });
2186  }
2187
2188  function setResidenceValue(select, value) {
2189    if (select.value === value) {
2190      return true;
2191    }
2192    internalResidenceChange = true;
2193    select.value = value;
2194    var applied = select.value === value || value === "";
2195    if (applied) {
2196      select.dispatchEvent(new Event("input", { bubbles: true }));
2197      select.dispatchEvent(new Event("change", { bubbles: true }));
2198    }
2199    internalResidenceChange = false;
2200    return applied;
2201  }
2202
2203  function syncResidence(field, country) {
2204    var select = residenceSelectFor(field);
2205    if (!select || select.multiple) {
2206      return;
2207    }
2208    bindResidenceUserTouch(select);
2209
2210    var autofilled = select.dataset.intlTelAutofilled === "true";
2211    var userTouched = select.dataset.intlTelUserTouched === "true";
2212    var hasValue = select.value !== "" && select.value != null;
2213
2214    // Never overwrite a value the visitor picked or that was set elsewhere.
2215    if (!autofilled && (userTouched || hasValue)) {
2216      return;
2217    }
2218
2219    if (country.c === AU_ISO || country.d === "61") {
2220      if (autofilled) {
2221        setResidenceValue(select, "");
2222        select.dataset.intlTelAutofilled = "false";
2223      }
2224      return;
2225    }
2226
2227    var opt = matchResidenceOption(select, country);
2228    if (opt && setResidenceValue(select, opt.value)) {
2229      select.dataset.intlTelAutofilled = "true";
2230    }
2231  }
2232
2233  /* ----------------------------------------------------------------- CSS -- */
2234
2235  function injectStyles() {
2236    if (doc.getElementById("wsu-intl-tel-styles")) {
2237      return;
2238    }
2239    var css =
2240      '@font-face{font-family:"Twemoji Country Flags";' +
2241      'src:url("' + FONT_URL + '") format("woff2");font-display:swap;' +
2242      'unicode-range:U+1F1E6-1F1FF,U+1F3F4,U+E0060-E007F}' +
2243      '.wsu-intl-tel{position:relative;display:block}' +
2244      '.wsu-intl-tel__input.wsu-intl-tel__input{padding-left:4rem}' +
2245      '.wsu-intl-tel__toggle{position:absolute;left:1px;top:1px;bottom:1px;display:flex;' +
2246      'align-items:center;gap:.25rem;margin:0;margin-top:12px;padding:0 .5rem;' +
2247      'background:transparent;border:0;border-right:1px solid rgba(0,0,0,.2);border-radius:0;' +
2248      'font:inherit;line-height:1;color:inherit;cursor:pointer}' +
2249      '.wsu-intl-tel__toggle:hover{background:rgba(0,0,0,.04)}' +
2250      '.wsu-intl-tel__toggle:focus-visible{outline:2px solid currentColor;outline-offset:-2px}' +
2251      '.wsu-intl-tel__flag{display:inline-block;min-width:1.4em;font-size:1.15em;line-height:1;' +
2252      'text-align:center;letter-spacing:0;font-weight:400;font-style:normal;' +
2253      'font-family:"Twemoji Country Flags","Noto Color Emoji","Apple Color Emoji",' +
2254      '"Segoe UI Emoji","Segoe UI Symbol",sans-serif}' +
2255      '.wsu-intl-tel__caret{width:0;height:0;border-left:4px solid transparent;' +
2256      'border-right:4px solid transparent;border-top:5px solid currentColor;opacity:.55}' +
2257      '.wsu-intl-tel.is-open .wsu-intl-tel__caret{transform:rotate(180deg)}' +
2258      '.wsu-intl-tel__panel{display:none;position:absolute;z-index:60;top:calc(100% + 2px);' +
2259      'left:0;width:340px;max-width:92vw;background:#fff;color:#000;' +
2260      'border:1px solid rgba(0,0,0,.25);border-radius:4px;box-shadow:0 6px 24px rgba(0,0,0,.18);' +
2261      'overflow:hidden}' +
2262      '.wsu-intl-tel.is-open .wsu-intl-tel__panel{display:block}' +
2263      '.wsu-intl-tel__list{list-style:none;margin:0;padding:.25rem 0;max-height:264px;' +
2264      'overflow-y:auto}' +
2265      '.wsu-intl-tel__option{display:flex;align-items:center;gap:.6rem;padding:.5rem .75rem;' +
2266      'font:inherit;cursor:pointer;outline:none}' +
2267      '.wsu-intl-tel__option.is-active,.wsu-intl-tel__option:hover{background:#ececec}' +
2268      '.wsu-intl-tel__option[aria-selected="true"]{font-weight:700}' +
2269      '.wsu-intl-tel__option-name{flex:1 1 auto}' +
2270      '.wsu-intl-tel__option-dial{flex:0 0 auto;color:rgba(0,0,0,.6)}' +
2271      '[dir="rtl"] .wsu-intl-tel__input.wsu-intl-tel__input{padding-left:0;padding-right:4rem}' +
2272      '[dir="rtl"] .wsu-intl-tel__toggle{left:auto;right:1px;border-right:0;' +
2273      'border-left:1px solid rgba(0,0,0,.2)}' +
2274      '[dir="rtl"] .wsu-intl-tel__panel{left:auto;right:0}';
2275    var style = doc.createElement("style");
2276    style.id = "wsu-intl-tel-styles";
2277    style.textContent = css;
2278    (doc.head || doc.documentElement).appendChild(style);
2279  }
2280
2281  /* --------------------------------------------------------------- widget -- */
2282
2283  /* Precedence: force off ("no-intl-tel" / data-intl-tel="false"), then force on
2284     ("intl-tel" class / data-intl-tel), then match on field Element name. */
2285  function isActivated(field, input) {
2286    if ((input && input.closest && input.closest(".no-intl-tel")) ||
2287      field.closest(".no-intl-tel") ||
2288      field.getAttribute("data-intl-tel") === "false") {
2289      return false;
2290    }
2291    if ((input && input.closest && input.closest(".intl-tel")) ||
2292      field.closest(".intl-tel") ||
2293      (field.hasAttribute("data-intl-tel") &&
2294        field.getAttribute("data-intl-tel") !== "false")) {
2295      return true;
2296    }
2297    var name = String(
2298      field.getAttribute("data-field-name") || (input && input.name) || ""
2299    ).toLowerCase();
2300    return name !== "" && NAME_MATCHES.indexOf(name) !== -1;
2301  }
2302
2303  function buildOption(country, isSelected, id) {
2304    var li = doc.createElement("li");
2305    li.className = "wsu-intl-tel__option";
2306    li.id = id;
2307    li.setAttribute("role", "option");
2308    li.setAttribute("tabindex", "-1");
2309    li.setAttribute("aria-selected", isSelected ? "true" : "false");
2310    li.dataset.iso = country.c;
2311    li.dataset.search = normalize(country.n + " " + country.c + " " + country.d);
2312
2313    var flag = doc.createElement("span");
2314    flag.className = "wsu-intl-tel__flag";
2315    flag.textContent = flagFor(country.c);
2316    var name = doc.createElement("span");
2317    name.className = "wsu-intl-tel__option-name";
2318    name.textContent = country.n;
2319    var dial = doc.createElement("span");
2320    dial.className = "wsu-intl-tel__option-dial";
2321    dial.textContent = "+" + country.d;
2322
2323    li.appendChild(flag);
2324    li.appendChild(name);
2325    li.appendChild(dial);
2326    return li;
2327  }
2328
2329  function enhanceField(field) {
2330    if (field.dataset.intlTelReady === "true" && field.querySelector(".wsu-intl-tel")) {
2331      return;
2332    }
2333    var input = field.querySelector(WIDGET_SELECTOR) ||
2334      field.querySelector('input[type="tel"]') || field.querySelector("input");
2335    if (!input || !isActivated(field, input)) {
2336      return;
2337    }
2338    field.dataset.intlTelReady = "true";
2339    injectStyles();
2340
2341    var uid = "wsu-intl-tel-" + (++widgetSeq);
2342    var state = { country: defaultCountryFor(field) };
2343
2344    var wrap = doc.createElement("div");
2345    wrap.className = "wsu-intl-tel";
2346    input.parentNode.insertBefore(wrap, input);
2347    wrap.appendChild(input);
2348    input.classList.add("wsu-intl-tel__input");
2349
2350    var toggle = doc.createElement("button");
2351    toggle.type = "button";
2352    toggle.className = "wsu-intl-tel__toggle";
2353    toggle.setAttribute("aria-haspopup", "listbox");
2354    toggle.setAttribute("aria-expanded", "false");
2355    toggle.innerHTML =
2356      '<span class="wsu-intl-tel__flag" data-hook="flag"></span>' +
2357      '<span class="wsu-intl-tel__caret" aria-hidden="true"></span>';
2358    wrap.insertBefore(toggle, input);
2359
2360    var panel = doc.createElement("div");
2361    panel.className = "wsu-intl-tel__panel";
2362    panel.innerHTML =
2363      '<ul class="wsu-intl-tel__list" id="' + uid + '-list" role="listbox" ' +
2364      'tabindex="-1" aria-label="Select country calling code"></ul>';
2365    wrap.appendChild(panel);
2366
2367    var list = panel.querySelector(".wsu-intl-tel__list");
2368    var flagHook = toggle.querySelector('[data-hook="flag"]');
2369    var optionEls = [];
2370
2371    COUNTRIES.forEach(function (country) {
2372      var el = buildOption(country, country.c === state.country.c,
2373        uid + "-opt-" + optionEls.length);
2374      list.appendChild(el);
2375      optionEls.push(el);
2376    });
2377
2378    function renderToggle() {
2379      flagHook.textContent = flagFor(state.country.c);
2380      var caption = state.country.n + " (+" + state.country.d + ")";
2381      toggle.title = caption;
2382      toggle.setAttribute("aria-label", "Country calling code: " + caption);
2383      optionEls.forEach(function (el) {
2384        el.setAttribute("aria-selected", el.dataset.iso === state.country.c ? "true" : "false");
2385      });
2386      syncDataAttrs();
2387    }
2388
2389    /* Expose the selected calling code as data-country-code on input + wrapper. */
2390    function syncDataAttrs() {
2391      var code = "+" + state.country.d;
2392      input.setAttribute("data-country-code", code);
2393      field.setAttribute("data-country-code", code);
2394    }
2395
2396    /* The input only ever holds the national number (no "+code"). If a visitor
2397       types or pastes a "+.." value, adopt the country and strip it back. */
2398    function stripPrefixToNational() {
2399      var raw = (input.value || "").trim();
2400      if (raw.charAt(0) === "+") {
2401        var c = countryByDialDigits(digitsOnly(raw));
2402        var national = nationalPart(raw, c ? c.d : state.country.d);
2403        if (c && c.c !== state.country.c) {
2404          state.country = c;
2405          renderToggle();
2406          syncResidence(field, c);
2407        }
2408        if (input.value !== national) {
2409          input.value = national;
2410          input.dispatchEvent(new Event("input", { bubbles: true }));
2411          input.dispatchEvent(new Event("change", { bubbles: true }));
2412        }
2413      }
2414      syncDataAttrs();
2415    }
2416
2417    function setCountry(country) {
2418      state.country = country;
2419      renderToggle();
2420      syncResidence(field, country);
2421    }
2422
2423    renderToggle();
2424    (function initFromValue() {
2425      var raw = (input.value || "").trim();
2426      if (raw.charAt(0) === "+") {
2427        var c = countryByDialDigits(digitsOnly(raw));
2428        if (c) {
2429          state.country = c;
2430        }
2431        input.value = nationalPart(raw, c ? c.d : state.country.d);
2432      }
2433      renderToggle();
2434      syncResidence(field, state.country);
2435    })();
2436
2437    input.addEventListener("focus", stripPrefixToNational);
2438    input.addEventListener("input", stripPrefixToNational);
2439    input.addEventListener("blur", stripPrefixToNational);
2440
2441    field._wsuIntlTelSync = syncDataAttrs;
2442
2443    /* ---- panel: no search box, matches intl-tel-input ---- */
2444
2445    var isOpen = false;
2446    var activeOption = null;
2447    var typeBuffer = "";
2448    var typeTimer = null;
2449
2450    function setActive(el, focusIt) {
2451      if (activeOption) {
2452        activeOption.classList.remove("is-active");
2453      }
2454      activeOption = el || null;
2455      if (!activeOption) {
2456        list.removeAttribute("aria-activedescendant");
2457        return;
2458      }
2459      activeOption.classList.add("is-active");
2460      activeOption.scrollIntoView({ block: "nearest" });
2461      list.setAttribute("aria-activedescendant", activeOption.id);
2462      if (focusIt !== false) {
2463        activeOption.focus();
2464      }
2465    }
2466
2467    function currentOption() {
2468      for (var i = 0; i < optionEls.length; i++) {
2469        if (optionEls[i].dataset.iso === state.country.c) {
2470          return optionEls[i];
2471        }
2472      }
2473      return optionEls[0];
2474    }
2475
2476    function openPanel() {
2477      if (isOpen) {
2478        return;
2479      }
2480      isOpen = true;
2481      wrap.classList.add("is-open");
2482      toggle.setAttribute("aria-expanded", "true");
2483      setActive(currentOption());
2484      doc.addEventListener("click", onDocClick, true);
2485    }
2486
2487    function closePanel(focusToggle) {
2488      if (!isOpen) {
2489        return;
2490      }
2491      isOpen = false;
2492      wrap.classList.remove("is-open");
2493      toggle.setAttribute("aria-expanded", "false");
2494      if (activeOption) {
2495        activeOption.classList.remove("is-active");
2496        activeOption = null;
2497      }
2498      list.removeAttribute("aria-activedescendant");
2499      doc.removeEventListener("click", onDocClick, true);
2500      if (focusToggle) {
2501        toggle.focus();
2502      }
2503    }
2504
2505    function onDocClick(e) {
2506      if (!wrap.contains(e.target)) {
2507        closePanel();
2508      }
2509    }
2510
2511    function choose(el) {
2512      if (!el || !el.dataset || !byIso[el.dataset.iso]) {
2513        return;
2514      }
2515      setCountry(byIso[el.dataset.iso]);
2516      closePanel();
2517      input.focus();
2518      try {
2519        input.setSelectionRange(input.value.length, input.value.length);
2520      } catch (e) { /* noop */ }
2521    }
2522
2523    function move(delta) {
2524      if (!optionEls.length) {
2525        return;
2526      }
2527      var idx = optionEls.indexOf(activeOption);
2528      idx = idx === -1 ? 0 : idx + delta;
2529      setActive(optionEls[Math.max(0, Math.min(optionEls.length - 1, idx))]);
2530    }
2531
2532    function typeahead(ch) {
2533      if (typeTimer) {
2534        global.clearTimeout(typeTimer);
2535      }
2536      typeBuffer += ch.toLowerCase();
2537      typeTimer = global.setTimeout(function () { typeBuffer = ""; }, 800);
2538      for (var i = 0; i < optionEls.length; i++) {
2539        if (optionEls[i].dataset.search.indexOf(typeBuffer) === 0) {
2540          setActive(optionEls[i]);
2541          return;
2542        }
2543      }
2544    }
2545
2546    toggle.addEventListener("click", function () {
2547      if (isOpen) {
2548        closePanel();
2549      } else {
2550        openPanel();
2551      }
2552    });
2553
2554    toggle.addEventListener("keydown", function (e) {
2555      if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
2556        e.preventDefault();
2557        openPanel();
2558      }
2559    });
2560
2561    list.addEventListener("click", function (e) {
2562      var el = e.target.closest(".wsu-intl-tel__option");
2563      if (el) {
2564        choose(el);
2565      }
2566    });
2567
2568    list.addEventListener("mousemove", function (e) {
2569      var el = e.target.closest(".wsu-intl-tel__option");
2570      if (el && el !== activeOption) {
2571        setActive(el, false);
2572      }
2573    });
2574
2575    list.addEventListener("keydown", function (e) {
2576      switch (e.key) {
2577        case "ArrowDown": e.preventDefault(); move(1); break;
2578        case "ArrowUp": e.preventDefault(); move(-1); break;
2579        case "Home": e.preventDefault(); setActive(optionEls[0]); break;
2580        case "End": e.preventDefault(); setActive(optionEls[optionEls.length - 1]); break;
2581        case "Enter":
2582        case " ": e.preventDefault(); choose(activeOption); break;
2583        case "Escape": e.preventDefault(); closePanel(true); break;
2584        case "Tab": closePanel(); break;
2585        default:
2586          if (e.key && e.key.length === 1 && /\S/.test(e.key)) {
2587            typeahead(e.key);
2588          }
2589      }
2590    });
2591
2592    field._wsuIntlTel = {
2593      resync: function () {
2594        syncResidence(field, state.country);
2595      }
2596    };
2597  }
2598
2599  /* --------------------------------------------------------------- driver -- */
2600
2601  function enhanceAll() {
2602    Array.prototype.forEach.call(doc.querySelectorAll(FIELD_SELECTOR), enhanceField);
2603  }
2604
2605  function resyncAll() {
2606    Array.prototype.forEach.call(doc.querySelectorAll(FIELD_SELECTOR), function (field) {
2607      if (field._wsuIntlTel) {
2608        field._wsuIntlTel.resync();
2609      }
2610    });
2611  }
2612
2613  /* Refresh the data-intl-tel-* attributes on every enhanced field. Runs on any
2614     submit so the receiving side reads a current value. */
2615  function syncAll() {
2616    Array.prototype.forEach.call(doc.querySelectorAll(FIELD_SELECTOR), function (field) {
2617      if (typeof field._wsuIntlTelSync === "function") {
2618        field._wsuIntlTelSync();
2619      }
2620    });
2621  }
2622
2623  var SUBMIT_SELECTOR =
2624    '.cmp-adaptiveform-button__widget,button[type="submit"],input[type="submit"]';
2625
2626  function onDocClickCapture(e) {
2627    var t = e.target;
2628    if (t && t.closest && t.closest(SUBMIT_SELECTOR)) {
2629      syncAll();
2630    }
2631  }
2632
2633  var scheduled = false;
2634  function scheduleScan() {
2635    if (scheduled) {
2636      return;
2637    }
2638    scheduled = true;
2639    global.setTimeout(function () {
2640      scheduled = false;
2641      enhanceAll();
2642      resyncAll();
2643    }, 150);
2644  }
2645
2646  function start() {
2647    enhanceAll();
2648    doc.addEventListener("click", onDocClickCapture, true);
2649    doc.addEventListener("submit", syncAll, true);
2650    if (typeof global.MutationObserver === "function") {
2651      new global.MutationObserver(scheduleScan).observe(doc.body, {
2652        childList: true,
2653        subtree: true
2654      });
2655    }
2656  }
2657
2658  if (doc.readyState === "loading") {
2659    doc.addEventListener("DOMContentLoaded", start);
2660  } else {
2661    start();
2662  }
2663
2664  function fieldInputByName(name) {
2665    return doc.querySelector('[data-country-code][name="' + name + '"]') ||
2666      doc.querySelector('[data-country-code][data-field-name="' + name + '"] input') ||
2667      doc.querySelector('[name="' + name + '"]');
2668  }
2669
2670  function fullNumberFor(name) {
2671    var el = fieldInputByName(name);
2672    if (!el) {
2673      return "";
2674    }
2675    var codeDigits = (el.getAttribute("data-country-code") || "").replace(/\D/g, "");
2676    var national = String(el.value || "").replace(/\D/g, "").replace(/^0/, "");
2677    return national ? codeDigits + national : "";
2678  }
2679
2680  global.wsuIntlTel = {
2681    refresh: enhanceAll,
2682    resyncResidence: resyncAll,
2683    sync: syncAll,
2684    getCountryCode: function (name) {
2685      var el = fieldInputByName(name);
2686      return el ? (el.getAttribute("data-country-code") || "") : "";
2687    },
2688    /* Dial-code digits + national digits (no "+", no leading trunk "0"),
2689       e.g. "61412345678" */
2690    getFullNumber: fullNumberFor
2691  };
2692})(typeof window !== "undefined" ? window : this);

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.