PageSourceSearch

https://kominamikazumaro.com/wp-content/themes/maro2021/js/featherlight.js?ver=5.6.21

js kominamikazumaro.com collected 2026-09-25 07:12:09 UTC 23,613 bytes, 641 lines download raw bytes

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