intersection-observer-admin
Advanced tools
Comparing version 0.1.0 to 0.2.0
@@ -1,4 +0,22 @@ | ||
var IntersectionObserverAdmin = /** @class */ (function () { | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
import Notifications, { CallbackType } from './notification'; | ||
import Registry from './registry'; | ||
var IntersectionObserverAdmin = /** @class */ (function (_super) { | ||
__extends(IntersectionObserverAdmin, _super); | ||
function IntersectionObserverAdmin() { | ||
this.DOMRef = new WeakMap(); | ||
var _this = _super.call(this) || this; | ||
_this.elementRegistry = new Registry(); | ||
return _this; | ||
} | ||
@@ -11,46 +29,12 @@ /** | ||
* @param {HTMLElement | Window} element | ||
* @param {Function} enterCallback | ||
* @param {Function} exitCallback | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.observe = function (element, enterCallback, exitCallback, observerOptions, scrollableArea) { | ||
var _a; | ||
if (!element || !observerOptions) { | ||
IntersectionObserverAdmin.prototype.observe = function (element, options) { | ||
if (options === void 0) { options = {}; } | ||
if (!element) { | ||
return; | ||
} | ||
var _b = observerOptions.root, root = _b === void 0 ? window : _b; | ||
// first find shared root element (window or scrollable area) | ||
var potentialRootMatch = this._findRoot(root); | ||
// second if there is a matching root, find an entry with the same observerOptions | ||
var matchingEntryForRoot; | ||
if (potentialRootMatch) { | ||
matchingEntryForRoot = this._determineMatchingElements(observerOptions, potentialRootMatch); | ||
} | ||
if (matchingEntryForRoot) { | ||
var elements = matchingEntryForRoot.elements, intersectionObserver = matchingEntryForRoot.intersectionObserver; | ||
elements.push({ element: element, enterCallback: enterCallback, exitCallback: exitCallback }); | ||
intersectionObserver.observe(element); | ||
return; | ||
} | ||
// No matching entry for root in static admin, thus create new IntersectionObserver instance | ||
var newIO = new IntersectionObserver(this._setupOnIntersection(observerOptions, scrollableArea).bind(this), observerOptions); | ||
newIO.observe(element); | ||
var observerEntry = { | ||
elements: [{ element: element, enterCallback: enterCallback, exitCallback: exitCallback }], | ||
intersectionObserver: newIO, | ||
observerOptions: observerOptions | ||
}; | ||
var stringifiedOptions = this._stringifyObserverOptions(observerOptions, scrollableArea); | ||
if (potentialRootMatch) { | ||
// if share same root and need to add new entry to root match | ||
potentialRootMatch[stringifiedOptions] = observerEntry; | ||
} | ||
else { | ||
// no root exists, so add to WeakMap | ||
if (this.DOMRef) { | ||
this.DOMRef.set(root, (_a = {}, _a[stringifiedOptions] = observerEntry, _a)); | ||
} | ||
} | ||
this.elementRegistry.addElement(element, options); | ||
this.setupObserver(element, options); | ||
}; | ||
@@ -62,21 +46,49 @@ /** | ||
* @param {HTMLElement|Window} target | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.unobserve = function (target, observerOptions, scrollableArea) { | ||
var matchingRootEntry = this._findMatchingRootEntry(observerOptions, scrollableArea); | ||
IntersectionObserverAdmin.prototype.unobserve = function (target, options) { | ||
var matchingRootEntry = this._findMatchingRootEntry(options); | ||
if (matchingRootEntry) { | ||
var intersectionObserver = matchingRootEntry.intersectionObserver, elements = matchingRootEntry.elements; | ||
var intersectionObserver = matchingRootEntry.intersectionObserver; | ||
intersectionObserver.unobserve(target); | ||
// important to do this in reverse order | ||
for (var i = elements.length - 1; i >= 0; i--) { | ||
if (elements[i] && elements[i].element === target) { | ||
elements.splice(i, 1); | ||
break; | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* register event to handle when intersection observer detects enter | ||
* | ||
* @method addEnterCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.addEnterCallback = function (element, callback) { | ||
this.addCallback(CallbackType.enter, element, callback); | ||
}; | ||
/** | ||
* register event to handle when intersection observer detects exit | ||
* | ||
* @method addExitCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.addExitCallback = function (element, callback) { | ||
this.addCallback(CallbackType.exit, element, callback); | ||
}; | ||
/** | ||
* retrieve registered callback and call with data | ||
* | ||
* @method dispatchEnterCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.dispatchEnterCallback = function (element) { | ||
this.dispatchCallback(CallbackType.enter, element); | ||
}; | ||
/** | ||
* retrieve registered callback and call with data on exit | ||
* | ||
* @method dispatchExitCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.dispatchExitCallback = function (element) { | ||
this.dispatchCallback(CallbackType.exit, element); | ||
}; | ||
/** | ||
* cleanup data structures and unobserve elements | ||
@@ -88,27 +100,77 @@ * | ||
IntersectionObserverAdmin.prototype.destroy = function () { | ||
this.DOMRef = null; | ||
this.elementRegistry.destroyRegistry(); | ||
}; | ||
/** | ||
* use function composition to curry observerOptions | ||
* use function composition to curry options | ||
* | ||
* @method _setupOnIntersection | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @method setupOnIntersection | ||
* @param {Object} options | ||
*/ | ||
IntersectionObserverAdmin.prototype._setupOnIntersection = function (observerOptions, scrollableArea) { | ||
IntersectionObserverAdmin.prototype.setupOnIntersection = function (options) { | ||
var _this = this; | ||
return function (entries) { | ||
return _this._onIntersection(observerOptions, scrollableArea, entries); | ||
return function (ioEntries) { | ||
return _this.onIntersection(options, ioEntries); | ||
}; | ||
}; | ||
IntersectionObserverAdmin.prototype.setupObserver = function (element, options) { | ||
var _a; | ||
var _b = options.root, root = _b === void 0 ? window : _b; | ||
// find shared root element (window or scrollable area) | ||
// this root is responsible for coordinating it's set of elements | ||
var potentialRootMatch = this._findRoot(root); | ||
// third if there is a matching root, see if an existing entry with the same options | ||
// regardless of sort order. This is a bit of work | ||
var matchingEntryForRoot; | ||
if (potentialRootMatch) { | ||
matchingEntryForRoot = this._determineMatchingElements(options, potentialRootMatch); | ||
} | ||
// next add found entry to elements and call observer if applicable | ||
if (matchingEntryForRoot) { | ||
var elements = matchingEntryForRoot.elements, intersectionObserver = matchingEntryForRoot.intersectionObserver; | ||
elements.push(element); | ||
if (intersectionObserver) { | ||
intersectionObserver.observe(element); | ||
} | ||
} | ||
else { | ||
// otherwise start observing this element if applicable | ||
// watcher is an instance that has an observe method | ||
var intersectionObserver = this.newObserver(element, options); | ||
var observerEntry = { | ||
elements: [element], | ||
intersectionObserver: intersectionObserver, | ||
options: options | ||
}; | ||
// and add entry to WeakMap under a root element | ||
// with watcher so we can use it later on | ||
var stringifiedOptions = this._stringifyOptions(options); | ||
if (potentialRootMatch) { | ||
// if share same root and need to add new entry to root match | ||
// not functional but :shrug | ||
potentialRootMatch[stringifiedOptions] = observerEntry; | ||
} | ||
else { | ||
// no root exists, so add to WeakMap | ||
this.elementRegistry.addElement(root, (_a = {}, | ||
_a[stringifiedOptions] = observerEntry, | ||
_a)); | ||
} | ||
} | ||
}; | ||
IntersectionObserverAdmin.prototype.newObserver = function (element, options) { | ||
// No matching entry for root in static admin, thus create new IntersectionObserver instance | ||
var root = options.root, rootMargin = options.rootMargin, threshold = options.threshold; | ||
var newIO = new IntersectionObserver(this.setupOnIntersection(options).bind(this), { root: root, rootMargin: rootMargin, threshold: threshold }); | ||
newIO.observe(element); | ||
return newIO; | ||
}; | ||
/** | ||
* IntersectionObserver callback when element is intersecting viewport | ||
* | ||
* @method _onIntersection | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @method onIntersection | ||
* @param {Object} options | ||
* @param {Array} ioEntries | ||
* @private | ||
*/ | ||
IntersectionObserverAdmin.prototype._onIntersection = function (observerOptions, scrollableArea, ioEntries) { | ||
IntersectionObserverAdmin.prototype.onIntersection = function (options, ioEntries) { | ||
var _this = this; | ||
@@ -120,10 +182,7 @@ ioEntries.forEach(function (entry) { | ||
// then find entry's callback in static administration | ||
var matchingRootEntry = _this._findMatchingRootEntry(observerOptions, scrollableArea); | ||
var matchingRootEntry = _this._findMatchingRootEntry(options); | ||
if (matchingRootEntry) { | ||
matchingRootEntry.elements.some(function (obj) { | ||
if (obj.element === entry.target) { | ||
// call entry's enter callback | ||
if (obj.enterCallback) { | ||
obj.enterCallback(); | ||
} | ||
matchingRootEntry.elements.some(function (element) { | ||
if (element && element === entry.target) { | ||
_this.dispatchEnterCallback(element); | ||
return true; | ||
@@ -137,10 +196,7 @@ } | ||
// then find entry's callback in static administration | ||
var matchingRootEntry = _this._findMatchingRootEntry(observerOptions, scrollableArea); | ||
var matchingRootEntry = _this._findMatchingRootEntry(options); | ||
if (matchingRootEntry) { | ||
matchingRootEntry.elements.some(function (obj) { | ||
if (obj.element === entry.target) { | ||
// call entry's enter callback | ||
if (obj.exitCallback) { | ||
obj.exitCallback(); | ||
} | ||
matchingRootEntry.elements.some(function (element) { | ||
if (element && element === entry.target) { | ||
_this.dispatchExitCallback(element); | ||
return true; | ||
@@ -155,3 +211,3 @@ } | ||
/** | ||
* { root: { stringifiedOptions: { elements: []...] } } | ||
* { root: { stringifiedOptions: { observer, elements: []...] } } | ||
* @method _findRoot | ||
@@ -163,22 +219,19 @@ * @param {HTMLElement|Window} root | ||
IntersectionObserverAdmin.prototype._findRoot = function (root) { | ||
if (this.DOMRef) { | ||
return this.DOMRef.get(root); | ||
if (this.elementRegistry) { | ||
return this.elementRegistry.getElement(root); | ||
} | ||
}; | ||
/** | ||
* Used for onIntersection callbacks and unobserving the IntersectionObserver | ||
* We don't care about observerOptions key order because we already added | ||
* to the static administrator or found an existing IntersectionObserver with the same | ||
* root && observerOptions to reuse | ||
* We don't care about options key order because we already added | ||
* to the static administrator | ||
* | ||
* @method _findMatchingRootEntry | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @return {Object} entry with elements and other options | ||
*/ | ||
IntersectionObserverAdmin.prototype._findMatchingRootEntry = function (observerOptions, scrollableArea) { | ||
var _a = observerOptions.root, root = _a === void 0 ? window : _a; | ||
IntersectionObserverAdmin.prototype._findMatchingRootEntry = function (options) { | ||
var _a = options.root, root = _a === void 0 ? window : _a; | ||
var matchingRoot = this._findRoot(root); | ||
if (matchingRoot) { | ||
var stringifiedOptions = this._stringifyObserverOptions(observerOptions, scrollableArea); | ||
var stringifiedOptions = this._stringifyOptions(options); | ||
return matchingRoot[stringifiedOptions]; | ||
@@ -188,7 +241,7 @@ } | ||
/** | ||
* Determine if existing elements for a given root based on passed in observerOptions | ||
* Determine if existing elements for a given root based on passed in options | ||
* regardless of sort order of keys | ||
* | ||
* @method _determineMatchingElements | ||
* @param {Object} observerOptions | ||
* @param {Object} options | ||
* @param {Object} potentialRootMatch e.g. { stringifiedOptions: { elements: [], ... }, stringifiedOptions: { elements: [], ... }} | ||
@@ -198,10 +251,7 @@ * @private | ||
*/ | ||
IntersectionObserverAdmin.prototype._determineMatchingElements = function (observerOptions, potentialRootMatch) { | ||
IntersectionObserverAdmin.prototype._determineMatchingElements = function (options, potentialRootMatch) { | ||
var _this = this; | ||
if (!potentialRootMatch) { | ||
return; | ||
} | ||
var matchingKey = Object.keys(potentialRootMatch).filter(function (key) { | ||
var comparableOptions = potentialRootMatch[key].observerOptions; | ||
return _this._areOptionsSame(observerOptions, comparableOptions); | ||
var comparableOptions = potentialRootMatch[key].options; | ||
return _this._areOptionsSame(options, comparableOptions); | ||
})[0]; | ||
@@ -215,3 +265,3 @@ return potentialRootMatch[matchingKey]; | ||
* @method _areOptionsSame | ||
* @param {Object} observerOptions | ||
* @param {Object} options | ||
* @param {Object} comparableOptions | ||
@@ -221,5 +271,5 @@ * @private | ||
*/ | ||
IntersectionObserverAdmin.prototype._areOptionsSame = function (observerOptions, comparableOptions) { | ||
IntersectionObserverAdmin.prototype._areOptionsSame = function (options, comparableOptions) { | ||
// simple comparison of string, number or even null/undefined | ||
var type1 = Object.prototype.toString.call(observerOptions); | ||
var type1 = Object.prototype.toString.call(options); | ||
var type2 = Object.prototype.toString.call(comparableOptions); | ||
@@ -230,10 +280,9 @@ if (type1 !== type2) { | ||
else if (type1 !== '[object Object]' && type2 !== '[object Object]') { | ||
return observerOptions === comparableOptions; | ||
return options === comparableOptions; | ||
} | ||
// complex comparison for only type of [object Object] | ||
for (var key in observerOptions) { | ||
if (observerOptions.hasOwnProperty(key)) { | ||
for (var key in options) { | ||
if (options.hasOwnProperty(key)) { | ||
// recursion to check nested | ||
if (this._areOptionsSame(observerOptions[key], comparableOptions[key]) === | ||
false) { | ||
if (this._areOptionsSame(options[key], comparableOptions[key]) === false) { | ||
return false; | ||
@@ -246,11 +295,11 @@ } | ||
/** | ||
* Stringify observerOptions for use as a key. | ||
* Excludes observerOptions.root so that the resulting key is stable | ||
* Stringify options for use as a key. | ||
* Excludes options.root so that the resulting key is stable | ||
* | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @private | ||
* @return {String} | ||
*/ | ||
IntersectionObserverAdmin.prototype._stringifyObserverOptions = function (observerOptions, scrollableArea) { | ||
IntersectionObserverAdmin.prototype._stringifyOptions = function (options) { | ||
var scrollableArea = options.scrollableArea; | ||
var replacer = function (key, value) { | ||
@@ -262,7 +311,7 @@ if (key === 'root' && scrollableArea) { | ||
}; | ||
return JSON.stringify(observerOptions, replacer); | ||
return JSON.stringify(options, replacer); | ||
}; | ||
return IntersectionObserverAdmin; | ||
}()); | ||
}(Notifications)); | ||
export default IntersectionObserverAdmin; | ||
//# sourceMappingURL=index.js.map |
@@ -1,22 +0,11 @@ | ||
declare type IndividualEntry = { | ||
element?: HTMLElement | Window; | ||
enterCallback?: Function; | ||
exitCallback?: Function; | ||
}; | ||
export interface IObserverOption { | ||
import Notifications from './notification'; | ||
export interface IOptions { | ||
root?: HTMLElement; | ||
rootMargin?: string; | ||
threshold?: number; | ||
scrollableArea?: string; | ||
[key: string]: any; | ||
} | ||
declare type RootEntry = { | ||
elements: [IndividualEntry]; | ||
observerOptions: IObserverOption; | ||
intersectionObserver: any; | ||
}; | ||
declare type PotentialRootEntry = { | ||
[stringifiedOptions: string]: RootEntry; | ||
}; | ||
export default class IntersectionObserverAdmin { | ||
private DOMRef; | ||
export default class IntersectionObserverAdmin extends Notifications { | ||
private elementRegistry; | ||
constructor(); | ||
@@ -29,9 +18,6 @@ /** | ||
* @param {HTMLElement | Window} element | ||
* @param {Function} enterCallback | ||
* @param {Function} exitCallback | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @public | ||
*/ | ||
observe(element: HTMLElement, enterCallback: Function, exitCallback: Function, observerOptions?: IObserverOption, scrollableArea?: string): void; | ||
observe(element: HTMLElement, options?: IOptions): void; | ||
/** | ||
@@ -42,8 +28,35 @@ * Unobserve target element and remove element from static admin | ||
* @param {HTMLElement|Window} target | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @public | ||
*/ | ||
unobserve(target: HTMLElement, observerOptions: IObserverOption, scrollableArea: string): void; | ||
unobserve(target: HTMLElement, options: IOptions): void; | ||
/** | ||
* register event to handle when intersection observer detects enter | ||
* | ||
* @method addEnterCallback | ||
* @public | ||
*/ | ||
addEnterCallback(element: HTMLElement | Window, callback: (data?: any) => void): void; | ||
/** | ||
* register event to handle when intersection observer detects exit | ||
* | ||
* @method addExitCallback | ||
* @public | ||
*/ | ||
addExitCallback(element: HTMLElement | Window, callback: (data?: any) => void): void; | ||
/** | ||
* retrieve registered callback and call with data | ||
* | ||
* @method dispatchEnterCallback | ||
* @public | ||
*/ | ||
dispatchEnterCallback(element: HTMLElement | Window): void; | ||
/** | ||
* retrieve registered callback and call with data on exit | ||
* | ||
* @method dispatchExitCallback | ||
* @public | ||
*/ | ||
dispatchExitCallback(element: HTMLElement | Window): void; | ||
/** | ||
* cleanup data structures and unobserve elements | ||
@@ -56,21 +69,21 @@ * | ||
/** | ||
* use function composition to curry observerOptions | ||
* use function composition to curry options | ||
* | ||
* @method _setupOnIntersection | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @method setupOnIntersection | ||
* @param {Object} options | ||
*/ | ||
protected _setupOnIntersection(observerOptions: IObserverOption, scrollableArea: string | undefined): Function; | ||
protected setupOnIntersection(options: IOptions): Function; | ||
protected setupObserver(element: HTMLElement, options: IOptions): void; | ||
private newObserver; | ||
/** | ||
* IntersectionObserver callback when element is intersecting viewport | ||
* | ||
* @method _onIntersection | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @method onIntersection | ||
* @param {Object} options | ||
* @param {Array} ioEntries | ||
* @private | ||
*/ | ||
protected _onIntersection(observerOptions: IObserverOption, scrollableArea: string | undefined, ioEntries: Array<any>): void; | ||
private onIntersection; | ||
/** | ||
* { root: { stringifiedOptions: { elements: []...] } } | ||
* { root: { stringifiedOptions: { observer, elements: []...] } } | ||
* @method _findRoot | ||
@@ -81,21 +94,18 @@ * @param {HTMLElement|Window} root | ||
*/ | ||
protected _findRoot(root: HTMLElement | Window): PotentialRootEntry | null | undefined; | ||
private _findRoot; | ||
/** | ||
* Used for onIntersection callbacks and unobserving the IntersectionObserver | ||
* We don't care about observerOptions key order because we already added | ||
* to the static administrator or found an existing IntersectionObserver with the same | ||
* root && observerOptions to reuse | ||
* We don't care about options key order because we already added | ||
* to the static administrator | ||
* | ||
* @method _findMatchingRootEntry | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @return {Object} entry with elements and other options | ||
*/ | ||
protected _findMatchingRootEntry(observerOptions: IObserverOption, scrollableArea: string | undefined): RootEntry | undefined; | ||
private _findMatchingRootEntry; | ||
/** | ||
* Determine if existing elements for a given root based on passed in observerOptions | ||
* Determine if existing elements for a given root based on passed in options | ||
* regardless of sort order of keys | ||
* | ||
* @method _determineMatchingElements | ||
* @param {Object} observerOptions | ||
* @param {Object} options | ||
* @param {Object} potentialRootMatch e.g. { stringifiedOptions: { elements: [], ... }, stringifiedOptions: { elements: [], ... }} | ||
@@ -105,3 +115,3 @@ * @private | ||
*/ | ||
protected _determineMatchingElements(observerOptions: IObserverOption, potentialRootMatch?: PotentialRootEntry): RootEntry | undefined; | ||
private _determineMatchingElements; | ||
/** | ||
@@ -112,3 +122,3 @@ * recursive method to test primitive string, number, null, etc and complex | ||
* @method _areOptionsSame | ||
* @param {Object} observerOptions | ||
* @param {Object} options | ||
* @param {Object} comparableOptions | ||
@@ -118,14 +128,12 @@ * @private | ||
*/ | ||
protected _areOptionsSame(observerOptions: IObserverOption, comparableOptions: IObserverOption): boolean; | ||
private _areOptionsSame; | ||
/** | ||
* Stringify observerOptions for use as a key. | ||
* Excludes observerOptions.root so that the resulting key is stable | ||
* Stringify options for use as a key. | ||
* Excludes options.root so that the resulting key is stable | ||
* | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @private | ||
* @return {String} | ||
*/ | ||
protected _stringifyObserverOptions(observerOptions: IObserverOption, scrollableArea: string | undefined): string; | ||
private _stringifyOptions; | ||
} | ||
export {}; |
@@ -1,57 +0,132 @@ | ||
var IntersectionObserverAdmin = /** @class */ (function () { | ||
function IntersectionObserverAdmin() { | ||
this.DOMRef = new WeakMap(); | ||
var Registry = /** @class */ (function () { | ||
function Registry() { | ||
this.registry = new WeakMap(); | ||
} | ||
Registry.prototype.elementExists = function (elem) { | ||
return this.registry.has(elem); | ||
}; | ||
Registry.prototype.getElement = function (elem) { | ||
return this.registry.get(elem); | ||
}; | ||
/** | ||
* Adds element to observe via IntersectionObserver and stores element + relevant callbacks and observer options in static | ||
* administrator for lookup in the future | ||
* | ||
* @method add | ||
* @param {HTMLElement | Window} element | ||
* @param {Function} enterCallback | ||
* @param {Function} exitCallback | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {HTMLElement | Window} element - the item to add to root element registry | ||
* @param {IOption} options | ||
* @param {IOption.root} root - contains optional root e.g. window, container div, etc | ||
* @param {IOption.watcher} observer - optional | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.observe = function (element, enterCallback, exitCallback, observerOptions, scrollableArea) { | ||
var _a; | ||
if (!element || !observerOptions) { | ||
Registry.prototype.addElement = function (element, options) { | ||
if (!element) { | ||
return; | ||
} | ||
var _b = observerOptions.root, root = _b === void 0 ? window : _b; | ||
// first find shared root element (window or scrollable area) | ||
var potentialRootMatch = this._findRoot(root); | ||
// second if there is a matching root, find an entry with the same observerOptions | ||
var matchingEntryForRoot; | ||
if (potentialRootMatch) { | ||
matchingEntryForRoot = this._determineMatchingElements(observerOptions, potentialRootMatch); | ||
this.registry.set(element, options || {}); | ||
}; | ||
/** | ||
* @method remove | ||
* @param {HTMLElement|Window} target | ||
* @public | ||
*/ | ||
Registry.prototype.removeElement = function (target) { | ||
this.registry.delete(target); | ||
}; | ||
/** | ||
* reset weak map | ||
* | ||
* @method destroy | ||
* @public | ||
*/ | ||
Registry.prototype.destroyRegistry = function () { | ||
this.registry = new WeakMap(); | ||
}; | ||
return Registry; | ||
}()); | ||
var noop = function () { }; | ||
var CallbackType; | ||
(function (CallbackType) { | ||
CallbackType["enter"] = "enter"; | ||
CallbackType["exit"] = "exit"; | ||
})(CallbackType || (CallbackType = {})); | ||
var Notifications = /** @class */ (function () { | ||
function Notifications() { | ||
this.registry = new Registry(); | ||
} | ||
/** | ||
* Adds an EventListener as a callback for an event key. | ||
* @param type 'enter' or 'exit' | ||
* @param key The key of the event | ||
* @param callback The callback function to invoke when the event occurs | ||
*/ | ||
Notifications.prototype.addCallback = function (type, element, callback) { | ||
var _a, _b; | ||
var entry; | ||
if (type === CallbackType.enter) { | ||
entry = (_a = {}, _a[CallbackType.enter] = callback, _a); | ||
} | ||
if (matchingEntryForRoot) { | ||
var elements = matchingEntryForRoot.elements, intersectionObserver = matchingEntryForRoot.intersectionObserver; | ||
elements.push({ element: element, enterCallback: enterCallback, exitCallback: exitCallback }); | ||
intersectionObserver.observe(element); | ||
return; | ||
else { | ||
entry = (_b = {}, _b[CallbackType.exit] = callback, _b); | ||
} | ||
// No matching entry for root in static admin, thus create new IntersectionObserver instance | ||
var newIO = new IntersectionObserver(this._setupOnIntersection(observerOptions, scrollableArea).bind(this), observerOptions); | ||
newIO.observe(element); | ||
var observerEntry = { | ||
elements: [{ element: element, enterCallback: enterCallback, exitCallback: exitCallback }], | ||
intersectionObserver: newIO, | ||
observerOptions: observerOptions | ||
}; | ||
var stringifiedOptions = this._stringifyObserverOptions(observerOptions, scrollableArea); | ||
if (potentialRootMatch) { | ||
// if share same root and need to add new entry to root match | ||
potentialRootMatch[stringifiedOptions] = observerEntry; | ||
this.registry.addElement(element, Object.assign({}, this.registry.getElement(element), entry)); | ||
}; | ||
/** | ||
* @hidden | ||
* Executes registered callbacks for key. | ||
* @param type | ||
* @param element | ||
* @param data | ||
*/ | ||
Notifications.prototype.dispatchCallback = function (type, element, data) { | ||
if (type === CallbackType.enter) { | ||
var _a = this.registry.getElement(element).enter, enter = _a === void 0 ? noop : _a; | ||
enter(data); | ||
} | ||
else { | ||
// no root exists, so add to WeakMap | ||
if (this.DOMRef) { | ||
this.DOMRef.set(root, (_a = {}, _a[stringifiedOptions] = observerEntry, _a)); | ||
} | ||
var _b = this.registry.getElement(element).exit, exit = _b === void 0 ? noop : _b; | ||
exit(data); | ||
} | ||
}; | ||
return Notifications; | ||
}()); | ||
var __extends = (undefined && undefined.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var IntersectionObserverAdmin = /** @class */ (function (_super) { | ||
__extends(IntersectionObserverAdmin, _super); | ||
function IntersectionObserverAdmin() { | ||
var _this = _super.call(this) || this; | ||
_this.elementRegistry = new Registry(); | ||
return _this; | ||
} | ||
/** | ||
* Adds element to observe via IntersectionObserver and stores element + relevant callbacks and observer options in static | ||
* administrator for lookup in the future | ||
* | ||
* @method add | ||
* @param {HTMLElement | Window} element | ||
* @param {Object} options | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.observe = function (element, options) { | ||
if (options === void 0) { options = {}; } | ||
if (!element) { | ||
return; | ||
} | ||
this.elementRegistry.addElement(element, options); | ||
this.setupObserver(element, options); | ||
}; | ||
/** | ||
* Unobserve target element and remove element from static admin | ||
@@ -61,21 +136,49 @@ * | ||
* @param {HTMLElement|Window} target | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.unobserve = function (target, observerOptions, scrollableArea) { | ||
var matchingRootEntry = this._findMatchingRootEntry(observerOptions, scrollableArea); | ||
IntersectionObserverAdmin.prototype.unobserve = function (target, options) { | ||
var matchingRootEntry = this._findMatchingRootEntry(options); | ||
if (matchingRootEntry) { | ||
var intersectionObserver = matchingRootEntry.intersectionObserver, elements = matchingRootEntry.elements; | ||
var intersectionObserver = matchingRootEntry.intersectionObserver; | ||
intersectionObserver.unobserve(target); | ||
// important to do this in reverse order | ||
for (var i = elements.length - 1; i >= 0; i--) { | ||
if (elements[i] && elements[i].element === target) { | ||
elements.splice(i, 1); | ||
break; | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* register event to handle when intersection observer detects enter | ||
* | ||
* @method addEnterCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.addEnterCallback = function (element, callback) { | ||
this.addCallback(CallbackType.enter, element, callback); | ||
}; | ||
/** | ||
* register event to handle when intersection observer detects exit | ||
* | ||
* @method addExitCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.addExitCallback = function (element, callback) { | ||
this.addCallback(CallbackType.exit, element, callback); | ||
}; | ||
/** | ||
* retrieve registered callback and call with data | ||
* | ||
* @method dispatchEnterCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.dispatchEnterCallback = function (element) { | ||
this.dispatchCallback(CallbackType.enter, element); | ||
}; | ||
/** | ||
* retrieve registered callback and call with data on exit | ||
* | ||
* @method dispatchExitCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.dispatchExitCallback = function (element) { | ||
this.dispatchCallback(CallbackType.exit, element); | ||
}; | ||
/** | ||
* cleanup data structures and unobserve elements | ||
@@ -87,27 +190,77 @@ * | ||
IntersectionObserverAdmin.prototype.destroy = function () { | ||
this.DOMRef = null; | ||
this.elementRegistry.destroyRegistry(); | ||
}; | ||
/** | ||
* use function composition to curry observerOptions | ||
* use function composition to curry options | ||
* | ||
* @method _setupOnIntersection | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @method setupOnIntersection | ||
* @param {Object} options | ||
*/ | ||
IntersectionObserverAdmin.prototype._setupOnIntersection = function (observerOptions, scrollableArea) { | ||
IntersectionObserverAdmin.prototype.setupOnIntersection = function (options) { | ||
var _this = this; | ||
return function (entries) { | ||
return _this._onIntersection(observerOptions, scrollableArea, entries); | ||
return function (ioEntries) { | ||
return _this.onIntersection(options, ioEntries); | ||
}; | ||
}; | ||
IntersectionObserverAdmin.prototype.setupObserver = function (element, options) { | ||
var _a; | ||
var _b = options.root, root = _b === void 0 ? window : _b; | ||
// find shared root element (window or scrollable area) | ||
// this root is responsible for coordinating it's set of elements | ||
var potentialRootMatch = this._findRoot(root); | ||
// third if there is a matching root, see if an existing entry with the same options | ||
// regardless of sort order. This is a bit of work | ||
var matchingEntryForRoot; | ||
if (potentialRootMatch) { | ||
matchingEntryForRoot = this._determineMatchingElements(options, potentialRootMatch); | ||
} | ||
// next add found entry to elements and call observer if applicable | ||
if (matchingEntryForRoot) { | ||
var elements = matchingEntryForRoot.elements, intersectionObserver = matchingEntryForRoot.intersectionObserver; | ||
elements.push(element); | ||
if (intersectionObserver) { | ||
intersectionObserver.observe(element); | ||
} | ||
} | ||
else { | ||
// otherwise start observing this element if applicable | ||
// watcher is an instance that has an observe method | ||
var intersectionObserver = this.newObserver(element, options); | ||
var observerEntry = { | ||
elements: [element], | ||
intersectionObserver: intersectionObserver, | ||
options: options | ||
}; | ||
// and add entry to WeakMap under a root element | ||
// with watcher so we can use it later on | ||
var stringifiedOptions = this._stringifyOptions(options); | ||
if (potentialRootMatch) { | ||
// if share same root and need to add new entry to root match | ||
// not functional but :shrug | ||
potentialRootMatch[stringifiedOptions] = observerEntry; | ||
} | ||
else { | ||
// no root exists, so add to WeakMap | ||
this.elementRegistry.addElement(root, (_a = {}, | ||
_a[stringifiedOptions] = observerEntry, | ||
_a)); | ||
} | ||
} | ||
}; | ||
IntersectionObserverAdmin.prototype.newObserver = function (element, options) { | ||
// No matching entry for root in static admin, thus create new IntersectionObserver instance | ||
var root = options.root, rootMargin = options.rootMargin, threshold = options.threshold; | ||
var newIO = new IntersectionObserver(this.setupOnIntersection(options).bind(this), { root: root, rootMargin: rootMargin, threshold: threshold }); | ||
newIO.observe(element); | ||
return newIO; | ||
}; | ||
/** | ||
* IntersectionObserver callback when element is intersecting viewport | ||
* | ||
* @method _onIntersection | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @method onIntersection | ||
* @param {Object} options | ||
* @param {Array} ioEntries | ||
* @private | ||
*/ | ||
IntersectionObserverAdmin.prototype._onIntersection = function (observerOptions, scrollableArea, ioEntries) { | ||
IntersectionObserverAdmin.prototype.onIntersection = function (options, ioEntries) { | ||
var _this = this; | ||
@@ -119,10 +272,7 @@ ioEntries.forEach(function (entry) { | ||
// then find entry's callback in static administration | ||
var matchingRootEntry = _this._findMatchingRootEntry(observerOptions, scrollableArea); | ||
var matchingRootEntry = _this._findMatchingRootEntry(options); | ||
if (matchingRootEntry) { | ||
matchingRootEntry.elements.some(function (obj) { | ||
if (obj.element === entry.target) { | ||
// call entry's enter callback | ||
if (obj.enterCallback) { | ||
obj.enterCallback(); | ||
} | ||
matchingRootEntry.elements.some(function (element) { | ||
if (element && element === entry.target) { | ||
_this.dispatchEnterCallback(element); | ||
return true; | ||
@@ -136,10 +286,7 @@ } | ||
// then find entry's callback in static administration | ||
var matchingRootEntry = _this._findMatchingRootEntry(observerOptions, scrollableArea); | ||
var matchingRootEntry = _this._findMatchingRootEntry(options); | ||
if (matchingRootEntry) { | ||
matchingRootEntry.elements.some(function (obj) { | ||
if (obj.element === entry.target) { | ||
// call entry's enter callback | ||
if (obj.exitCallback) { | ||
obj.exitCallback(); | ||
} | ||
matchingRootEntry.elements.some(function (element) { | ||
if (element && element === entry.target) { | ||
_this.dispatchExitCallback(element); | ||
return true; | ||
@@ -154,3 +301,3 @@ } | ||
/** | ||
* { root: { stringifiedOptions: { elements: []...] } } | ||
* { root: { stringifiedOptions: { observer, elements: []...] } } | ||
* @method _findRoot | ||
@@ -162,22 +309,19 @@ * @param {HTMLElement|Window} root | ||
IntersectionObserverAdmin.prototype._findRoot = function (root) { | ||
if (this.DOMRef) { | ||
return this.DOMRef.get(root); | ||
if (this.elementRegistry) { | ||
return this.elementRegistry.getElement(root); | ||
} | ||
}; | ||
/** | ||
* Used for onIntersection callbacks and unobserving the IntersectionObserver | ||
* We don't care about observerOptions key order because we already added | ||
* to the static administrator or found an existing IntersectionObserver with the same | ||
* root && observerOptions to reuse | ||
* We don't care about options key order because we already added | ||
* to the static administrator | ||
* | ||
* @method _findMatchingRootEntry | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @return {Object} entry with elements and other options | ||
*/ | ||
IntersectionObserverAdmin.prototype._findMatchingRootEntry = function (observerOptions, scrollableArea) { | ||
var _a = observerOptions.root, root = _a === void 0 ? window : _a; | ||
IntersectionObserverAdmin.prototype._findMatchingRootEntry = function (options) { | ||
var _a = options.root, root = _a === void 0 ? window : _a; | ||
var matchingRoot = this._findRoot(root); | ||
if (matchingRoot) { | ||
var stringifiedOptions = this._stringifyObserverOptions(observerOptions, scrollableArea); | ||
var stringifiedOptions = this._stringifyOptions(options); | ||
return matchingRoot[stringifiedOptions]; | ||
@@ -187,7 +331,7 @@ } | ||
/** | ||
* Determine if existing elements for a given root based on passed in observerOptions | ||
* Determine if existing elements for a given root based on passed in options | ||
* regardless of sort order of keys | ||
* | ||
* @method _determineMatchingElements | ||
* @param {Object} observerOptions | ||
* @param {Object} options | ||
* @param {Object} potentialRootMatch e.g. { stringifiedOptions: { elements: [], ... }, stringifiedOptions: { elements: [], ... }} | ||
@@ -197,10 +341,7 @@ * @private | ||
*/ | ||
IntersectionObserverAdmin.prototype._determineMatchingElements = function (observerOptions, potentialRootMatch) { | ||
IntersectionObserverAdmin.prototype._determineMatchingElements = function (options, potentialRootMatch) { | ||
var _this = this; | ||
if (!potentialRootMatch) { | ||
return; | ||
} | ||
var matchingKey = Object.keys(potentialRootMatch).filter(function (key) { | ||
var comparableOptions = potentialRootMatch[key].observerOptions; | ||
return _this._areOptionsSame(observerOptions, comparableOptions); | ||
var comparableOptions = potentialRootMatch[key].options; | ||
return _this._areOptionsSame(options, comparableOptions); | ||
})[0]; | ||
@@ -214,3 +355,3 @@ return potentialRootMatch[matchingKey]; | ||
* @method _areOptionsSame | ||
* @param {Object} observerOptions | ||
* @param {Object} options | ||
* @param {Object} comparableOptions | ||
@@ -220,5 +361,5 @@ * @private | ||
*/ | ||
IntersectionObserverAdmin.prototype._areOptionsSame = function (observerOptions, comparableOptions) { | ||
IntersectionObserverAdmin.prototype._areOptionsSame = function (options, comparableOptions) { | ||
// simple comparison of string, number or even null/undefined | ||
var type1 = Object.prototype.toString.call(observerOptions); | ||
var type1 = Object.prototype.toString.call(options); | ||
var type2 = Object.prototype.toString.call(comparableOptions); | ||
@@ -229,10 +370,9 @@ if (type1 !== type2) { | ||
else if (type1 !== '[object Object]' && type2 !== '[object Object]') { | ||
return observerOptions === comparableOptions; | ||
return options === comparableOptions; | ||
} | ||
// complex comparison for only type of [object Object] | ||
for (var key in observerOptions) { | ||
if (observerOptions.hasOwnProperty(key)) { | ||
for (var key in options) { | ||
if (options.hasOwnProperty(key)) { | ||
// recursion to check nested | ||
if (this._areOptionsSame(observerOptions[key], comparableOptions[key]) === | ||
false) { | ||
if (this._areOptionsSame(options[key], comparableOptions[key]) === false) { | ||
return false; | ||
@@ -245,11 +385,11 @@ } | ||
/** | ||
* Stringify observerOptions for use as a key. | ||
* Excludes observerOptions.root so that the resulting key is stable | ||
* Stringify options for use as a key. | ||
* Excludes options.root so that the resulting key is stable | ||
* | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @private | ||
* @return {String} | ||
*/ | ||
IntersectionObserverAdmin.prototype._stringifyObserverOptions = function (observerOptions, scrollableArea) { | ||
IntersectionObserverAdmin.prototype._stringifyOptions = function (options) { | ||
var scrollableArea = options.scrollableArea; | ||
var replacer = function (key, value) { | ||
@@ -261,7 +401,7 @@ if (key === 'root' && scrollableArea) { | ||
}; | ||
return JSON.stringify(observerOptions, replacer); | ||
return JSON.stringify(options, replacer); | ||
}; | ||
return IntersectionObserverAdmin; | ||
}()); | ||
}(Notifications)); | ||
export default IntersectionObserverAdmin; |
@@ -7,58 +7,133 @@ (function (global, factory) { | ||
var IntersectionObserverAdmin = /** @class */ (function () { | ||
function IntersectionObserverAdmin() { | ||
this.DOMRef = new WeakMap(); | ||
var Registry = /** @class */ (function () { | ||
function Registry() { | ||
this.registry = new WeakMap(); | ||
} | ||
Registry.prototype.elementExists = function (elem) { | ||
return this.registry.has(elem); | ||
}; | ||
Registry.prototype.getElement = function (elem) { | ||
return this.registry.get(elem); | ||
}; | ||
/** | ||
* Adds element to observe via IntersectionObserver and stores element + relevant callbacks and observer options in static | ||
* administrator for lookup in the future | ||
* | ||
* @method add | ||
* @param {HTMLElement | Window} element | ||
* @param {Function} enterCallback | ||
* @param {Function} exitCallback | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {HTMLElement | Window} element - the item to add to root element registry | ||
* @param {IOption} options | ||
* @param {IOption.root} root - contains optional root e.g. window, container div, etc | ||
* @param {IOption.watcher} observer - optional | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.observe = function (element, enterCallback, exitCallback, observerOptions, scrollableArea) { | ||
var _a; | ||
if (!element || !observerOptions) { | ||
Registry.prototype.addElement = function (element, options) { | ||
if (!element) { | ||
return; | ||
} | ||
var _b = observerOptions.root, root = _b === void 0 ? window : _b; | ||
// first find shared root element (window or scrollable area) | ||
var potentialRootMatch = this._findRoot(root); | ||
// second if there is a matching root, find an entry with the same observerOptions | ||
var matchingEntryForRoot; | ||
if (potentialRootMatch) { | ||
matchingEntryForRoot = this._determineMatchingElements(observerOptions, potentialRootMatch); | ||
this.registry.set(element, options || {}); | ||
}; | ||
/** | ||
* @method remove | ||
* @param {HTMLElement|Window} target | ||
* @public | ||
*/ | ||
Registry.prototype.removeElement = function (target) { | ||
this.registry.delete(target); | ||
}; | ||
/** | ||
* reset weak map | ||
* | ||
* @method destroy | ||
* @public | ||
*/ | ||
Registry.prototype.destroyRegistry = function () { | ||
this.registry = new WeakMap(); | ||
}; | ||
return Registry; | ||
}()); | ||
var noop = function () { }; | ||
var CallbackType; | ||
(function (CallbackType) { | ||
CallbackType["enter"] = "enter"; | ||
CallbackType["exit"] = "exit"; | ||
})(CallbackType || (CallbackType = {})); | ||
var Notifications = /** @class */ (function () { | ||
function Notifications() { | ||
this.registry = new Registry(); | ||
} | ||
/** | ||
* Adds an EventListener as a callback for an event key. | ||
* @param type 'enter' or 'exit' | ||
* @param key The key of the event | ||
* @param callback The callback function to invoke when the event occurs | ||
*/ | ||
Notifications.prototype.addCallback = function (type, element, callback) { | ||
var _a, _b; | ||
var entry; | ||
if (type === CallbackType.enter) { | ||
entry = (_a = {}, _a[CallbackType.enter] = callback, _a); | ||
} | ||
if (matchingEntryForRoot) { | ||
var elements = matchingEntryForRoot.elements, intersectionObserver = matchingEntryForRoot.intersectionObserver; | ||
elements.push({ element: element, enterCallback: enterCallback, exitCallback: exitCallback }); | ||
intersectionObserver.observe(element); | ||
return; | ||
else { | ||
entry = (_b = {}, _b[CallbackType.exit] = callback, _b); | ||
} | ||
// No matching entry for root in static admin, thus create new IntersectionObserver instance | ||
var newIO = new IntersectionObserver(this._setupOnIntersection(observerOptions, scrollableArea).bind(this), observerOptions); | ||
newIO.observe(element); | ||
var observerEntry = { | ||
elements: [{ element: element, enterCallback: enterCallback, exitCallback: exitCallback }], | ||
intersectionObserver: newIO, | ||
observerOptions: observerOptions | ||
}; | ||
var stringifiedOptions = this._stringifyObserverOptions(observerOptions, scrollableArea); | ||
if (potentialRootMatch) { | ||
// if share same root and need to add new entry to root match | ||
potentialRootMatch[stringifiedOptions] = observerEntry; | ||
this.registry.addElement(element, Object.assign({}, this.registry.getElement(element), entry)); | ||
}; | ||
/** | ||
* @hidden | ||
* Executes registered callbacks for key. | ||
* @param type | ||
* @param element | ||
* @param data | ||
*/ | ||
Notifications.prototype.dispatchCallback = function (type, element, data) { | ||
if (type === CallbackType.enter) { | ||
var _a = this.registry.getElement(element).enter, enter = _a === void 0 ? noop : _a; | ||
enter(data); | ||
} | ||
else { | ||
// no root exists, so add to WeakMap | ||
if (this.DOMRef) { | ||
this.DOMRef.set(root, (_a = {}, _a[stringifiedOptions] = observerEntry, _a)); | ||
} | ||
var _b = this.registry.getElement(element).exit, exit = _b === void 0 ? noop : _b; | ||
exit(data); | ||
} | ||
}; | ||
return Notifications; | ||
}()); | ||
var __extends = (undefined && undefined.__extends) || (function () { | ||
var extendStatics = function (d, b) { | ||
extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return extendStatics(d, b); | ||
}; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var IntersectionObserverAdmin = /** @class */ (function (_super) { | ||
__extends(IntersectionObserverAdmin, _super); | ||
function IntersectionObserverAdmin() { | ||
var _this = _super.call(this) || this; | ||
_this.elementRegistry = new Registry(); | ||
return _this; | ||
} | ||
/** | ||
* Adds element to observe via IntersectionObserver and stores element + relevant callbacks and observer options in static | ||
* administrator for lookup in the future | ||
* | ||
* @method add | ||
* @param {HTMLElement | Window} element | ||
* @param {Object} options | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.observe = function (element, options) { | ||
if (options === void 0) { options = {}; } | ||
if (!element) { | ||
return; | ||
} | ||
this.elementRegistry.addElement(element, options); | ||
this.setupObserver(element, options); | ||
}; | ||
/** | ||
* Unobserve target element and remove element from static admin | ||
@@ -68,21 +143,49 @@ * | ||
* @param {HTMLElement|Window} target | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.unobserve = function (target, observerOptions, scrollableArea) { | ||
var matchingRootEntry = this._findMatchingRootEntry(observerOptions, scrollableArea); | ||
IntersectionObserverAdmin.prototype.unobserve = function (target, options) { | ||
var matchingRootEntry = this._findMatchingRootEntry(options); | ||
if (matchingRootEntry) { | ||
var intersectionObserver = matchingRootEntry.intersectionObserver, elements = matchingRootEntry.elements; | ||
var intersectionObserver = matchingRootEntry.intersectionObserver; | ||
intersectionObserver.unobserve(target); | ||
// important to do this in reverse order | ||
for (var i = elements.length - 1; i >= 0; i--) { | ||
if (elements[i] && elements[i].element === target) { | ||
elements.splice(i, 1); | ||
break; | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* register event to handle when intersection observer detects enter | ||
* | ||
* @method addEnterCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.addEnterCallback = function (element, callback) { | ||
this.addCallback(CallbackType.enter, element, callback); | ||
}; | ||
/** | ||
* register event to handle when intersection observer detects exit | ||
* | ||
* @method addExitCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.addExitCallback = function (element, callback) { | ||
this.addCallback(CallbackType.exit, element, callback); | ||
}; | ||
/** | ||
* retrieve registered callback and call with data | ||
* | ||
* @method dispatchEnterCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.dispatchEnterCallback = function (element) { | ||
this.dispatchCallback(CallbackType.enter, element); | ||
}; | ||
/** | ||
* retrieve registered callback and call with data on exit | ||
* | ||
* @method dispatchExitCallback | ||
* @public | ||
*/ | ||
IntersectionObserverAdmin.prototype.dispatchExitCallback = function (element) { | ||
this.dispatchCallback(CallbackType.exit, element); | ||
}; | ||
/** | ||
* cleanup data structures and unobserve elements | ||
@@ -94,27 +197,77 @@ * | ||
IntersectionObserverAdmin.prototype.destroy = function () { | ||
this.DOMRef = null; | ||
this.elementRegistry.destroyRegistry(); | ||
}; | ||
/** | ||
* use function composition to curry observerOptions | ||
* use function composition to curry options | ||
* | ||
* @method _setupOnIntersection | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @method setupOnIntersection | ||
* @param {Object} options | ||
*/ | ||
IntersectionObserverAdmin.prototype._setupOnIntersection = function (observerOptions, scrollableArea) { | ||
IntersectionObserverAdmin.prototype.setupOnIntersection = function (options) { | ||
var _this = this; | ||
return function (entries) { | ||
return _this._onIntersection(observerOptions, scrollableArea, entries); | ||
return function (ioEntries) { | ||
return _this.onIntersection(options, ioEntries); | ||
}; | ||
}; | ||
IntersectionObserverAdmin.prototype.setupObserver = function (element, options) { | ||
var _a; | ||
var _b = options.root, root = _b === void 0 ? window : _b; | ||
// find shared root element (window or scrollable area) | ||
// this root is responsible for coordinating it's set of elements | ||
var potentialRootMatch = this._findRoot(root); | ||
// third if there is a matching root, see if an existing entry with the same options | ||
// regardless of sort order. This is a bit of work | ||
var matchingEntryForRoot; | ||
if (potentialRootMatch) { | ||
matchingEntryForRoot = this._determineMatchingElements(options, potentialRootMatch); | ||
} | ||
// next add found entry to elements and call observer if applicable | ||
if (matchingEntryForRoot) { | ||
var elements = matchingEntryForRoot.elements, intersectionObserver = matchingEntryForRoot.intersectionObserver; | ||
elements.push(element); | ||
if (intersectionObserver) { | ||
intersectionObserver.observe(element); | ||
} | ||
} | ||
else { | ||
// otherwise start observing this element if applicable | ||
// watcher is an instance that has an observe method | ||
var intersectionObserver = this.newObserver(element, options); | ||
var observerEntry = { | ||
elements: [element], | ||
intersectionObserver: intersectionObserver, | ||
options: options | ||
}; | ||
// and add entry to WeakMap under a root element | ||
// with watcher so we can use it later on | ||
var stringifiedOptions = this._stringifyOptions(options); | ||
if (potentialRootMatch) { | ||
// if share same root and need to add new entry to root match | ||
// not functional but :shrug | ||
potentialRootMatch[stringifiedOptions] = observerEntry; | ||
} | ||
else { | ||
// no root exists, so add to WeakMap | ||
this.elementRegistry.addElement(root, (_a = {}, | ||
_a[stringifiedOptions] = observerEntry, | ||
_a)); | ||
} | ||
} | ||
}; | ||
IntersectionObserverAdmin.prototype.newObserver = function (element, options) { | ||
// No matching entry for root in static admin, thus create new IntersectionObserver instance | ||
var root = options.root, rootMargin = options.rootMargin, threshold = options.threshold; | ||
var newIO = new IntersectionObserver(this.setupOnIntersection(options).bind(this), { root: root, rootMargin: rootMargin, threshold: threshold }); | ||
newIO.observe(element); | ||
return newIO; | ||
}; | ||
/** | ||
* IntersectionObserver callback when element is intersecting viewport | ||
* | ||
* @method _onIntersection | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @method onIntersection | ||
* @param {Object} options | ||
* @param {Array} ioEntries | ||
* @private | ||
*/ | ||
IntersectionObserverAdmin.prototype._onIntersection = function (observerOptions, scrollableArea, ioEntries) { | ||
IntersectionObserverAdmin.prototype.onIntersection = function (options, ioEntries) { | ||
var _this = this; | ||
@@ -126,10 +279,7 @@ ioEntries.forEach(function (entry) { | ||
// then find entry's callback in static administration | ||
var matchingRootEntry = _this._findMatchingRootEntry(observerOptions, scrollableArea); | ||
var matchingRootEntry = _this._findMatchingRootEntry(options); | ||
if (matchingRootEntry) { | ||
matchingRootEntry.elements.some(function (obj) { | ||
if (obj.element === entry.target) { | ||
// call entry's enter callback | ||
if (obj.enterCallback) { | ||
obj.enterCallback(); | ||
} | ||
matchingRootEntry.elements.some(function (element) { | ||
if (element && element === entry.target) { | ||
_this.dispatchEnterCallback(element); | ||
return true; | ||
@@ -143,10 +293,7 @@ } | ||
// then find entry's callback in static administration | ||
var matchingRootEntry = _this._findMatchingRootEntry(observerOptions, scrollableArea); | ||
var matchingRootEntry = _this._findMatchingRootEntry(options); | ||
if (matchingRootEntry) { | ||
matchingRootEntry.elements.some(function (obj) { | ||
if (obj.element === entry.target) { | ||
// call entry's enter callback | ||
if (obj.exitCallback) { | ||
obj.exitCallback(); | ||
} | ||
matchingRootEntry.elements.some(function (element) { | ||
if (element && element === entry.target) { | ||
_this.dispatchExitCallback(element); | ||
return true; | ||
@@ -161,3 +308,3 @@ } | ||
/** | ||
* { root: { stringifiedOptions: { elements: []...] } } | ||
* { root: { stringifiedOptions: { observer, elements: []...] } } | ||
* @method _findRoot | ||
@@ -169,22 +316,19 @@ * @param {HTMLElement|Window} root | ||
IntersectionObserverAdmin.prototype._findRoot = function (root) { | ||
if (this.DOMRef) { | ||
return this.DOMRef.get(root); | ||
if (this.elementRegistry) { | ||
return this.elementRegistry.getElement(root); | ||
} | ||
}; | ||
/** | ||
* Used for onIntersection callbacks and unobserving the IntersectionObserver | ||
* We don't care about observerOptions key order because we already added | ||
* to the static administrator or found an existing IntersectionObserver with the same | ||
* root && observerOptions to reuse | ||
* We don't care about options key order because we already added | ||
* to the static administrator | ||
* | ||
* @method _findMatchingRootEntry | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @return {Object} entry with elements and other options | ||
*/ | ||
IntersectionObserverAdmin.prototype._findMatchingRootEntry = function (observerOptions, scrollableArea) { | ||
var _a = observerOptions.root, root = _a === void 0 ? window : _a; | ||
IntersectionObserverAdmin.prototype._findMatchingRootEntry = function (options) { | ||
var _a = options.root, root = _a === void 0 ? window : _a; | ||
var matchingRoot = this._findRoot(root); | ||
if (matchingRoot) { | ||
var stringifiedOptions = this._stringifyObserverOptions(observerOptions, scrollableArea); | ||
var stringifiedOptions = this._stringifyOptions(options); | ||
return matchingRoot[stringifiedOptions]; | ||
@@ -194,7 +338,7 @@ } | ||
/** | ||
* Determine if existing elements for a given root based on passed in observerOptions | ||
* Determine if existing elements for a given root based on passed in options | ||
* regardless of sort order of keys | ||
* | ||
* @method _determineMatchingElements | ||
* @param {Object} observerOptions | ||
* @param {Object} options | ||
* @param {Object} potentialRootMatch e.g. { stringifiedOptions: { elements: [], ... }, stringifiedOptions: { elements: [], ... }} | ||
@@ -204,10 +348,7 @@ * @private | ||
*/ | ||
IntersectionObserverAdmin.prototype._determineMatchingElements = function (observerOptions, potentialRootMatch) { | ||
IntersectionObserverAdmin.prototype._determineMatchingElements = function (options, potentialRootMatch) { | ||
var _this = this; | ||
if (!potentialRootMatch) { | ||
return; | ||
} | ||
var matchingKey = Object.keys(potentialRootMatch).filter(function (key) { | ||
var comparableOptions = potentialRootMatch[key].observerOptions; | ||
return _this._areOptionsSame(observerOptions, comparableOptions); | ||
var comparableOptions = potentialRootMatch[key].options; | ||
return _this._areOptionsSame(options, comparableOptions); | ||
})[0]; | ||
@@ -221,3 +362,3 @@ return potentialRootMatch[matchingKey]; | ||
* @method _areOptionsSame | ||
* @param {Object} observerOptions | ||
* @param {Object} options | ||
* @param {Object} comparableOptions | ||
@@ -227,5 +368,5 @@ * @private | ||
*/ | ||
IntersectionObserverAdmin.prototype._areOptionsSame = function (observerOptions, comparableOptions) { | ||
IntersectionObserverAdmin.prototype._areOptionsSame = function (options, comparableOptions) { | ||
// simple comparison of string, number or even null/undefined | ||
var type1 = Object.prototype.toString.call(observerOptions); | ||
var type1 = Object.prototype.toString.call(options); | ||
var type2 = Object.prototype.toString.call(comparableOptions); | ||
@@ -236,10 +377,9 @@ if (type1 !== type2) { | ||
else if (type1 !== '[object Object]' && type2 !== '[object Object]') { | ||
return observerOptions === comparableOptions; | ||
return options === comparableOptions; | ||
} | ||
// complex comparison for only type of [object Object] | ||
for (var key in observerOptions) { | ||
if (observerOptions.hasOwnProperty(key)) { | ||
for (var key in options) { | ||
if (options.hasOwnProperty(key)) { | ||
// recursion to check nested | ||
if (this._areOptionsSame(observerOptions[key], comparableOptions[key]) === | ||
false) { | ||
if (this._areOptionsSame(options[key], comparableOptions[key]) === false) { | ||
return false; | ||
@@ -252,11 +392,11 @@ } | ||
/** | ||
* Stringify observerOptions for use as a key. | ||
* Excludes observerOptions.root so that the resulting key is stable | ||
* Stringify options for use as a key. | ||
* Excludes options.root so that the resulting key is stable | ||
* | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @private | ||
* @return {String} | ||
*/ | ||
IntersectionObserverAdmin.prototype._stringifyObserverOptions = function (observerOptions, scrollableArea) { | ||
IntersectionObserverAdmin.prototype._stringifyOptions = function (options) { | ||
var scrollableArea = options.scrollableArea; | ||
var replacer = function (key, value) { | ||
@@ -268,6 +408,6 @@ if (key === 'root' && scrollableArea) { | ||
}; | ||
return JSON.stringify(observerOptions, replacer); | ||
return JSON.stringify(options, replacer); | ||
}; | ||
return IntersectionObserverAdmin; | ||
}()); | ||
}(Notifications)); | ||
@@ -274,0 +414,0 @@ return IntersectionObserverAdmin; |
@@ -1,22 +0,11 @@ | ||
declare type IndividualEntry = { | ||
element?: HTMLElement | Window; | ||
enterCallback?: Function; | ||
exitCallback?: Function; | ||
}; | ||
export interface IObserverOption { | ||
import Notifications from './notification'; | ||
export interface IOptions { | ||
root?: HTMLElement; | ||
rootMargin?: string; | ||
threshold?: number; | ||
scrollableArea?: string; | ||
[key: string]: any; | ||
} | ||
declare type RootEntry = { | ||
elements: [IndividualEntry]; | ||
observerOptions: IObserverOption; | ||
intersectionObserver: any; | ||
}; | ||
declare type PotentialRootEntry = { | ||
[stringifiedOptions: string]: RootEntry; | ||
}; | ||
export default class IntersectionObserverAdmin { | ||
private DOMRef; | ||
export default class IntersectionObserverAdmin extends Notifications { | ||
private elementRegistry; | ||
constructor(); | ||
@@ -29,9 +18,6 @@ /** | ||
* @param {HTMLElement | Window} element | ||
* @param {Function} enterCallback | ||
* @param {Function} exitCallback | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @public | ||
*/ | ||
observe(element: HTMLElement, enterCallback: Function, exitCallback: Function, observerOptions?: IObserverOption, scrollableArea?: string): void; | ||
observe(element: HTMLElement, options?: IOptions): void; | ||
/** | ||
@@ -42,8 +28,35 @@ * Unobserve target element and remove element from static admin | ||
* @param {HTMLElement|Window} target | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @public | ||
*/ | ||
unobserve(target: HTMLElement, observerOptions: IObserverOption, scrollableArea: string): void; | ||
unobserve(target: HTMLElement, options: IOptions): void; | ||
/** | ||
* register event to handle when intersection observer detects enter | ||
* | ||
* @method addEnterCallback | ||
* @public | ||
*/ | ||
addEnterCallback(element: HTMLElement | Window, callback: (data?: any) => void): void; | ||
/** | ||
* register event to handle when intersection observer detects exit | ||
* | ||
* @method addExitCallback | ||
* @public | ||
*/ | ||
addExitCallback(element: HTMLElement | Window, callback: (data?: any) => void): void; | ||
/** | ||
* retrieve registered callback and call with data | ||
* | ||
* @method dispatchEnterCallback | ||
* @public | ||
*/ | ||
dispatchEnterCallback(element: HTMLElement | Window): void; | ||
/** | ||
* retrieve registered callback and call with data on exit | ||
* | ||
* @method dispatchExitCallback | ||
* @public | ||
*/ | ||
dispatchExitCallback(element: HTMLElement | Window): void; | ||
/** | ||
* cleanup data structures and unobserve elements | ||
@@ -56,21 +69,21 @@ * | ||
/** | ||
* use function composition to curry observerOptions | ||
* use function composition to curry options | ||
* | ||
* @method _setupOnIntersection | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @method setupOnIntersection | ||
* @param {Object} options | ||
*/ | ||
protected _setupOnIntersection(observerOptions: IObserverOption, scrollableArea: string | undefined): Function; | ||
protected setupOnIntersection(options: IOptions): Function; | ||
protected setupObserver(element: HTMLElement, options: IOptions): void; | ||
private newObserver; | ||
/** | ||
* IntersectionObserver callback when element is intersecting viewport | ||
* | ||
* @method _onIntersection | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @method onIntersection | ||
* @param {Object} options | ||
* @param {Array} ioEntries | ||
* @private | ||
*/ | ||
protected _onIntersection(observerOptions: IObserverOption, scrollableArea: string | undefined, ioEntries: Array<any>): void; | ||
private onIntersection; | ||
/** | ||
* { root: { stringifiedOptions: { elements: []...] } } | ||
* { root: { stringifiedOptions: { observer, elements: []...] } } | ||
* @method _findRoot | ||
@@ -81,21 +94,18 @@ * @param {HTMLElement|Window} root | ||
*/ | ||
protected _findRoot(root: HTMLElement | Window): PotentialRootEntry | null | undefined; | ||
private _findRoot; | ||
/** | ||
* Used for onIntersection callbacks and unobserving the IntersectionObserver | ||
* We don't care about observerOptions key order because we already added | ||
* to the static administrator or found an existing IntersectionObserver with the same | ||
* root && observerOptions to reuse | ||
* We don't care about options key order because we already added | ||
* to the static administrator | ||
* | ||
* @method _findMatchingRootEntry | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @return {Object} entry with elements and other options | ||
*/ | ||
protected _findMatchingRootEntry(observerOptions: IObserverOption, scrollableArea: string | undefined): RootEntry | undefined; | ||
private _findMatchingRootEntry; | ||
/** | ||
* Determine if existing elements for a given root based on passed in observerOptions | ||
* Determine if existing elements for a given root based on passed in options | ||
* regardless of sort order of keys | ||
* | ||
* @method _determineMatchingElements | ||
* @param {Object} observerOptions | ||
* @param {Object} options | ||
* @param {Object} potentialRootMatch e.g. { stringifiedOptions: { elements: [], ... }, stringifiedOptions: { elements: [], ... }} | ||
@@ -105,3 +115,3 @@ * @private | ||
*/ | ||
protected _determineMatchingElements(observerOptions: IObserverOption, potentialRootMatch?: PotentialRootEntry): RootEntry | undefined; | ||
private _determineMatchingElements; | ||
/** | ||
@@ -112,3 +122,3 @@ * recursive method to test primitive string, number, null, etc and complex | ||
* @method _areOptionsSame | ||
* @param {Object} observerOptions | ||
* @param {Object} options | ||
* @param {Object} comparableOptions | ||
@@ -118,14 +128,12 @@ * @private | ||
*/ | ||
protected _areOptionsSame(observerOptions: IObserverOption, comparableOptions: IObserverOption): boolean; | ||
private _areOptionsSame; | ||
/** | ||
* Stringify observerOptions for use as a key. | ||
* Excludes observerOptions.root so that the resulting key is stable | ||
* Stringify options for use as a key. | ||
* Excludes options.root so that the resulting key is stable | ||
* | ||
* @param {Object} observerOptions | ||
* @param {String} scrollableArea | ||
* @param {Object} options | ||
* @private | ||
* @return {String} | ||
*/ | ||
protected _stringifyObserverOptions(observerOptions: IObserverOption, scrollableArea: string | undefined): string; | ||
private _stringifyOptions; | ||
} | ||
export {}; |
{ | ||
"name": "intersection-observer-admin", | ||
"version": "0.1.0", | ||
"version": "0.2.0", | ||
"description": "Intersection Observer Admin for better performance", | ||
@@ -62,3 +62,4 @@ "main": "dist/intersection-observer-admin.umd.js", | ||
"typescript": "^3.2.0" | ||
} | ||
}, | ||
"dependencies": {} | ||
} |
@@ -31,6 +31,6 @@ intersection-observer-admin | ||
- callback function to perform when element is leaving the viewport | ||
4. observerOptions: Function | ||
4. observerOptions: Object | ||
- list of options to pass to Intersection Observer constructor (https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/IntersectionObserver) | ||
4. scrollableArea: String | ||
- used for determining if element should use existing or new IntersectionObserver | ||
- scrollableArea: String | ||
- used for determining if element should use existing or new IntersectionObserver | ||
@@ -40,12 +40,16 @@ ```js | ||
const intersectionObserverAdmin = new IntersectionObserver(); | ||
const intersectionObserverAdmin = new IntersectionObserverAdmin(); | ||
// Add callbacks that will be called when observer detects entering and leaving viewport | ||
intersectionObserverAdmin.addEnterCallback(element, enterCallback); | ||
intersectionObserverAdmin.addExitCallback(element, exitCallback); | ||
// add an element to static administrator | ||
intersectionObserverAdmin.observe(element, enterCallback, exitCallback, { root, rootMargin: '0px 0px 100px 0px', threshold: 0 }); | ||
intersectionObserverAdmin.observe(element, { root, rootMargin: '0px 0px 100px 0px', threshold: 0 }); | ||
// add an element in a scrolling container | ||
intersectionObserverAdmin.add(element, enterCallback, exitCallback, { root, rootMargin: '0px 0px 100px 0px', threshold: 0 }, '.my-list'); | ||
intersectionObserverAdmin.add(element, { root, rootMargin: '0px 0px 100px 0px', threshold: 0, scrollableArea: '.my-list' }); | ||
// Use in cleanup lifecycle hooks (if applicable) from the element being observed | ||
intersectionObserverAdmin.unobserve(element, observerOptions, scrollableArea); | ||
intersectionObserverAdmin.unobserve(element, observerOptions); | ||
@@ -52,0 +56,0 @@ // Use in cleanup lifecycle hooks of your application as a whole |
@@ -1,5 +0,19 @@ | ||
describe('format number', () => { | ||
it('ok', () => { | ||
expect(true).toBe(true); | ||
import IntersectionObserverAdmin from '../src/index'; | ||
describe('add entry', () => { | ||
it('observe exists', () => { | ||
const el = document.createElement('div'); | ||
const ioAdmin = new IntersectionObserverAdmin(); | ||
expect(ioAdmin.observe).toBeDefined(); | ||
expect(ioAdmin.unobserve).toBeDefined(); | ||
}); | ||
it('callbacks', () => { | ||
const el = document.createElement('div'); | ||
const ioAdmin = new IntersectionObserverAdmin(); | ||
expect(ioAdmin.addEnterCallback).toBeDefined(); | ||
expect(ioAdmin.addExitCallback).toBeDefined(); | ||
expect(ioAdmin.dispatchEnterCallback).toBeDefined(); | ||
expect(ioAdmin.dispatchExitCallback).toBeDefined(); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
72524
20
1577
104
1