PageSourceSearch

https://hearthappyfilms.com/js/tingle.js

js hearthappyfilms.com collected 2026-10-09 18:15:25 UTC 14,145 bytes, 447 lines download raw bytes

1/*!
2* tingle.js
3* @author  robin_parisi
4* @version 0.13.2
5* @url
6*/
7(function(root, factory) {
8    if (typeof define === 'function' && define.amd) {
9        define(factory);
10    } else if (typeof exports === 'object') {
11        module.exports = factory();
12    } else {
13        root.tingle = factory();
14    }
15}(this, function() {
16
17    /* ----------------------------------------------------------- */
18    /* == modal */
19    /* ----------------------------------------------------------- */
20
21    var transitionEvent = whichTransitionEvent();
22
23    function Modal(options) {
24
25        var defaults = {
26            onClose: null,
27            onOpen: null,
28            beforeOpen: null,
29            beforeClose: null,
30            stickyFooter: false,
31            footer: false,
32            cssClass: [],
33            closeLabel: 'Close',
34            closeMethods: ['overlay', 'button', 'escape']
35        };
36
37        // extends config
38        this.opts = extend({}, defaults, options);
39
40        // init modal
41        this.init();
42    }
43
44    Modal.prototype.init = function() {
45        if (this.modal) {
46            return;
47        }
48
49        _build.call(this);
50        _bindEvents.call(this);
51
52        // insert modal in dom
53        document.body.insertBefore(this.modal, document.body.firstChild);
54
55        if (this.opts.footer) {
56            this.addFooter();
57        }
58    };
59
60    Modal.prototype.destroy = function() {
61        if (this.modal === null) {
62            return;
63        }
64
65        // unbind all events
66        _unbindEvents.call(this);
67
68        // remove modal from dom
69        this.modal.parentNode.removeChild(this.modal);
70
71        this.modal = null;
72    };
73
74
75    Modal.prototype.open = function() {
76
77        var self = this;
78
79        // before open callback
80        if (typeof self.opts.beforeOpen === 'function') {
81            self.opts.beforeOpen();
82        }
83
84        if (this.modal.style.removeProperty) {
85            this.modal.style.removeProperty('display');
86        } else {
87            this.modal.style.removeAttribute('display');
88        }
89
90        // prevent double scroll
91        this._scrollPosition = window.pageYOffset;
92        document.body.classList.add('tingle-enabled');
93        document.body.style.top = -this._scrollPosition + 'px';
94
95        // sticky footer
96        this.setStickyFooter(this.opts.stickyFooter);
97
98        // show modal
99        this.modal.classList.add('tingle-modal--visible');
100
101        if (transitionEvent) {
102            this.modal.addEventListener(transitionEvent, function handler() {
103                if (typeof self.opts.onOpen === 'function') {
104                    self.opts.onOpen.call(self);
105                }
106
107                // detach event after transition end (so it doesn't fire multiple onOpen)
108                self.modal.removeEventListener(transitionEvent, handler, false);
109
110            }, false);
111        } else {
112            if (typeof self.opts.onOpen === 'function') {
113                self.opts.onOpen.call(self);
114            }
115        }
116
117        // check if modal is bigger than screen height
118        this.checkOverflow();
119    };
120
121    Modal.prototype.isOpen = function() {
122        return !!this.modal.classList.contains("tingle-modal--visible");
123    };
124
125    Modal.prototype.close = function() {
126
127        //  before close
128        if (typeof this.opts.beforeClose === "function") {
129            var close = this.opts.beforeClose.call(this);
130            if (!close) return;
131        }
132
133        document.body.classList.remove('tingle-enabled');
134        window.scrollTo(0, this._scrollPosition);
135        document.body.style.top = null;
136
137        this.modal.classList.remove('tingle-modal--visible');
138
139        //Using similar setup as onOpen
140        //Reference to the Modal that's created
141        var self = this;
142
143        if (transitionEvent) {
144            //Track when transition is happening then run onClose on complete
145            this.modal.addEventListener(transitionEvent, function handler() {
146                // detach event after transition end (so it doesn't fire multiple onClose)
147                self.modal.removeEventListener(transitionEvent, handler, false);
148
149                self.modal.style.display = 'none';
150
151                // on close callback
152                if (typeof self.opts.onClose === "function") {
153                    self.opts.onClose.call(this);
154                }
155
156            }, false);
157        } else {
158            self.modal.style.display = 'none';
159            // on close callback
160            if (typeof self.opts.onClose === "function") {
161                self.opts.onClose.call(this);
162            }
163        }
164    };
165
166    Modal.prototype.setContent = function(content) {
167        // check type of content : String or Node
168        if (typeof content === 'string') {
169            this.modalBoxContent.innerHTML = content;
170        } else {
171            this.modalBoxContent.innerHTML = "";
172            this.modalBoxContent.appendChild(content);
173        }
174    };
175
176    Modal.prototype.getContent = function() {
177        return this.modalBoxContent;
178    };
179
180    Modal.prototype.addFooter = function() {
181        // add footer to modal
182        _buildFooter.call(this);
183    };
184
185    Modal.prototype.setFooterContent = function(content) {
186        // set footer content
187        this.modalBoxFooter.innerHTML = content;
188    };
189
190    Modal.prototype.getFooterContent = function() {
191        return this.modalBoxFooter;
192    };
193
194    Modal.prototype.setStickyFooter = function(isSticky) {
195        // if the modal is smaller than the viewport height, we don't need sticky
196        if (!this.isOverflow()) {
197            isSticky = false;
198        }
199
200        if (isSticky) {
201            if (this.modalBox.contains(this.modalBoxFooter)) {
202                this.modalBox.removeChild(this.modalBoxFooter);
203                this.modal.appendChild(this.modalBoxFooter);
204                this.modalBoxFooter.classList.add('tingle-modal-box__footer--sticky');
205                _recalculateFooterPosition.call(this);
206                this.modalBoxContent.style['padding-bottom'] = this.modalBoxFooter.clientHeight + 20 + 'px';
207            }
208        } else if (this.modalBoxFooter) {
209            if (!this.modalBox.contains(this.modalBoxFooter)) {
210                this.modal.removeChild(this.modalBoxFooter);
211                this.modalBox.appendChild(this.modalBoxFooter);
212                this.modalBoxFooter.style.width = 'auto';
213                this.modalBoxFooter.style.left = '';
214                this.modalBoxContent.style['padding-bottom'] = '';
215                this.modalBoxFooter.classList.remove('tingle-modal-box__footer--sticky');
216            }
217        }
218    };
219
220
221    Modal.prototype.addFooterBtn = function(label, cssClass, callback) {
222        var btn = document.createElement("button");
223
224        // set label
225        btn.innerHTML = label;
226
227        // bind callback
228        btn.addEventListener('click', callback);
229
230        if (typeof cssClass === 'string' && cssClass.length) {
231            // add classes to btn
232            cssClass.split(" ").forEach(function(item) {
233                btn.classList.add(item);
234            });
235        }
236
237        this.modalBoxFooter.appendChild(btn);
238
239        return btn;
240    };
241
242    Modal.prototype.resize = function() {
243        console.warn('Resize is deprecated and will be removed in version 1.0');
244    };
245
246
247    Modal.prototype.isOverflow = function() {
248        var viewportHeight = window.innerHeight;
249        var modalHeight = this.modalBox.clientHeight;
250
251        return modalHeight >= viewportHeight;
252    };
253
254    Modal.prototype.checkOverflow = function() {
255        // only if the modal is currently shown
256        if (this.modal.classList.contains('tingle-modal--visible')) {
257            if (this.isOverflow()) {
258                this.modal.classList.add('tingle-modal--overflow');
259            } else {
260                this.modal.classList.remove('tingle-modal--overflow');
261            }
262
263            // TODO: remove offset
264            //_offset.call(this);
265            if (!this.isOverflow() && this.opts.stickyFooter) {
266                this.setStickyFooter(false);
267            } else if (this.isOverflow() && this.opts.stickyFooter) {
268                _recalculateFooterPosition.call(this);
269                this.setStickyFooter(true);
270            }
271        }
272    }
273
274
275    /* ----------------------------------------------------------- */
276    /* == private methods */
277    /* ----------------------------------------------------------- */
278
279    function _recalculateFooterPosition() {
280        if (!this.modalBoxFooter) {
281            return;
282        }
283        this.modalBoxFooter.style.width = this.modalBox.clientWidth + 'px';
284        this.modalBoxFooter.style.left = this.modalBox.offsetLeft + 'px';
285    }
286
287    function _build() {
288
289        // wrapper
290        this.modal = document.createElement('div');
291        this.modal.classList.add('tingle-modal');
292
293        // remove cusor if no overlay close method
294        if (this.opts.closeMethods.length === 0 || this.opts.closeMethods.indexOf('overlay') === -1) {
295            this.modal.classList.add('tingle-modal--noOverlayClose');
296        }
297
298        this.modal.style.display = 'none';
299
300        // custom class
301        this.opts.cssClass.forEach(function(item) {
302            if (typeof item === 'string') {
303                this.modal.classList.add(item);
304            }
305        }, this);
306
307        // close btn
308        if (this.opts.closeMethods.indexOf('button') !== -1) {
309            this.modalCloseBtn = document.createElement('button');
310            this.modalCloseBtn.classList.add('tingle-modal__close');
311
312            this.modalCloseBtnIcon = document.createElement('span');
313            this.modalCloseBtnIcon.classList.add('tingle-modal__closeIcon');
314            this.modalCloseBtnIcon.innerHTML = '×';
315
316            this.modalCloseBtnLabel = document.createElement('span');
317            this.modalCloseBtnLabel.classList.add('tingle-modal__closeLabel');
318            this.modalCloseBtnLabel.innerHTML = this.opts.closeLabel;
319
320            this.modalCloseBtn.appendChild(this.modalCloseBtnIcon);
321            this.modalCloseBtn.appendChild(this.modalCloseBtnLabel);
322        }
323
324        // modal
325        this.modalBox = document.createElement('div');
326        this.modalBox.classList.add('tingle-modal-box');
327
328        // modal box content
329        this.modalBoxContent = document.createElement('div');
330        this.modalBoxContent.classList.add('tingle-modal-box__content');
331
332        this.modalBox.appendChild(this.modalBoxContent);
333
334        if (this.opts.closeMethods.indexOf('button') !== -1) {
335            this.modal.appendChild(this.modalCloseBtn);
336        }
337
338        this.modal.appendChild(this.modalBox);
339
340    }
341
342    function _buildFooter() {
343        this.modalBoxFooter = document.createElement('div');
344        this.modalBoxFooter.classList.add('tingle-modal-box__footer');
345        this.modalBox.appendChild(this.modalBoxFooter);
346    }
347
348    function _bindEvents() {
349
350        this._events = {
351            clickCloseBtn: this.close.bind(this),
352            clickOverlay: _handleClickOutside.bind(this),
353            resize: this.checkOverflow.bind(this),
354            keyboardNav: _handleKeyboardNav.bind(this)
355        };
356
357        if (this.opts.closeMethods.indexOf('button') !== -1) {
358            this.modalCloseBtn.addEventListener('click', this._events.clickCloseBtn);
359        }
360
361        this.modal.addEventListener('mousedown', this._events.clickOverlay);
362        window.addEventListener('resize', this._events.resize);
363        document.addEventListener("keydown", this._events.keyboardNav);
364    }
365
366    function _handleKeyboardNav(event) {
367        // escape key
368        if (this.opts.closeMethods.indexOf('escape') !== -1 && event.which === 27 && this.isOpen()) {
369            this.close();
370        }
371    }
372
373    function _handleClickOutside(event) {
374        // if click is outside the modal
375        if (this.opts.closeMethods.indexOf('overlay') !== -1 && !_findAnce
375stor(event.target, 'tingle-modal') &&
376        event.clientX < this.modal.clientWidth) {
377            this.close();
378        }
379    }
380
381    function _findAncestor(el, cls) {
382        while ((el = el.parentElement) && !el.classList.contains(cls));
383        return el;
384    }
385
386    function _unbindEvents() {
387        if (this.opts.closeMethods.indexOf('button') !== -1) {
388            this.modalCloseBtn.removeEventListener('click', this._events.clickCloseBtn);
389        }
390        this.modal.removeEventListener('mousedown', this._events.clickOverlay);
391        window.removeEventListener('resize', this._events.resize);
392        document.removeEventListener("keydown", this._events.keyboardNav);
393    }
394
395    /* ----------------------------------------------------------- */
396    /* == confirm */
397    /* ----------------------------------------------------------- */
398
399    // coming soon
400
401    /* ----------------------------------------------------------- */
402    /* == alert */
403    /* ----------------------------------------------------------- */
404
405    // coming soon
406
407    /* ----------------------------------------------------------- */
408    /* == helpers */
409    /* ----------------------------------------------------------- */
410
411    function extend() {
412        for (var i = 1; i < arguments.length; i++) {
413            for (var key in arguments[i]) {
414                if (arguments[i].hasOwnProperty(key)) {
415                    arguments[0][key] = arguments[i][key];
416                }
417            }
418        }
419        return arguments[0];
420    }
421
422    function whichTransitionEvent() {
423        var t;
424        var el = document.createElement('tingle-test-transition');
425        var transitions = {
426            'transition': 'transitionend',
427            'OTransition': 'oTransitionEnd',
428            'MozTransition': 'transitionend',
429            'WebkitTransition': 'webkitTransitionEnd'
430        };
431
432        for (t in transitions) {
433            if (el.style[t] !== undefined) {
434                return transitions[t];
435            }
436        }
437    }
438
439    /* ----------------------------------------------------------- */
440    /* == return */
441    /* ----------------------------------------------------------- */
442
443    return {
444        modal: Modal
445    };
446
447}));

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.