blazy-8.x-2.x-dev/js/src/plugin/blazy.once.js
js/src/plugin/blazy.once.js
/**
* @file
* Provides once compat for D9+.
*
* @internal
* This is an internal part of the Blazy system and should only be used by
* blazy-related code in Blazy module, or its sub-modules.
*
* @see https://www.drupal.org/project/drupal/issues/1461322
* @see https://www.drupal.org/project/slick/issues/3340509
* @see https://www.drupal.org/project/slick/issues/3211873
*/
(function ($, Drupal, _win) {
'use strict';
// See https://www.drupal.org/project/drupal/issues/3254840
var coreOnce = Drupal.once || _win.once;
/**
* A wrapper for core/once with some BC.
*
* @param {Function|string} cb
* The executed function, or string for regular core/once.
* @param {string} id
* The id of the once call.
* @param {NodeList|Array.<Element>|Element|string} selector
* A NodeList, array of elements, single Element, or a string.
* @param {Document|Element|null} ctx
* An element to use as context for querySelectorAll, or empty.
* @param {Object|undefined} scope
* A value to use as `this` when executing cb, default to `undefined`.
*
* @return {Array.<Element>}
* An array of elements to process, or empty for old behavior.
*/
function onceCompat(cb, id, selector, ctx, scope) {
var els = [];
// Prevents from BigPipe problematic multiple invocations.
// Mostly relevant for [DOM|AJAX]-related mutation environment (LB, infinite
// scroll, etc), hardly static pages without DOM modification. What this
// does is waiting for BigPipe to do its job, and only when it is done, once
// is called. The drawback, it will slightly delay DOM changes, yet better
// than problematic multiple invocations.
if (!$.wwoBigPipeDone()) {
return els;
}
// If cb is a string, allow empty selector/ context for document.
// Assumes once(id, selector, context), by shifting one argument.
// This is the common implementation of core/once, but hardly used by Blazy.
if ($.isStr(cb) && $.isUnd(ctx)) {
return initOnce(cb, id, selector);
}
// Original once for BC.
if ($.isUnd(selector)) {
_once(cb);
}
// If extra arguments are provided, assumes regular loop over elements.
else {
els = initOnce(id, selector, ctx);
if (els.length) {
// Already avoids loop for a single item.
$.each(els, cb, scope);
}
}
return els;
}
/**
* Executes the function once.
*
* @private
*
* @author Daniel Lamb <dlamb.open.source@gmail.com>
* @link https://github.com/daniellmb/once.js
*
* @param {Function} cb
* The executed function.
*
* @return {Object}
* The function result.
*/
function _once(cb) {
var result;
var ran = false;
return function proxy() {
if (ran) {
return result;
}
ran = true;
result = cb.apply(this, arguments);
// For garbage collection.
cb = null;
return result;
};
}
function _filter(selector, elements, apply) {
return elements.filter(function (el) {
var selected = $.is(el, selector);
if (selected && apply) {
apply(el);
}
return selected;
});
}
// Since 3.0.6, uses core/once.
function initOnce(id, selector, ctx) {
var root = $.context(ctx, selector);
return coreOnce(id, selector, root);
}
$.once = $.extend(onceCompat, coreOnce);
$.once.counter = 0;
$.filter = _filter;
// Tested at D10.3, a workaround, not a final fix, till BigPipe issues fixed.
// This is likely the root cause of BigPipe issues, unmatched detachments.
// Normally called in Drupal.behaviors.detach() with trigger `unload`.
// This check basically makes BigPipe behaves like without it as otherwise
// `unload` trigger is called many times on BigPipe replacement jobs.
// @todo update this if blazy ajax-related is broken later, that is when
// BigPipe fixes this issue. See the above BigPipe issues.
// @fixme, not really crucial, AJAX (IO/ VIS) requires 2, the rest 1.
// Without BigPipe, always 0.
$.once.unload = $.once.counter >= ($.isBigPipe() ? 1 : 0);
// See https://developer.mozilla.org/en-US/docs/Web/CSS/:not
function extractNot(selector) {
var mounted = [];
if ($.contains(selector, ':not')) {
var notsels = selector.split(':not');
$.each(notsels, function (notsel) {
if ($.contains(notsel, '(')) {
var cls = notsel.split('(').pop().split(')')[0];
// Selector list/ compound argument, with commas.
if ($.contains(cls, ',')) {
var vals = cls.split(',');
$.each(vals, function (val) {
val = val.replace('.', '');
mounted.push(val);
});
}
else {
if (cls) {
cls = cls.replace('.', '');
mounted.push(cls);
}
}
}
});
}
return mounted;
}
$.once.removeSafely = function (id, selector, ctx) {
var me = this;
var els = [];
var root;
var unload;
var mounted;
if ($.wwoBigPipeDone()) {
unload = $.once.unload;
root = $.context(ctx, selector);
if (unload && me.find(id, root).length) {
els = me.remove(id, selector, root);
// @todo remove, might be no longer relevant for ::wwoBigPipeDone(),
// only remove after :not() classes are removed to avoid blocking.
mounted = extractNot(selector);
if (els.length && mounted.length) {
$.removeClass(els, mounted);
}
}
}
return els;
};
/**
* Attaches Blazy behavior to nothing for BigPipe compat.
*
* @type {Drupal~behavior}
*/
Drupal.behaviors.blazyOnce = {
attach: function (context) {
$.wwoBigPipe(function () {
if ($.once.counter > 1) {
$.once.counter--;
}
});
},
detach: function (context, setting, trigger) {
if (trigger === 'unload') {
$.wwoBigPipe(function () {
$.once.counter++;
});
}
}
};
})(dBlazy, Drupal, this);
