this repo has no description
1var Registry = /** @class */ (function () {
2 function Registry() {
3 this.registry = new WeakMap();
4 }
5 Registry.prototype.elementExists = function (elem) {
6 return this.registry.has(elem);
7 };
8 Registry.prototype.getElement = function (elem) {
9 return this.registry.get(elem);
10 };
11 /**
12 * administrator for lookup in the future
13 *
14 * @method add
15 * @param {HTMLElement | Window} element - the item to add to root element registry
16 * @param {IOption} options
17 * @param {IOption.root} [root] - contains optional root e.g. window, container div, etc
18 * @param {IOption.watcher} [observer] - optional
19 * @public
20 */
21 Registry.prototype.addElement = function (element, options) {
22 if (!element) {
23 return;
24 }
25 this.registry.set(element, options || {});
26 };
27 /**
28 * @method remove
29 * @param {HTMLElement|Window} target
30 * @public
31 */
32 Registry.prototype.removeElement = function (target) {
33 this.registry.delete(target);
34 };
35 /**
36 * reset weak map
37 *
38 * @method destroy
39 * @public
40 */
41 Registry.prototype.destroyRegistry = function () {
42 this.registry = new WeakMap();
43 };
44 return Registry;
45}());
46
47var noop = function () { };
48var CallbackType;
49(function (CallbackType) {
50 CallbackType["enter"] = "enter";
51 CallbackType["exit"] = "exit";
52})(CallbackType || (CallbackType = {}));
53var Notifications = /** @class */ (function () {
54 function Notifications() {
55 this.registry = new Registry();
56 }
57 /**
58 * Adds an EventListener as a callback for an event key.
59 * @param type 'enter' or 'exit'
60 * @param key The key of the event
61 * @param callback The callback function to invoke when the event occurs
62 */
63 Notifications.prototype.addCallback = function (type, element, callback) {
64 var _a, _b;
65 var entry;
66 if (type === CallbackType.enter) {
67 entry = (_a = {}, _a[CallbackType.enter] = callback, _a);
68 }
69 else {
70 entry = (_b = {}, _b[CallbackType.exit] = callback, _b);
71 }
72 this.registry.addElement(element, Object.assign({}, this.registry.getElement(element), entry));
73 };
74 /**
75 * @hidden
76 * Executes registered callbacks for key.
77 * @param type
78 * @param element
79 * @param data
80 */
81 Notifications.prototype.dispatchCallback = function (type, element, data) {
82 if (type === CallbackType.enter) {
83 var _a = this.registry.getElement(element).enter, enter = _a === void 0 ? noop : _a;
84 enter(data);
85 }
86 else {
87 // no element in WeakMap possible because element may be removed from DOM by the time we get here
88 var found = this.registry.getElement(element);
89 if (found && found.exit) {
90 found.exit(data);
91 }
92 }
93 };
94 return Notifications;
95}());
96
97var __extends = (undefined && undefined.__extends) || (function () {
98 var extendStatics = function (d, b) {
99 extendStatics = Object.setPrototypeOf ||
100 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
101 function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
102 return extendStatics(d, b);
103 };
104 return function (d, b) {
105 if (typeof b !== "function" && b !== null)
106 throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
107 extendStatics(d, b);
108 function __() { this.constructor = d; }
109 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
110 };
111})();
112var __assign = (undefined && undefined.__assign) || function () {
113 __assign = Object.assign || function(t) {
114 for (var s, i = 1, n = arguments.length; i < n; i++) {
115 s = arguments[i];
116 for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
117 t[p] = s[p];
118 }
119 return t;
120 };
121 return __assign.apply(this, arguments);
122};
123var IntersectionObserverAdmin = /** @class */ (function (_super) {
124 __extends(IntersectionObserverAdmin, _super);
125 function IntersectionObserverAdmin() {
126 var _this = _super.call(this) || this;
127 _this.elementRegistry = new Registry();
128 return _this;
129 }
130 /**
131 * Adds element to observe via IntersectionObserver and stores element + relevant callbacks and observer options in static
132 * administrator for lookup in the future
133 *
134 * @method observe
135 * @param {HTMLElement | Window} element
136 * @param {Object} options
137 * @public
138 */
139 IntersectionObserverAdmin.prototype.observe = function (element, options) {
140 if (options === void 0) { options = {}; }
141 if (!element) {
142 return;
143 }
144 this.elementRegistry.addElement(element, __assign({}, options));
145 this.setupObserver(element, __assign({}, options));
146 };
147 /**
148 * Unobserve target element and remove element from static admin
149 *
150 * @method unobserve
151 * @param {HTMLElement|Window} target
152 * @param {Object} options
153 * @public
154 */
155 IntersectionObserverAdmin.prototype.unobserve = function (target, options) {
156 var matchingRootEntry = this.findMatchingRootEntry(options);
157 if (matchingRootEntry) {
158 var intersectionObserver = matchingRootEntry.intersectionObserver;
159 intersectionObserver.unobserve(target);
160 }
161 };
162 /**
163 * register event to handle when intersection observer detects enter
164 *
165 * @method addEnterCallback
166 * @public
167 */
168 IntersectionObserverAdmin.prototype.addEnterCallback = function (element, callback) {
169 this.addCallback(CallbackType.enter, element, callback);
170 };
171 /**
172 * register event to handle when intersection observer detects exit
173 *
174 * @method addExitCallback
175 * @public
176 */
177 IntersectionObserverAdmin.prototype.addExitCallback = function (element, callback) {
178 this.addCallback(CallbackType.exit, element, callback);
179 };
180 /**
181 * retrieve registered callback and call with data
182 *
183 * @method dispatchEnterCallback
184 * @public
185 */
186 IntersectionObserverAdmin.prototype.dispatchEnterCallback = function (element, entry) {
187 this.dispatchCallback(CallbackType.enter, element, entry);
188 };
189 /**
190 * retrieve registered callback and call with data on exit
191 *
192 * @method dispatchExitCallback
193 * @public
194 */
195 IntersectionObserverAdmin.prototype.dispatchExitCallback = function (element, entry) {
196 this.dispatchCallback(CallbackType.exit, element, entry);
197 };
198 /**
199 * cleanup data structures and unobserve elements
200 *
201 * @method destroy
202 * @public
203 */
204 IntersectionObserverAdmin.prototype.destroy = function () {
205 this.elementRegistry.destroyRegistry();
206 };
207 /**
208 * use function composition to curry options
209 *
210 * @method setupOnIntersection
211 * @param {Object} options
212 */
213 IntersectionObserverAdmin.prototype.setupOnIntersection = function (options) {
214 var _this = this;
215 return function (ioEntries) {
216 return _this.onIntersection(options, ioEntries);
217 };
218 };
219 IntersectionObserverAdmin.prototype.setupObserver = function (element, options) {
220 var _a;
221 var _b = options.root, root = _b === void 0 ? window : _b;
222 // First - find shared root element (window or target HTMLElement)
223 // this root is responsible for coordinating it's set of elements
224 var potentialRootMatch = this.findRootFromRegistry(root);
225 // Second - if there is a matching root, see if an existing entry with the same options
226 // regardless of sort order. This is a bit of work
227 var matchingEntryForRoot;
228 if (potentialRootMatch) {
229 matchingEntryForRoot = this.determineMatchingElements(options, potentialRootMatch);
230 }
231 // next add found entry to elements and call observer if applicable
232 if (matchingEntryForRoot) {
233 var elements = matchingEntryForRoot.elements, intersectionObserver = matchingEntryForRoot.intersectionObserver;
234 elements.push(element);
235 if (intersectionObserver) {
236 intersectionObserver.observe(element);
237 }
238 }
239 else {
240 // otherwise start observing this element if applicable
241 // watcher is an instance that has an observe method
242 var intersectionObserver = this.newObserver(element, options);
243 var observerEntry = {
244 elements: [element],
245 intersectionObserver: intersectionObserver,
246 options: options
247 };
248 // and add entry to WeakMap under a root element
249 // with watcher so we can use it later on
250 var stringifiedOptions = this.stringifyOptions(options);
251 if (potentialRootMatch) {
252 // if share same root and need to add new entry to root match
253 // not functional but :shrug
254 potentialRootMatch[stringifiedOptions] = observerEntry;
255 }
256 else {
257 // no root exists, so add to WeakMap
258 this.elementRegistry.addElement(root, (_a = {},
259 _a[stringifiedOptions] = observerEntry,
260 _a));
261 }
262 }
263 };
264 IntersectionObserverAdmin.prototype.newObserver = function (element, options) {
265 // No matching entry for root in static admin, thus create new IntersectionObserver instance
266 var root = options.root, rootMargin = options.rootMargin, threshold = options.threshold;
267 var newIO = new IntersectionObserver(this.setupOnIntersection(options).bind(this), { root: root, rootMargin: rootMargin, threshold: threshold });
268 newIO.observe(element);
269 return newIO;
270 };
271 /**
272 * IntersectionObserver callback when element is intersecting viewport
273 * either when `isIntersecting` changes or `intersectionRadio` crosses on of the
274 * configured `threshold`s.
275 * Exit callback occurs eagerly (when element is initially out of scope)
276 * See https://stackoverflow.com/questions/53214116/intersectionobserver-callback-firing-immediately-on-page-load/53385264#53385264
277 *
278 * @method onIntersection
279 * @param {Object} options
280 * @param {Array} ioEntries
281 * @private
282 */
283 IntersectionObserverAdmin.prototype.onIntersection = function (options, ioEntries) {
284 var _this = this;
285 ioEntries.forEach(function (entry) {
286 var isIntersecting = entry.isIntersecting, intersectionRatio = entry.intersectionRatio;
287 var threshold = options.threshold || 0;
288 if (Array.isArray(threshold)) {
289 threshold = threshold[threshold.length - 1];
290 }
291 // then find entry's callback in static administration
292 var matchingRootEntry = _this.findMatchingRootEntry(options);
293 // first determine if entry intersecting
294 if (isIntersecting || intersectionRatio > threshold) {
295 if (matchingRootEntry) {
296 matchingRootEntry.elements.some(function (element) {
297 if (element && element === entry.target) {
298 _this.dispatchEnterCallback(element, entry);
299 return true;
300 }
301 return false;
302 });
303 }
304 }
305 else {
306 if (matchingRootEntry) {
307 matchingRootEntry.elements.some(function (element) {
308 if (element && element === entry.target) {
309 _this.dispatchExitCallback(element, entry);
310 return true;
311 }
312 return false;
313 });
314 }
315 }
316 });
317 };
318 /**
319 * { root: { stringifiedOptions: { observer, elements: []...] } }
320 * @method findRootFromRegistry
321 * @param {HTMLElement|Window} root
322 * @private
323 * @return {Object} of elements that share same root
324 */
325 IntersectionObserverAdmin.prototype.findRootFromRegistry = function (root) {
326 if (this.elementRegistry) {
327 return this.elementRegistry.getElement(root);
328 }
329 };
330 /**
331 * We don't care about options key order because we already added
332 * to the static administrator
333 *
334 * @method findMatchingRootEntry
335 * @param {Object} options
336 * @return {Object} entry with elements and other options
337 */
338 IntersectionObserverAdmin.prototype.findMatchingRootEntry = function (options) {
339 var _a = options.root, root = _a === void 0 ? window : _a;
340 var matchingRoot = this.findRootFromRegistry(root);
341 if (matchingRoot) {
342 var stringifiedOptions = this.stringifyOptions(options);
343 return matchingRoot[stringifiedOptions];
344 }
345 };
346 /**
347 * Determine if existing elements for a given root based on passed in options
348 * regardless of sort order of keys
349 *
350 * @method determineMatchingElements
351 * @param {Object} options
352 * @param {Object} potentialRootMatch e.g. { stringifiedOptions: { elements: [], ... }, stringifiedOptions: { elements: [], ... }}
353 * @private
354 * @return {Object} containing array of elements and other meta
355 */
356 IntersectionObserverAdmin.prototype.determineMatchingElements = function (options, potentialRootMatch) {
357 var _this = this;
358 var matchingStringifiedOptions = Object.keys(potentialRootMatch).filter(function (key) {
359 var comparableOptions = potentialRootMatch[key].options;
360 return _this.areOptionsSame(options, comparableOptions);
361 })[0];
362 return potentialRootMatch[matchingStringifiedOptions];
363 };
364 /**
365 * recursive method to test primitive string, number, null, etc and complex
366 * object equality.
367 *
368 * @method areOptionsSame
369 * @param {any} a
370 * @param {any} b
371 * @private
372 * @return {boolean}
373 */
374 IntersectionObserverAdmin.prototype.areOptionsSame = function (a, b) {
375 if (a === b) {
376 return true;
377 }
378 // simple comparison
379 var type1 = Object.prototype.toString.call(a);
380 var type2 = Object.prototype.toString.call(b);
381 if (type1 !== type2) {
382 return false;
383 }
384 else if (type1 !== '[object Object]' && type2 !== '[object Object]') {
385 return a === b;
386 }
387 if (a && b && typeof a === 'object' && typeof b === 'object') {
388 // complex comparison for only type of [object Object]
389 for (var key in a) {
390 if (Object.prototype.hasOwnProperty.call(a, key)) {
391 // recursion to check nested
392 if (this.areOptionsSame(a[key], b[key]) === false) {
393 return false;
394 }
395 }
396 }
397 }
398 // if nothing failed
399 return true;
400 };
401 /**
402 * Stringify options for use as a key.
403 * Excludes options.root so that the resulting key is stable
404 *
405 * @param {Object} options
406 * @private
407 * @return {String}
408 */
409 IntersectionObserverAdmin.prototype.stringifyOptions = function (options) {
410 var root = options.root;
411 var replacer = function (key, value) {
412 if (key === 'root' && root) {
413 var classList = Array.prototype.slice.call(root.classList);
414 var classToken = classList.reduce(function (acc, item) {
415 return (acc += item);
416 }, '');
417 var id = root.id;
418 return "".concat(id, "-").concat(classToken);
419 }
420 return value;
421 };
422 return JSON.stringify(options, replacer);
423 };
424 return IntersectionObserverAdmin;
425}(Notifications));
426
427export default IntersectionObserverAdmin;
428//# sourceMappingURL=intersection-observer-admin.es5.js.map