PageSourceSearch

https://spicewoodchess.org/wp-content/plugins/qwiz-online-quizzes-and-flashcards/featherlight.js?ver=4.03

js spicewoodchess.org collected 2026-09-24 15:11:30 UTC 24,461 bytes, 654 lines download raw bytes

1/**
2 * Featherlight - ultra slim jQuery lightbox
3 * Version 1.7.8 - http://noelboss.github.io/featherlight/
4 *
5 * Copyright 2017, Noël Raoul Bossart (http://www.noelboss.com)
6 * MIT Licensed.
7**/
8(function($) {
9	"use strict";
10
11	if('undefined' === typeof $) {
12		if('console' in window){ window.console.info('Too much lightness, Featherlight needs jQuery.'); }
13		return;
14	}
15
16	/* Featherlight is exported as $.featherlight.
17	   It is a function used to open a featherlight lightbox.
18
19	   [tech]
20	   Featherlight uses prototype inheritance.
21	   Each opened lightbox will have a corresponding object.
22	   That object may have some attributes that override the
23	   prototype's.
24	   Extensions created with Featherlight.extend will have their
25	   own prototype that inherits from Featherlight's prototype,
26	   thus attributes can be overriden either at the object level,
27	   or at the extension level.
28	   To create callbacks that chain themselves instead of overriding,
29	   use chainCallbacks.
30	   For those familiar with CoffeeScript, this correspond to
31	   Featherlight being a class and the Gallery being a class
32	   extending Featherlight.
33	   The chainCallbacks is used since we don't have access to
34	   CoffeeScript's `super`.
35	*/
36
37	function Featherlight($content, config) {
38		if(this instanceof Featherlight) {  /* called with new */
39			this.id = Featherlight.id++;
40			this.setup($content, config);
41			this.chainCallbacks(Featherlight._callbackChain);
42		} else {
43			var fl = new Featherlight($content, config);
44			fl.open();
45			return fl;
46		}
47	}
48
49	var opened = [],
50		pruneOpened = function(remove) {
51			opened = $.grep(opened, function(fl) {
52				return fl !== remove && fl.$instance.closest('body').length > 0;
53			} );
54			return opened;
55		};
56
57	// Removes keys of `set` from `obj` and returns the removed key/values.
58	function slice(obj, set) {
59		var r = {};
60		for (var key in obj) {
61			if (key in set) {
62				r[key] = obj[key];
63				delete obj[key];
64			}
65		}
66		return r;
67	}
68
69	// NOTE: List of available [iframe attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe).
70	var iFrameAttributeSet = {
71		allowfullscreen: 1, frameborder: 1, height: 1, longdesc: 1, marginheight: 1, marginwidth: 1,
72		name: 1, referrerpolicy: 1, scrolling: 1, sandbox: 1, src: 1, srcdoc: 1, width: 1
73	};
74
75	// Converts camelCased attributes to dasherized versions for given prefix:
76	//   parseAttrs({hello: 1, hellFrozeOver: 2}, 'hell') => {froze-over: 2}
77	function parseAttrs(obj, prefix) {
78		var attrs = {},
79			regex = new RegExp('^' + prefix + '([A-Z])(.*)');
80		for (var key in obj) {
81			var match = key.match(regex);
82			if (match) {
83				var dasherized = (match[1] + match[2].replace(/([A-Z])/g, '-$1')).toLowerCase();
84				attrs[dasherized] = obj[key];
85			}
86		}
87		return attrs;
88	}
89
90	/* document wide key handler */
91	var eventMap = { keyup: 'onKeyUp', resize: 'onResize' };
92
93	var globalEventHandler = function(event) {
94		$.each(Featherlight.opened().reverse(), function() {
95			if (!event.isDefaultPrevented()) {
96				if (false === this[eventMap[event.type]](event)) {
97					event.preventDefault(); event.stopPropagation(); return false;
98			  }
99			}
100		});
101	};
102
103	var toggleGlobalEvents = function(set) {
104			if(set !== Featherlight._globalHandlerInstalled) {
105				Featherlight._globalHandlerInstalled = set;
106				var events = $.map(eventMap, function(_, name) { return name+'.'+Featherlight.prototype.namespace; } ).join(' ');
107				$(window)[set ? 'on' : 'off'](events, globalEventHandler);
108			}
109		};
110
111        var globalNoClose_b = true;
112        var globalOpenTimeout;
113	Featherlight.prototype = {
114		constructor: Featherlight,
115		/*** defaults ***/
116		/* extend featherlight with defaults and methods */
117		namespace:      'featherlight',        /* Name of the events and css class prefix */
118		targetAttr:     'data-featherlight',   /* Attribute of the triggered element that contains the selector to the lightbox content */
119		variant:        null,                  /* Class that will be added to change look of the lightbox */
120		resetCss:       false,                 /* Reset all css */
121		background:     null,                  /* Custom DOM for the background, wrapper and the closebutton */
122		openTrigger:    'click',               /* Event that triggers the lightbox */
123		closeTrigger:   'click',               /* Event that triggers the closing of the lightbox */
124		filter:         null,                  /* Selector to filter events. Think $(...).on('click', filter, eventHandler) */
125		root:           'body',                /* Where to append featherlights */
126		openSpeed:      250,                   /* Duration of opening animation */
127		closeSpeed:     250,                   /* Duration of closing animation */
128		closeOnClick:   'background',          /* Close lightbox on click ('background', 'anywhere' or false) */
129		closeOnEsc:     true,                  /* Close lightbox when pressing esc */
130		closeIcon:      '✕',            /* Close icon */
131		loading:        '',                    /* Content to show while initial content is loading */
132		persist:        false,                 /* If set, the content will persist and will be shown again when opened again. 'shared' is a special value when binding multiple elements for them to share the same content */
133		otherClose:     null,                  /* Selector for alternate close buttons (e.g. "a.close") */
134		beforeOpen:     $.noop,                /* Called before open. can return false to prevent opening of lightbox. Gets event as parameter, this contains all data */
135		beforeContent:  $.noop,                /* Called when content is loaded. Gets event as parameter, this contains all data */
136		beforeClose:    $.noop,                /* Called before close. can return false to prevent opening of lightbox. Gets event as parameter, this contains all data */
137		afterOpen:      $.noop,                /* Called after open. Gets event as parameter, this contains all data */
138		afterContent:   $.noop,                /* Called after content is ready and has been set. Gets event as parameter, this contains all data */
139		afterClose:     $.noop,                /* Called after close. Gets event as parameter, this contains all data */
140		onKeyUp:        $.noop,                /* Called on key up for the frontmost featherlight */
141		onResize:       $.noop,                /* Called after new content and when a window is resized */
142		type:           null,                  /* Specify type of lightbox. If unset, it will check for the targetAttrs value. */
143		contentFilters: ['jquery', 'image', 'html', 'ajax', 'iframe', 'text'], /* List of content filters to use to determine the content */
144
145		/*** methods ***/
146		/* setup iterates over a single instance of featherlight and prepares the background and binds the events */
147		setup: function(target, config){
148			/* all arguments are optional */
149			if (typeof target === 'object' && target instanceof $ === false && !config) {
150				config = target;
151				target = undefined;
152			}
153
154			var self = $.extend(this, config, {target: target}),
155				css = !self.resetCss ? self.namespace : self.namespace+'-reset', /* by adding -reset to the classname, we reset all the default css */
156				$background = $(self.background || [
157					'<div class="'+css+'-loading '+css+'">',
158						'<div class="'+css+'-content">',
159							'<button class="'+css+'-close-icon '+ self.namespace + '-close" aria-label="Close">',
160								self.closeIcon,
161							'</button>',
162							'<div class="'+self.namespace+'-inner">' + self.loading + '</div>',
163						'</div>',
164					'</div>'].join('')),
165				closeButtonSelector = '.'+self.namespace+'-close' + (self.otherClose ? ',' + self.otherClose : '');
166
167			self.$instance = $background.clone().addClass(self.variant); /* clone DOM for the background, wrapper and the close button */
168
169			/* close when click on background/anywhere/null or closebox */
170			self.$instance.on(self.closeTrigger+'.'+self.namespace, function(event) {
171				var $target = $(event.target);
172				if( ('background' === self.closeOnClick  && $target.is('.'+self.namespace))
173					|| 'anywhere' === self.closeOnClick
174					|| $target.closest(closeButtonSelector).length ){
175
176                                        // DK: we're going to close on
177                                        // mousemove, but not within a while
178                                        // of open.
179                                        if (! globalNoClose_b) {
180                                           self.close(event);
181                                           event.preventDefault();
182                                        }
183				}
184			});
185
186			return this;
187		},
188
189		/* this method prepares the content and converts it into a jQuery object or a promise */
190		getContent: function(){
191			if(this.persist !== false && this.$content) {
192				return this.$content;
193			}
194			var self = this,
195				filters = this.constructor.contentFilters,
196				readTargetAttr = function(name){ return self.$currentTarget && self.$currentTarget.attr(name); },
197				targetValue = readTargetAttr(self.targetAttr),
198				data = self.target || targetValue || '';
199
200			/* Find which filter applies */
201			var filter = filters[self.type]; /* check explicit type like {type: 'image'} */
202
203			/* check explicit type like data-featherlight="image" */
204			if(!filter && data in filters) {
205				filter = filters[data];
206				data = self.target && targetValue;
207			}
208			data = data || readTargetAttr('href') || '';
209
210			/* check explicity type & content like {image: 'photo.jpg'} */
211			if(!filter) {
212				for(var filterName in filters) {
213					if(self[filterName]) {
214						filter = filters[filterName];
215						data = self[filterName];
216					}
217				}
218			}
219
220			/* otherwise it's implicit, run checks */
221			if(!filter) {
222				var target = data;
223				data = null;
224				$.each(self.contentFilters, function() {
225					filter = filters[this];
226					if(filter.test)  {
227						data = filter.test(target);
228					}
229					if(!data && filter.regex && target.match && target.match(filter.regex)) {
230						data = target;
231					}
232					return !data;
233				});
234				if(!data) {
235					if('console' in window){ window.console.error('Featherlight: no content filter found ' + (target ? ' for "' + target + '"' : ' (no target specified)')); }
236					return false;
237				}
238			}
239			/* Process it */
240			return filter.process.call(self, data);
241		},
242
243		/* sets the content of $instance to $content */
244		setContent: function($content){
245			var self = this;
246			/* we need a special class for the iframe */
247			if($content.is('iframe')) {
248				self.$instance.addClass(self.namespace+'-iframe');
249			}
250
251			self.$instance.removeClass(self.namespace+'-loading');
252
253			/* replace content by appending to existing one before it is removed
254			   this insures that featherlight-inner remain at the same relative
255				 position to any other items added to featherlight-content */
256			self.$instance.find('.'+self.namespace+'-inner')
257				.not($content)                /* excluded new content, important if persisted */
258				.slice(1).remove().end()      /* In the unexpected event where there are many inner elements, remove all but the first one */
259				.replaceWith($.contains(self.$instance[0], $content[0]) ? '' : $content);
260
261			self.$content = $content.addClass(self.namespace+'-inner');
262
263			return self;
264		},
265
266		/* opens the lightbox. "this" contains $instance with the lightbox, and with the config.
267			Returns a promise that is resolved after is successfully opened. */
268		open: function(event){
269			var self = this;
270			self.$instance.hide().appendTo(self.root);
271			if((!event || !event.isDefaultPrevented())
272				&& self.beforeOpen(event) !== false) {
273
274				if(event){
275					event.preventDefault();
276				}
277				var $content = self.getContent();
278
279				if($content) {
280					opened.push(self);
281
282					toggleGlobalEvents(true);
283
284					self.$instance.fadeIn(self.openSpeed);
285					self.beforeContent(event);
286
287					/* Set content and show */
288					return $.when($content)
289						.always(function($content){
290							self.setContent($content);
291							self.afterContent(event);
292						})
293						.then(self.$instance.promise())
294						/* Call afterOpen after fadeIn is done */
295						.done(function(){ self.afterOpen(event); });
296				}
297			}
298			self.$instance.detach();
299			return $.Deferred().reject().promise();
300		},
301
302		/* closes the lightbox. "this" contains $instance with the lightbox, and with the config
303			returns a promise, resolved after the lightbox is successfully closed. */
304		close: function(event){
305			var self = this,
306				deferred = $.Deferred();
307
308			if(self.beforeClose(event) === false) {
309				deferred.reject();
310			} else {
311
312				if (0 === pruneOpened(self).length) {
313					toggleGlobalEvents(false);
314				}
315
316				self.$instance.fadeOut(self.closeSpeed,function(){
317					self.$instance.detach();
318					self.afterClose(event);
319					deferred.resolve();
320				});
321			}
322			return deferred.promise();
323		},
324
325		/* resizes the content so it fits in visible area and keeps the same aspect ratio.
326				Does nothing if either the width or the height is not specified.
327				Called automatically on window resize.
328				Override if you want different behavior. */
329		resize: function(w, h) {
330			if (w && h) {
331				/* Reset apparent image size first so container grows */
332				this.$content.css('width', '').css('height', '');
333				/* Calculate the worst ratio so that dimensions fit */
334				 /* Note: -1 to avoid rounding errors */
335				var ratio = Math.max(
336					w  / (this.$content.parent().width()-1),
337					h / (this.$content.parent().height()-1));
338				/* Resize content */
339				if (ratio > 1) {
340					ratio = h / Math.floor(h / ratio); /* Round ratio down so height calc works */
341					this.$content.css('width', '' + w / ratio + 'px').css('height', '' + h / ratio + 'px');
342				}
343			}
344		},
345
346		/* Utility function to chain callbacks
347		   [Warning: guru-level]
348		   Used be extensions that want to let users specify callbacks but
349		   also need themselves to use the callbacks.
350		   The argument 'chain' has callback names as keys and function(super, event)
351		   as values. That function is meant to call `super` at some point.
352		*/
353		chainCallbacks: function(chain) {
354			for (var name in chain) {
355				this[name] = $.proxy(chain[name], this, $.proxy(this[name], this));
356			}
357		}
358	};
359
360	$.extend(Featherlight, {
361		id: 0,                                    /* Used to id single featherlight instances */
362		autoBind:       '[data-featherlight]',    /* Will automatically bind elements matching this selector. Clear or set before onReady */
363		defaults:       Featherlight.prototype,   /* You can access and override all defaults using $.featherlight.defaults, which is just a synonym for $.featherlight.prototype */
364		/* Contains the logic to determine content */
365		contentFilters: {
366			jquery: {
367				regex: /^[#.]\w/,         /* Anything that starts with a class name or identifiers */
368				test: function(elem)    { return elem instanceof $ && elem; },
369				process: function(elem) { return this.persist !== false ? $(elem) : $(elem).clone(true); }
370			},
371			image: {
372				regex: /\.(png|jpg|jpeg|gif|tiff|bmp|svg)(\?\S*)?$/i,
373				process: function(url)  {
374					var self = this,
375						deferred = $.Deferred(),
376						img = new Image(),
377						$img = $('<img src="'+url+'" alt="Could not load full-size image" class="'+self.namespace+'-image" />');
378					img.onload  = function() {
379						/* Store naturalWidth & height for IE8 */
380						$img.naturalWidth = img.width; $img.naturalHeight = img.height;
381						deferred.resolve( $img );
382					};
383					img.onerror = function() { deferred.reject($img); };
384					img.src = url;
385					return deferred.promise();
386				}
387			},
388			html: {
389				regex: /^\s*<[\w!][^<]*>/, /* Anything that starts with some kind of valid tag */
390				process: function(html) { return $(html); }
391			},
392			ajax: {
393				regex: /./,            /* At this point, any content is assumed to be an URL */
394				process: function(url)  {
395					var self = this,
396						deferred = $.Deferred();
397					/* we are using load so one can specify a target with: url.html #targetelement */
398					var $container = $('<div></div>').load(url, function(response, status){
399						if ( status !== "error" ) {
400							deferred.resolve($container.contents());
401						}
402						deferred.fail();
403					});
404					return deferred.promise();
405				}
406			},
407			iframe: {
408				process: function(url) {
409					var deferred = new $.Deferred();
410					var $content = $('<iframe/>');
411					var css = parseAttrs(this, 'iframe');
412					var attrs = slice(css, iFrameAttributeSet);
413					$content.hide()
414						.attr('src', url)
415						.attr(attrs)
416						.css(css)
417						.on('load', function() { deferred.resolve($content.show()); })
418						// We can't move an <iframe> and avoid reloading it,
419						// so let's put it in place ourselves right now:
420						.appendTo(this.$instance.find('.' + this.namespace + '-content'));
421					return deferred.promise();
422				}
423			},
424			text: {
425				process: function(text) { return $('<div>', {text: text}); }
426			}
427		},
428
429		functionAttributes: ['beforeOpen', 'afterOpen', 'beforeContent', 'afterContent', 'beforeClose', 'afterClose'],
430
431		/*** class methods ***/
432		/* read element's attributes starting with data-featherlight- */
433		readElementConfig: function(element, namespace) {
434			var Klass = this,
435				regexp = new RegExp('^data-' + namespace + '-(.*)'),
436				config = {};
437			if (element && element.attributes) {
438				$.each(element.attributes, function(){
439					var match = this.name.match(regexp);
440					if (match) {
441						var val = this.value,
442							name = $.camelCase(match[1]);
443						if ($.inArray(name, Klass.functionAttributes) >= 0) {  /* jshint -W054 */
444							val = new Function(val);                           /* jshint +W054 */
445						} else {
446							try { val = JSON.parse(val); }
447							catch(e) {}
448						}
449						config[name] = val;
450					}
451				});
452			}
453			return config;
454		},
455
456		/* Used to create a Featherlight extension
457		   [Warning: guru-level]
458		   Creates the extension's prototype that in turn
459		   inherits Featherlight's prototype.
460		   Could be used to extend an extension too...
461		   This is pretty high level wizardy, it comes pretty much straight
462		   from CoffeeScript and won't teach you anything about Featherlight
463		   as it's not really specific to this library.
464		   My suggestion: move along and keep your sanity.
465		*/
466		extend: function(child, defaults) {
467			/* Setup class hierarchy, adapted from CoffeeScript */
468			var Ctor = function(){ this.constructor = child; };
469			Ctor.prototype = this.prototype;
470			child.prototype = new Ctor();
471			child.__super__ = this.prototype;
472			/* Copy class methods & attributes */
473			$.extend(child, this, defaults);
474			child.defaults = child.prototype;
475			return child;
476		},
477
478		attach: function($source, $content, config) {
479			var Klass = this;
480			if (typeof $content === 'object' && $content instanceof $ === false && !config) {
481				config = $content;
482				$content = undefined;
483			}
484			/* make a copy */
485			config = $.extend({}, config);
486
487			/* Only for openTrigger and namespace... */
488			var namespace = config.namespace || Klass.defaults.namespace,
489				tempConfig = $.extend({}, Klass.defaults, Klass.readElementConfig($source[0], namespace), config),
490				sharedPersist;
491			var handler = function(event) {
492				var $target = $(event.currentTarget);
493				/* ... since we might as well compute the config on the actual target */
494				var elemConfig = $.extend(
495					{$source: $source, $currentTarget: $target},
496					Klass.readElementConfig($source[0], tempConfig.namespace),
497					Klass.readElementConfig(event.currentTarget, tempConfig.namespace),
498					config);
499				var fl = sharedPersist || $target.data('featherlight-persisted') || new Klass($content, elemConfig);
500				if(fl.persist === 'shared') {
501					sharedPersist = fl;
502				} else if(fl.persist !== false) {
503					$target.data('featherlight-persisted', fl);
504				}
505				if (elemConfig.$currentTarget.blur) {
506					elemConfig.$currentTarget.blur(); // Otherwise 'enter' key might trigger the dialog again
507				}
508
509                                // DK: Don't do open just yet, in case immediate
510                                // mouseleave.
511                                globalOpenTimeout = setTimeout (function () {
512                                                       fl.open(event);
513                                                    }, 250);
514
515                                // DK: set flag and start timer - no close for
516                                // a while.
517                                globalNoClose_b = true;
518                                setTimeout (function () {
519                                               globalNoClose_b = false;
520                                            }, 500);
521			};
522
523			$source.on(tempConfig.openTrigger+'.'+tempConfig.namespace, tempConfig.filter, handler);
524
525                        // DK: if immediate mouseleave (before open timeOut
526                        // finishes), cancel.
527                        $source.on('mouseleave', function () {
528                                                    clearTimeout (globalOpenTimeout);
529                                                 });
530
531			return handler;
532		},
533
534		current: function() {
535			var all = this.opened();
536			return all[all.length - 1] || null;
537		},
538
539		opened: function() {
540			var klass = this;
541			pruneOpened();
542			return $.grep(opened, function(fl) { return fl instanceof klass; } );
543		},
544
545		close: function(event) {
546			var cur = this.current();
547			if(cur) { return cur.close(event); }
548		},
549
550		/* Does the auto binding on startup.
551		   Meant only to be used by Featherlight and its extensions
552		*/
553		_onReady: function() {
554			var Klass = this;
555			if(Klass.autoBind){
556				/* Bind existing elements */
557				$(Klass.autoBind).each(function(){
558					Klass.attach($(this));
559				});
560				/* If a click propagates to the document level, then we have an item that was added later on */
561				$(document).on('click', Klass.autoBind, function(evt) {
562					if (evt.isDefaultPrevented()) {
563						return;
564					}
565					/* Bind featherlight */
566					var handler = Klass.attach($(evt.currentTarget));
567					/* Dispatch event directly */
568					handler(evt);
569				});
570			}
571		},
572
573		/* Featherlight uses the onKeyUp callback to intercept the escape key.
574		   Private to Featherlight.
575		*/
576		_callbackChain: {
577			onKeyUp: function(_super, event){
578				if(27 === event.keyCode) {
579					if (this.closeOnEsc) {
580						$.featherlight.close(event);
581					}
582					return false;
583				} else {
584					return _super(event);
585				}
586			},
587
588			beforeOpen: function(_super, event) {
589				// Used to disable scrolling
590				$(document.documentElement).addClass('with-featherlight');
591
592				// Remember focus:
593				this._previouslyActive = document.activeElement;
594
595				// Disable tabbing:
596				// See http://stackoverflow.com/questions/1599660/which-html-elements-can-receive-focus
597				this._$previouslyTabbable = $("a, input, select, textarea, iframe, button, iframe, [contentEditable=true]")
598					.not('[tabindex]')
599					.not(this.$instance.find('button'));
600
601				this._$previouslyWithTabIndex = $('[tabindex]').not('[tabindex="-1"]');
602				this._previousWithTabIndices = this._$previouslyWithTabIndex.map(function(_i, elem) {
603					return $(elem).attr('tabindex');
604				});
605
606				this._$previouslyWithTabIndex.add(this._$previouslyTabbable).attr('tabindex', -1);
607
608				if (document.activeElement.blur) {
609					document.activeElement.blur();
610				}
611				return _super(event);
612			},
613
614			afterClose: function(_super, event) {
615				var r = _super(event);
616				// Restore focus
617				var self = this;
618				this._$previouslyTabbable.removeAttr('tabindex');
619				this._$previouslyWithTabIndex.each(function(i, elem) {
620					$(elem).attr('tabindex', self._previousWithTabIndices[i]);
621				});
622				this._previouslyActive.focus();
623				// Restore scroll
624				if(Featherlight.opened().length === 0) {
625					$(document.documentElement).removeClass('with-featherlight');
626				}
627				return r;
628			},
629
630			onResize: function(_super, event){
631				this.resize(this.$content.naturalWidth, this.$content.naturalHeight);
632				return _super(event);
633			},
634
635			afterContent: function(_super, event){
636				var r = _super(event);
637				this.$instance.find('[autofocus]:not([disabled])').focus();
638				this.onResize(event);
639				return r;
640			}
641		}
642	});
643
644	$.featherlight = Featherlight;
645
646	/* bind jQuery elements to trigger featherlight */
647	$.fn.featherlight = function($content, config) {
648		Featherlight.attach(this, $content, config);
649		return this;
650	};
651
652	/* bind featherlight on ready if config autoBind is set */
653	$(document).ready(function(){ Featherlight._onReady(); });
654}(jQuery));

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.