PageSourceSearch

https://inovabs.com.br/shared/js/featherlight.js

js inovabs.com.br collected 2026-09-26 02:14:57 UTC 20,655 bytes, 557 lines download raw bytes

1/**
2 * Featherlight - ultra slim jQuery lightbox
3 * Version 1.4.0 - http://noelboss.github.io/featherlight/
4 *
5 * Copyright 2016, 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	// structure({iframeMinHeight: 44, foo: 0}, 'iframe')
58	//   #=> {min-height: 44}
59	var structure = function(obj, prefix) {
60		var result = {},
61			regex = new RegExp('^' + prefix + '([A-Z])(.*)');
62		for (var key in obj) {
63			var match = key.match(regex);
64			if (match) {
65				var dasherized = (match[1] + match[2].replace(/([A-Z])/g, '-$1')).toLowerCase();
66				result[dasherized] = obj[key];
67			}
68		}
69		return result;
70	};
71
72	/* document wide key handler */
73	var eventMap = { keyup: 'onKeyUp', resize: 'onResize' };
74
75	var globalEventHandler = function(event) {
76		$.each(Featherlight.opened().reverse(), function() {
77			if (!event.isDefaultPrevented()) {
78				if (false === this[eventMap[event.type]](event)) {
79					event.preventDefault(); event.stopPropagation(); return false;
80			  }
81			}
82		});
83	};
84
85	var toggleGlobalEvents = function(set) {
86			if(set !== Featherlight._globalHandlerInstalled) {
87				Featherlight._globalHandlerInstalled = set;
88				var events = $.map(eventMap, function(_, name) { return name+'.'+Featherlight.prototype.namespace; } ).join(' ');
89				$(window)[set ? 'on' : 'off'](events, globalEventHandler);
90			}
91		};
92
93	Featherlight.prototype = {
94		constructor: Featherlight,
95		/*** defaults ***/
96		/* extend featherlight with defaults and methods */
97		namespace:      'featherlight',        /* Name of the events and css class prefix */
98		targetAttr:     'data-featherlight',   /* Attribute of the triggered element that contains the selector to the lightbox content */
99		variant:        null,                  /* Class that will be added to change look of the lightbox */
100		resetCss:       false,                 /* Reset all css */
101		background:     null,                  /* Custom DOM for the background, wrapper and the closebutton */
102		openTrigger:    'click',               /* Event that triggers the lightbox */
103		closeTrigger:   'click',               /* Event that triggers the closing of the lightbox */
104		filter:         null,                  /* Selector to filter events. Think $(...).on('click', filter, eventHandler) */
105		root:           'body',                /* Where to append featherlights */
106		openSpeed:      250,                   /* Duration of opening animation */
107		closeSpeed:     250,                   /* Duration of closing animation */
108		closeOnClick:   'background',          /* Close lightbox on click ('background', 'anywhere' or false) */
109		closeOnEsc:     true,                  /* Close lightbox when pressing esc */
110		closeIcon:      '✕',            /* Close icon */
111		loading:        '',                    /* Content to show while initial content is loading */
112		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 */
113		otherClose:     null,                  /* Selector for alternate close buttons (e.g. "a.close") */
114		beforeOpen:     $.noop,                /* Called before open. can return false to prevent opening of lightbox. Gets event as parameter, this contains all data */
115		beforeContent:  $.noop,                /* Called when content is loaded. Gets event as parameter, this contains all data */
116		beforeClose:    $.noop,                /* Called before close. can return false to prevent opening of lightbox. Gets event as parameter, this contains all data */
117		afterOpen:      $.noop,                /* Called after open. Gets event as parameter, this contains all data */
118		afterContent:   $.noop,                /* Called after content is ready and has been set. Gets event as parameter, this contains all data */
119		afterClose:     $.noop,                /* Called after close. Gets event as parameter, this contains all data */
120		onKeyUp:        $.noop,                /* Called on key up for the frontmost featherlight */
121		onResize:       $.noop,                /* Called after new content and when a window is resized */
122		type:           null,                  /* Specify type of lightbox. If unset, it will check for the targetAttrs value. */
123		contentFilters: ['jquery', 'image', 'html', 'ajax', 'iframe', 'text'], /* List of content filters to use to determine the content */
124
125		/*** methods ***/
126		/* setup iterates over a single instance of featherlight and prepares the background and binds the events */
127		setup: function(target, config){
128			/* all arguments are optional */
129			if (typeof target === 'object' && target instanceof $ === false && !config) {
130				config = target;
131				target = undefined;
132			}
133
134			var self = $.extend(this, config, {target: target}),
135				css = !self.resetCss ? self.namespace : self.namespace+'-reset', /* by adding -reset to the classname, we reset all the default css */
136				$background = $(self.background || [
137					'<div class="'+css+'-loading '+css+'">',
138						'<div class="'+css+'-content">',
139							'<span class="'+css+'-close-icon '+ self.namespace + '-close">',
140								self.closeIcon,
141							'</span>',
142							'<div class="'+self.namespace+'-inner">' + self.loading + '</div>',
143						'</div>',
144					'</div>'].join('')),
145				closeButtonSelector = '.'+self.namespace+'-close' + (self.otherClose ? ',' + self.otherClose : '');
146
147			self.$instance = $background.clone().addClass(self.variant); /* clone DOM for the background, wrapper and the close button */
148
149			/* close when click on background/anywhere/null or closebox */
150			self.$instance.on(self.closeTrigger+'.'+self.namespace, function(event) {
151				var $target = $(event.target);
152				if( ('background' === self.closeOnClick  && $target.is('.'+self.namespace))
153					|| 'anywhere' === self.closeOnClick
154					|| $target.closest(closeButtonSelector).length ){
155					self.close(event);
156					event.preventDefault();
157				}
158			});
159
160			return this;
161		},
162
163		/* this method prepares the content and converts it into a jQuery object or a promise */
164		getContent: function(){
165			if(this.persist !== false && this.$content) {
166				return this.$content;
167			}
168			var self = this,
169				filters = this.constructor.contentFilters,
170				readTargetAttr = function(name){ return self.$currentTarget && self.$currentTarget.attr(name); },
171				targetValue = readTargetAttr(self.targetAttr),
172				data = self.target || targetValue || '';
173
174			/* Find which filter applies */
175			var filter = filters[self.type]; /* check explicit type like {type: 'image'} */
176
177			/* check explicit type like data-featherlight="image" */
178			if(!filter && data in filters) {
179				filter = filters[data];
180				data = self.target && targetValue;
181			}
182			data = data || readTargetAttr('href') || '';
183
184			/* check explicity type & content like {image: 'photo.jpg'} */
185			if(!filter) {
186				for(var filterName in filters) {
187					if(self[filterName]) {
188						filter = filters[filterName];
189						data = self[filterName];
190					}
191				}
192			}
193
194			/* otherwise it's implicit, run checks */
195			if(!filter) {
196				var target = data;
197				data = null;
198				$.each(self.contentFilters, function() {
199					filter = filters[this];
200					if(filter.test)  {
201						data = filter.test(target);
202					}
203					if(!data && filter.regex && target.match && target.match(filter.regex)) {
204						data = target;
205					}
206					return !data;
207				});
208				if(!data) {
209					if('console' in window){ window.console.error('Featherlight: no content filter found ' + (target ? ' for "' + target + '"' : ' (no target specified)')); }
210					return false;
211				}
212			}
213			/* Process it */
214			return filter.process.call(self, data);
215		},
216
217		/* sets the content of $instance to $content */
218		setContent: function($content){
219			var self = this;
220			/* we need a special class for the iframe */
221			if($content.is('iframe') || $('iframe', $content).length > 0){
222				self.$instance.addClass(self.namespace+'-iframe');
223			}
224
225			self.$instance.removeClass(self.namespace+'-loading');
226
227			/* replace content by appending to existing one before it is removed
228			   this insures that featherlight-inner remain at the same relative
229				 position to any other items added to featherlight-content */
230			self.$instance.find('.'+self.namespace+'-inner')
231				.not($content)                /* excluded new content, important if persisted */
232				.slice(1).remove().end()      /* In the unexpected event where there are many inner elements, remove all but the first one */
233				.replaceWith($.contains(self.$instance[0], $content[0]) ? '' : $content);
234
235			self.$content = $content.addClass(self.namespace+'-inner');
236
237			return self;
238		},
239
240		/* opens the lightbox. "this" contains $instance with the lightbox, and with the config.
241			Returns a promise that is resolved after is successfully opened. */
242		open: function(event){
243			var self = this;
244			self.$instance.hide().appendTo(self.root);
245			if((!event || !event.isDefaultPrevented())
246				&& self.beforeOpen(event) !== false) {
247
248				if(event){
249					event.preventDefault();
250				}
251				var $content = self.getContent();
252
253				if($content) {
254					opened.push(self);
255
256					toggleGlobalEvents(true);
257
258					self.$instance.fadeIn(self.openSpeed);
259					self.beforeContent(event);
260
261					/* Set content and show */
262					return $.when($content)
263						.always(function($content){
264							self.setContent($content);
265							self.afterContent(event);
266						})
267						.then(self.$instance.promise())
268						/* Call afterOpen after fadeIn is done */
269						.done(function(){ self.afterOpen(event); });
270				}
271			}
272			self.$instance.detach();
273			return $.Deferred().reject().promise();
274		},
275
276		/* closes the lightbox. "this" contains $instance with the lightbox, and with the config
277			returns a promise, resolved after the lightbox is successfully closed. */
278		close: function(event){
279			var self = this,
280				deferred = $.Deferred();
281
282			if(self.beforeClose(event) === false) {
283				deferred.reject();
284			} else {
285
286				if (0 === pruneOpened(self).length) {
287					toggleGlobalEvents(false);
288				}
289
290				self.$instance.fadeOut(self.closeSpeed,function(){
291					self.$instance.detach();
292					self.afterClose(event);
293					deferred.resolve();
294				});
295			}
296			return deferred.promise();
297		},
298
299		/* resizes the content so it fits in visible area and keeps the same aspect ratio.
300				Does nothing if either the width or the height is not specified.
301				Called automatically on window resize.
302				Override if you want different behavior. */
303		resize: function(w, h) {
304			if (w && h) {
305				/* Reset apparent image size first so container grows */
306				this.$content.css('width', '').css('height', '');
307				/* Calculate the worst ratio so that dimensions fit */
308				var ratio = Math.max(
309					w  / parseInt(this.$content.parent().css('width'),10),
310					h / parseInt(this.$content.parent().css('height'),10));
311				/* Resize content */
312				if (ratio > 1) {
313					this.$content.css('width', '' + w / ratio + 'px').css('height', '' + h / ratio + 'px');
314				}
315			}
316		},
317
318		/* Utility function to chain callbacks
319		   [Warning: guru-level]
320		   Used be extensions that want to let users specify callbacks but
321		   also need themselves to use the callbacks.
322		   The argument 'chain' has callback names as keys and function(super, event)
323		   as values. That function is meant to call `super` at some point.
324		*/
325		chainCallbacks: function(chain) {
326			for (var name in chain) {
327				this[name] = $.proxy(chain[name], this, $.proxy(this[name], this));
328			}
329		}
330	};
331
332	$.extend(Featherlight, {
333		id: 0,                                    /* Used to id single featherlight instances */
334		autoBind:       '[data-featherlight]',    /* Will automatically bind elements matching this selector. Clear or set before onReady */
335		defaults:       Featherlight.prototype,   /* You can access and override all defaults using $.featherlight.defaults, which is just a synonym for $.featherlight.prototype */
336		/* Contains the logic to determine content */
337		contentFilters: {
338			jquery: {
339				regex: /^[#.]\w/,         /* Anything that starts with a class name or identifiers */
340				test: function(elem)    { return elem instanceof $ && elem; },
341				process: function(elem) { return this.persist !== false ? $(elem) : $(elem).clone(true); }
342			},
343			image: {
344				regex: /\.(png|jpg|jpeg|gif|tiff|bmp|svg)(\?\S*)?$/i,
345				process: function(url)  {
346					var self = this,
347						deferred = $.Deferred(),
348						img = new Image(),
349						$img = $('<img src="'+url+'" alt="" class="'+self.namespace+'-image" />');
350					img.onload  = function() {
351						/* Store naturalWidth & height for IE8 */
352						$img.naturalWidth = img.width; $img.naturalHeight = img.height;
353						deferred.resolve( $img );
354					};
355					img.onerror = function() { deferred.reject($img); };
356					img.src = url;
357					return deferred.promise();
358				}
359			},
360			html: {
361				regex: /^\s*<[\w!][^<]*>/, /* Anything that starts with some kind of valid tag */
362				process: function(html) { return $(html); }
363			},
364			ajax: {
365				regex: /./,            /* At this point, any content is assumed to be an URL */
366				process: function(url)  {
367					var self = this,
368						deferred = $.Deferred();
369					/* we are using load so one can specify a target with: url.html #targetelement */
370					var $container = $('<div></div>').load(url, function(response, status){
371						if ( status !== "error" ) {
372							deferred.resolve($container.contents());
373						}
374						deferred.fail();
375					});
376					return deferred.promise();
377				}
378			},
379			iframe: {
380				process: function(url) {
381					var deferred = new $.Deferred();
382					var $content = $('<iframe/>')
383						.hide()
384						.attr('src', url)
385						.css(structure(this, 'iframe'))
386						.on('load', function() { deferred.resolve($content.show()); })
387						// We can't move an <iframe> and avoid reloading it,
388						// so let's put it in place ourselves right now:
389						.appendTo(this.$instance.find('.' + this.namespace + '-content'));
390					return deferred.promise();
391				}
392			},
393			text: {
394				process: function(text) { return $('<div>', {text: text}); }
395			}
396		},
397
398		functionAttributes: ['beforeOpen', 'afterOpen', 'beforeContent', 'afterContent', 'beforeClose', 'afterClose'],
399
400		/*** class methods ***/
401		/* read element's attributes starting with data-featherlight- */
402		readElementConfig: function(element, namespace) {
403			var Klass = this,
404				regexp = new RegExp('^data-' + namespace + '-(.*)'),
405				config = {};
406			if (element && element.attributes) {
407				$.each(element.attributes, function(){
408					var match = this.name.match(regexp);
409					if (match) {
410						var val = this.value,
411							name = $.camelCase(match[1]);
412						if ($.inArray(name, Klass.functionAttributes) >= 0) {  /* jshint -W054 */
413							val = new Function(val);                           /* jshint +W054 */
414						} else {
415							try { val = $.parseJSON(val); }
416							catch(e) {}
417						}
418						config[name] = val;
419					}
420				});
421			}
422			return config;
423		},
424
425		/* Used to create a Featherlight extension
426		   [Warning: guru-level]
427		   Creates the extension's prototype that in turn
428		   inherits Featherlight's prototype.
429		   Could be used to extend an extension too...
430		   This is pretty high level wizardy, it comes pretty much straight
431		   from CoffeeScript and won't teach you anything about Featherlight
432		   as it's not really specific to this library.
433		   My suggestion: move along and keep your sanity.
434		*/
435		extend: function(child, defaults) {
436			/* Setup class hierarchy, adapted from CoffeeScript */
437			var Ctor = function(){ this.constructor = child; };
438			Ctor.prototype = this.prototype;
439			child.prototype = new Ctor();
440			child.__super__ = this.prototype;
441			/* Copy class methods & attributes */
442			$.extend(child, this, defaults);
443			child.defaults = child.prototype;
444			return child;
445		},
446
447		attach: function($source, $content, config) {
448			var Klass = this;
449			if (typeof $content === 'object' && $content instanceof $ === false && !config) {
450				config = $content;
451				$content = undefined;
452			}
453			/* make a copy */
454			config = $.extend({}, config);
455
456			/* Only for openTrigger and namespace... */
457			var namespace = config.namespace || Klass.defaults.namespace,
458				tempConfig = $.extend({}, Klass.defaults, Klass.readElementConfig($source[0], namespace), config),
459				sharedPersist;
460
461			$source.on(tempConfig.openTrigger+'.'+tempConfig.namespace, tempConfig.filter, function(event) {
462				/* ... since we might as well compute the config on the actual target */
463				var elemConfig = $.extend(
464					{$source: $source, $currentTarget: $(this)},
465					Klass.readElementConfig($source[0], tempConfig.namespace),
466					Klass.readElementConfig(this, tempConfig.namespace),
467					config);
468				var fl = sharedPersist || $(this).data('featherlight-persisted') || new Klass($content, elemConfig);
469				if(fl.persist === 'shared') {
470					sharedPersist = fl;
471				} else if(fl.persist !== false) {
472					$(this).data('featherlight-persisted', fl);
473				}
474				elemConfig.$currentTarget.blur(); // Otherwise 'enter' key might trigger the dialog again
475				fl.open(event);
476			});
477			return $source;
478		},
479
480		current: function() {
481			var all = this.opened();
482			return all[all.length - 1] || null;
483		},
484
485		opened: function() {
486			var klass = this;
487			pruneOpened();
488			return $.grep(opened, function(fl) { return fl instanceof klass; } );
489		},
490
491		close: function(event) {
492			var cur = this.current();
493			if(cur) { return cur.close(event); }
494		},
495
496		/* Does the auto binding on startup.
497		   Meant only to be used by Featherlight and its extensions
498		*/
499		_onReady: function() {
500			var Klass = this;
501			if(Klass.autoBind){
502				/* Bind existing elements */
503				$(Klass.autoBind).each(function(){
504					Klass.attach($(this));
505				});
506				/* If a click propagates to the document level, then we have an item that was added later on */
507				$(document).on('click', Klass.autoBind, function(evt) {
508					if (evt.isDefaultPrevented() || evt.namespace === 'featherlight') {
509						return;
510					}
511					evt.preventDefault();
512					/* Bind featherlight */
513					Klass.attach($(evt.currentTarget));
514					/* Click again; this time our binding will catch it */
515					$(evt.target).trigger('click.featherlight');
516				});
517			}
518		},
519
520		/* Featherlight uses the onKeyUp callback to intercept the escape key.
521		   Private to Featherlight.
522		*/
523		_callbackChain: {
524			onKeyUp: function(_super, event){
525				if(27 === event.keyCode) {
526					if (this.closeOnEsc) {
527						$.featherlight.close(event);
528					}
529					return false;
530				} else {
531					return _super(event);
532				}
533			},
534
535			onResize: function(_super, event){
536				this.resize(this.$content.naturalWidth, this.$content.naturalHeight);
537				return _super(event);
538			},
539
540			afterContent: function(_super, event){
541				var r = _super(event);
542				this.onResize(event);
543				return r;
544			}
545		}
546	});
547
548	$.featherlight = Featherlight;
549
550	/* bind jQuery elements to trigger featherlight */
551	$.fn.featherlight = function($content, config) {
552		return Featherlight.attach(this, $content, config);
553	};
554
555	/* bind featherlight on ready if config autoBind is set */
556	$(document).ready(function(){ Featherlight._onReady(); });
557}(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.