PageSourceSearch

https://girlsleadership.org/wp-content/themes/gli/assets/js/styleselect/js/styleselect.js

js girlsleadership.org collected 2026-09-25 02:33:44 UTC 12,620 bytes, 362 lines download raw bytes

1// UMD module from From https://github.com/umdjs/umd/blob/master/returnExports.js
2// From 'if the module has no dependencies' example.
3(function (root, factory) {
4    if (typeof define === 'function' && define.amd) {
5        // AMD. Register as an anonymous module.
6        define([], factory);
7    } else if (typeof exports === 'object') {
8        // Node. Does not work with strict CommonJS, but
9        // only CommonJS-like environments that support module.exports,
10        // like Node.
11        module.exports = factory();
12    } else {
13        // Browser globals (root is window)
14        root.styleSelect  = factory();
15  }
16}(this, function () {
17// End of UMD module
18
19	// Quick aliases and polyfills if needed
20	var query = document.querySelector.bind(document);
21	var queryAll = document.querySelectorAll.bind(document);
22
23	var KEYCODES = {
24		SPACE: 32,
25		UP: 38,
26		DOWN: 40,
27		ENTER: 13
28	};
29
30	if ( ! NodeList.prototype.forEach ) {
31		NodeList.prototype.forEach = Array.prototype.forEach;
32	}
33	if ( ! HTMLCollection.prototype.forEach ) {
34		HTMLCollection.prototype.forEach = Array.prototype.forEach;
35	}
36	if ( ! Element.prototype.matches ) {
37		// See https://developer.mozilla.org/en-US/docs/Web/API/Element.matches
38		Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector
39	}
40
41	// IE 9-11 CustomEvent polyfill
42	// From https://developer.mozilla.org/en/docs/Web/API/CustomEvent
43	var CustomEvent = function( eventName, params ) {
44		params = params || { bubbles: false, cancelable: false, detail: undefined };
45		var event = document.createEvent( 'CustomEvent' );
46		event.initCustomEvent( eventName, params.bubbles, params.cancelable, params.detail );
47		return event;
48	};
49	CustomEvent.prototype = window.Event.prototype;
50	window.CustomEvent = CustomEvent;
51
52	// IE10 dataset polyfill
53	// From https://gist.githubusercontent.com/brettz9/4093766/raw/ba31a05e7ce21af67c6cafee9b3f439c86e95b01/html5-dataset.js
54	if (!document.documentElement.dataset &&
55			 // FF is empty while IE gives empty object
56			(!Object.getOwnPropertyDescriptor(Element.prototype, 'dataset')  ||
57			!Object.getOwnPropertyDescriptor(Element.prototype, 'dataset').get)
58		) {
59		var propDescriptor = {
60			enumerable: true,
61			get: function () {
62				'use strict';
63				var i,
64					that = this,
65					HTML5_DOMStringMap,
66					attrVal, attrName, propName,
67					attribute,
68					attributes = this.attributes,
69					attsLength = attributes.length,
70					toUpperCase = function (n0) {
71						return n0.charAt(1).toUpperCase();
72					},
73					getter = function () {
74						return this;
75					},
76					setter = function (attrName, value) {
77						return (typeof value !== 'undefined') ?
78							this.setAttribute(attrName, value) :
79							this.removeAttribute(attrName);
80					};
81				try { // Simulate DOMStringMap w/accessor support
82					// Test setting accessor on normal object
83					({}).__defineGetter__('test', function () {});
84					HTML5_DOMStringMap = {};
85				}
86				catch (e1) { // Use a DOM object for IE8
87					HTML5_DOMStringMap = document.createElement('div');
88				}
89				for (i = 0; i < attsLength; i++) {
90					attribute = attributes[i];
91					// Fix: This test really should allow any XML Name without
92					//         colons (and non-uppercase for XHTML)
93					if (attribute && attribute.name &&
94						(/^data-\w[\w\-]*$/).test(attribute.name)) {
95						attrVal = attribute.value;
96						attrName = attribute.name;
97						// Change to CamelCase
98						propName = attrName.substr(5).replace(/-./g, toUpperCase);
99						try {
100							Object.defineProperty(HTML5_DOMStringMap, propName, {
101								enumerable: this.enumerable,
102								get: getter.bind(attrVal || ''),
103								set: setter.bind(that, attrName)
104							});
105						}
106						catch (e2) { // if accessors are not working
107							HTML5_DOMStringMap[propName] = attrVal;
108						}
109					}
110				}
111				return HTML5_DOMStringMap;
112			}
113		};
114		try {
115			// FF enumerates over element's dataset, but not
116			//   Element.prototype.dataset; IE9 iterates over both
117			Object.defineProperty(Element.prototype, 'dataset', propDescriptor);
118		} catch (e) {
119			propDescriptor.enumerable = false; // IE8 does not allow setting to true
120			Object.defineProperty(Element.prototype, 'dataset', propDescriptor);
121		}
122	}
123
124	// Return true if any ancestor matches selector
125	// Borrowed from ancestorMatches() from agave.js (MIT)
126	var isAncestorOf = function(element, selector, includeSelf) {
127		var parent = element.parentNode;
128		if ( includeSelf && element.matches(selector) ) {
129			return true
130		}
131		// While parents are 'element' type nodes
132		// See https://developer.mozilla.org/en-US/docs/DOM/Node.nodeType
133		while ( parent && parent.nodeType && parent.nodeType === 1 ) {
134			if ( parent.matches(selector) ) {
135				return true
136			}
137			parent = parent.parentNode;
138		}
139		return false;
140	};
141
142
143	// Used to match select boxes to their style select partners
144	var makeUUID = function(){
145		return 'ss-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function (c) {
146			var r = Math.random() * 16 | 0, v = c == 'x' ? r : r & 0x3 | 0x8;
147			return v.toString(16);
148		})
149	};
150
151
152	// The 'styleSelect' main function
153	// selector:String - CSS selector for the select box to style
154	return function(selector) {
155
156		// Use native selects (which pop up large native UIs to go through the options ) on iOS/Android
157		if ( navigator.userAgent.match( /iPad|iPhone|Android/i ) ) {
158			return
159		}
160
161		var realSelect = typeof selector == 'object' ? selector : query(selector),
162			realOptions = realSelect.children,
163			selectedIndex = realSelect.selectedIndex,
164			uuid = makeUUID(),
165			styleSelectHTML = '<div class="style-select" aria-hidden="true" data-ss-uuid="' + uuid + '">';
166
167		// The index of the item that's being highlighted by the mouse or keyboard
168		var highlightedOptionIndex;
169		var highlightedOptionIndexMax = realOptions.length - 1;
170
171		realSelect.setAttribute('data-ss-uuid', uuid);
172		// Even though the element is display: none, a11y users should still see it.
173		// According to http://www.w3.org/TR/wai-aria/states_and_properties#aria-hidden
174		// some browsers may have bugs with this but future implementation may improve
175		realSelect.setAttribute('aria-hidden', "false");
176
177		// Build styled clones of all the real options
178		var selectedOptionHTML;
179		var optionsHTML = '<div class="ss-dropdown">';
180		realOptions.forEach(function(realOption, index){
181			var text = realOption.textContent,
182				value = realOption.getAttribute('value') || '',
183                cssClass = 'ss-option';
184
185			if (index === selectedIndex) {
186				// Mark first item as selected-option - this is where we store state for the styled select box
187				// aria-hidden=true so screen readers ignore the styles selext box in favor of the real one (which is visible by default)
188				selectedOptionHTML = '<div class="ss-selected-option" tabindex="0" data-value="' + value + '">
188' + text + '</div>'
189			}
190
191            if (realOption.disabled) {
192                cssClass += ' disabled';
193            }
194
195            // Continue building optionsHTML
196			optionsHTML += '<div class="' + cssClass + '" data-value="' + value + '">' + text + '</div>';
197		});
198		optionsHTML += '</div>';
199		styleSelectHTML += selectedOptionHTML += optionsHTML += '</div>';
200		// And add out styled select just after the real select
201		realSelect.insertAdjacentHTML('afterend', styleSelectHTML);
202
203		var styledSelect = query('.style-select[data-ss-uuid="'+uuid+'"]');
204		var styleSelectOptions = styledSelect.querySelectorAll('.ss-option');
205		var selectedOption = styledSelect.querySelector('.ss-selected-option');
206
207		var changeRealSelectBox = function(newValue, newLabel) {
208			// Close styledSelect
209			styledSelect.classList.remove('open');
210
211			// Update styled value
212			selectedOption.textContent = newLabel;
213			selectedOption.dataset.value = newValue;
214
215			// Update the 'tick' that shows the option with the current value
216			styleSelectOptions.forEach(function(styleSelectOption){
217				if ( styleSelectOption.dataset.value === newValue) {
218					styleSelectOption.classList.add('ticked')
219				} else {
220					styleSelectOption.classList.remove('ticked')
221				}
222			});
223
224			// Update real select box
225			realSelect.value = newValue;
226
227			// Send 'change' event to real select - to trigger any change event listeners
228			var changeEvent = new CustomEvent('change');
229			realSelect.dispatchEvent(changeEvent);
230		};
231
232		// Change real select box when a styled option is clicked
233		styleSelectOptions.forEach(function(unused, index){
234			var styleSelectOption = styleSelectOptions.item(index);
235
236            if (styleSelectOption.className.match(/\bdisabled\b/)) {
237                return;
238            }
239
240            styleSelectOption.addEventListener('click', function(ev) {
241				var target = ev.target,
242					styledSelectBox = target.parentNode.parentNode,
243					uuid = styledSelectBox.getAttribute('data-ss-uuid'),
244					newValue = target.getAttribute('data-value'),
245					newLabel = target.textContent;
246
247				changeRealSelectBox(newValue, newLabel)
248
249			});
250
251			// Tick and highlight the option that's currently in use
252			if ( styleSelectOption.dataset.value === realSelect.value ) {
253				highlightedOptionIndex = index;
254				styleSelectOption.classList.add('ticked');
255				styleSelectOption.classList.add('highlighted')
256			}
257
258			// Important: we can't use ':hover' as the keyboard and default value can also set the highlight
259			styleSelectOption.addEventListener('mouseover', function(ev){
260				styleSelectOption.parentNode.childNodes.forEach(function(sibling, index){
261					if ( sibling === ev.target ) {
262						sibling.classList.add('highlighted');
263						highlightedOptionIndex = index;
264					} else {
265						sibling.classList.remove('highlighted')
266					}
267				})
268			})
269		});
270
271
272
273		var closeAllStyleSelects = function(exception){
274			queryAll('.style-select').forEach(function(styleSelectEl) {
275				if ( styleSelectEl !== exception ) {
276					styleSelectEl.classList.remove('open');
277				}
278			});
279		};
280
281		var toggleStyledSelect = function(styledSelectBox){
282			if ( ! styledSelectBox.classList.contains('open') ) {
283				// If we're closed and about to open, close other style selects on the page
284				closeAllStyleSelects(styledSelectBox);
285			}
286			// Then toggle open/close
287			styledSelectBox.classList.toggle('open');
288		};
289
290		// When a styled select box is clicked
291		var styledSelectedOption = query('.style-select[data-ss-uuid="' + uuid + '"] .ss-selected-option');
292		styledSelectedOption.addEventListener('click', function(ev) {
293			ev.preventDefault();
294			ev.stopPropagation();
295			toggleStyledSelect(ev.target.parentNode);
296		});
297
298		// Keyboard handling
299		styledSelectedOption.addEventListener('keydown', function(ev) {
300			var styledSelectBox = ev.target.parentNode;
301
302			switch (ev.keyCode) {
303				case KEYCODES.SPACE:
304					// Space shows and hides styles select boxes
305					toggleStyledSelect(styledSelectBox);
306					break;
307				case KEYCODES.DOWN:
308				case KEYCODES.UP:
309					// Move the highlight up and down
310					if ( ! styledSelectBox.classList.contains('open') ) {
311						// If style select is not open, up/down should open it.
312						toggleStyledSelect(styledSelectBox);
313					} else {
314						// If style select is already open, these should change what the highlighted option is
315						if ( ev.keyCode === KEYCODES.UP ) {
316							// Up arrow moves earlier in list
317							if ( highlightedOptionIndex !== 0 ) {
318								highlightedOptionIndex = highlightedOptionIndex - 1
319							}
320						} else {
321							// Down arrow moves later in list
322							if ( highlightedOptionIndex < highlightedOptionIndexMax ) {
323								highlightedOptionIndex = highlightedOptionIndex + 1
324							}
325						}
326						styleSelectOptions.forEach(function(option, index){
327							if ( index === highlightedOptionIndex ) {
328								option.classList.add('highlighted')
329							} else {
330								option.classList.remove('highlighted')
331							}
332						})
333					}
334					ev.preventDefault();
335					ev.stopPropagation();
336					break;
337				// User has picked an item from the keyboard
338				case KEYCODES.ENTER:
339					var highlightedOption = styledSelectedOption.parentNode.querySelectorAll('.ss-option')[highlightedOptionIndex],
340						newValue = highlightedOption.dataset.value,
341						newLabel = highlightedOption.textContent;
342
343					changeRealSelectBox(newValue, newLabel);
344					ev.preventDefault();
345					ev.stopPropagation();
346					break;
347			}
348		});
349
350		// Clicking outside of the styled select box closes any open styled select boxes
351		query('body').addEventListener('click', function(ev){
352
353			if ( ! isAncestorOf(ev.target, '.style-select', true) ) {
354				closeAllStyleSelects();
355			}
356		})
357
358	};
359
360// Close UMD module
361}));
362

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.