PageSourceSearch

https://franklincountyhometeam.com/wp-content/plugins/mlsimport/pu…/mlsimport-property-interest.js?ver=7.1.2

js franklincountyhometeam.com collected 2026-09-29 17:08:14 UTC 6,786 bytes, 198 lines download raw bytes

1/**
2 * The "I'm interested in" dropdown — progressive enhancement over the native
3 * <select name="mlsimport_interest"> in the property lead forms.
4 *
5 * A native select's option list is drawn by the operating system and cannot be
6 * styled, so it never matched the rest of the site however the closed box was
7 * themed. This replaces the closed box with a button and the list with ordinary
8 * markup, reusing the .mlsimport-location__list / __option panel the search
9 * form's Location autocomplete already uses — so the two dropdowns match by
10 * construction rather than by hand-tuned values.
11 *
12 * The native select STAYS in the DOM (hidden once enhanced) and remains the
13 * submitted control: every pick writes through to it. So the lead handler is
14 * untouched, and with JS off the form degrades to the plain select. Enhancement
15 * is keyed off the wrapper gaining .is-enhanced, which is also what hides the
16 * native control — CSS alone never hides a control the user might still need.
17 *
18 * No jQuery.
19 */
20( function () {
21	'use strict';
22
23	// Wrapper emitted by mlsimport_property_lead_interest_field().
24	var ROOT = '.mlsimport-interest';
25
26	/**
27	 * Build the button + listbox for one wrapper and wire its behaviour.
28	 * @param {Element} root The .mlsimport-interest wrapper.
29	 */
30	function enhance( root ) {
31		// Enhance once, and only when there is a native select to mirror.
32		if ( root.classList.contains( 'is-enhanced' ) ) {
33			return;
34		}
35		var select = root.querySelector( 'select' );
36		if ( ! select ) {
37			return;
38		}
39
40		// The trigger reports the current choice; the placeholder option's text is
41		// the resting label, so the control reads the same before and after JS.
42		var trigger = document.createElement( 'button' );
43		trigger.type = 'button';
44		trigger.className = 'mlsimport-interest__trigger';
45		trigger.setAttribute( 'aria-haspopup', 'listbox' );
46		trigger.setAttribute( 'aria-expanded', 'false' );
47
48		var label = document.createElement( 'span' );
49		label.className = 'mlsimport-interest__label';
50		trigger.appendChild( label );
51
52		// The panel reuses the Location autocomplete's classes verbatim.
53		var list = document.createElement( 'ul' );
54		list.className = 'mlsimport-location__list mlsimport-interest__list';
55		list.setAttribute( 'role', 'listbox' );
56		list.hidden = true;
57
58		// One option per <option>, carrying its index so selection is a plain lookup.
59		var options = [];
60		Array.prototype.forEach.call( select.options, function ( opt, i ) {
61			var li = document.createElement( 'li' );
62			li.className = 'mlsimport-location__option';
63			li.setAttribute( 'role', 'option' );
64			li.setAttribute( 'data-index', String( i ) );
65			li.textContent = opt.textContent;
66			list.appendChild( li );
67			options.push( li );
68		} );
69
70		/**
71		 * Mirror the select's current value onto the trigger and the listbox.
72		 */
73		function paint() {
74			var opt = select.options[ select.selectedIndex ];
75			label.textContent = opt ? opt.textContent : '';
76			// The placeholder option has an empty value — style it as unfilled.
77			root.classList.toggle( 'is-placeholder', ! select.value );
78			options.forEach( function ( li, i ) {
79				var on = i === select.selectedIndex;
80				li.classList.toggle( 'is-selected', on );
81				li.setAttribute( 'aria-selected', on ? 'true' : 'false' );
82			} );
83		}
84
85		/**
86		 * Open or close the panel.
87		 * @param {boolean} open Whether the panel should be open.
88		 */
89		function setOpen( open ) {
90			list.hidden = ! open;
91			root.classList.toggle( 'is-open', open );
92			trigger.setAttribute( 'aria-expanded', open ? 'true' : 'false' );
93			if ( open ) {
94				// Start the keyboard walk from the current choice.
95				highlight( select.selectedIndex );
96			}
97		}
98
99		// Index currently highlighted by the keyboard (-1 = none).
100		var active = -1;
101
102		/**
103		 * Move the keyboard highlight, clamped to the option range.
104		 * @param {number} i Target index.
105		 */
106		function highlight( i ) {
107			if ( active > -1 && options[ active ] ) {
108				options[ active ].classList.remove( 'is-active' );
109			}
110			active = Math.max( 0, Math.min( options.length - 1, i ) );
111			options[ active ].classList.add( 'is-active' );
112			// Keep the highlighted row inside the scrollable panel.
113			if ( options[ active ].scrollIntoView ) {
114				options[ active ].scrollIntoView( { block: 'nearest' } );
115			}
116		}
117
118		/**
119		 * Commit an option: write through to the native select, repaint, close.
120		 * @param {number} i Option index to select.
121		 */
122		function choose( i ) {
123			select.selectedIndex = i;
124			// Fire change so anything listening on the real control still hears it.
125			select.dispatchEvent( new Event( 'change', { bubbles: true } ) );
126			paint();
127			setOpen( false );
128			trigger.focus();
129		}
130
131		trigger.addEventListener( 'click', function () {
132			setOpen( list.hidden );
133		} );
134
135		// Pointer selection. Using mousedown would fire before the click that closes
136		// an already-open panel, so click is the one that behaves.
137		list.addEventListener( 'click', function ( e ) {
138			var li = e.target.closest ? e.target.closest( '.mlsimport-location__option' ) : null;
139			if ( li ) {
140				choose( parseInt( li.getAttribute( 'data-index' ), 10 ) || 0 );
141			}
142		} );
143
144		// Keyboard: the listbox conventions a native select already answers to.
145		trigger.addEventListener( 'keydown', function ( e ) {
146			if ( 'ArrowDown' === e.key || 'ArrowUp' === e.key ) {
147				e.preventDefault();
148				if ( list.hidden ) {
149					setOpen( true );
150					return;
151				}
152				highlight( active + ( 'ArrowDown' === e.key ? 1 : -1 ) );
153			} else if ( 'Enter' === e.key || ' ' === e.key ) {
154				// Enter on a closed control opens it; on an open one it commits.
155				e.preventDefault();
156				if ( list.hidden ) {
157					setOpen( true );
158				} else {
159					choose( active );
160				}
161			} else if ( 'Escape' === e.key && ! list.hidden ) {
162				e.preventDefault();
163				setOpen( false );
164			}
165		} );
166
167		// Any click outside closes the panel.
168		document.addEventListener( 'click', function ( e ) {
169			if ( ! root.contains( e.target ) ) {
170				setOpen( false );
171			}
172		} );
173
174		// Insert the widget, then flag the wrapper — the flag is what hides the
175		// native select, so it is set only once the replacement is really in place.
176		root.appendChild( trigger );
177		root.appendChild( list );
178		paint();
179		root.classList.add( 'is-enhanced' );
180	}
181
182	/**
183	 * Enhance every interest dropdown inside a scope.
184	 * @param {Element|Document} [scope] Root to search; defaults to document.
185	 */
186	function init( scope ) {
187		var node = scope && scope.querySelectorAll ? scope : document;
188		Array.prototype.forEach.call( node.querySelectorAll( ROOT ), enhance );
189	}
190
191	if ( 'loading' === document.readyState ) {
192		document.addEventListener( 'DOMContentLoaded', function () {
193			init( document );
194		} );
195	} else {
196		init( document );
197	}
198} )();

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.