Last active
October 7, 2019 20:39
-
-
Save andreineculau/d892eeb40ac44d6d8446c659b130295e to your computer and use it in GitHub Desktop.
original, simpler, proper --- typing isn't everything
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://github.com/m59peacemaker/browser-insular-observer | |
// | |
// Usage: | |
// let observe = InsularObserver(MutationObserver); | |
// observe(document.documentElement, options, function(documentElementEntry) {...}); | |
// | |
import keyMaster = require('key-master') | |
interface Observer<Target, Options> { | |
observe(target: Target, options?: Options): void | |
unobserve?(target: Target): void | |
} | |
interface ObserverEntry<Target> { | |
target: Target | |
} | |
type ObserverCallback<Target, Entry extends ObserverEntry<Target>> = (entries: Entry[]) => void | |
type ObserverClass<Target, Options, Entry extends ObserverEntry<Target>> = ( | |
new(callback: ObserverCallback<Target, Entry>, options?: Options) => Observer<Target, Options> | |
) | |
type TargetListener<Target, Entry extends ObserverEntry<Target>> = (entry: Entry) => void | |
interface ObserveCallback< | |
Target, | |
Options, | |
Entry extends ObserverEntry<Target>, | |
Listener extends TargetListener<Target, Entry>, | |
> { | |
(target: Target, listener: Listener): UnobserveCallback | |
(target: Target, options: Options, listener: Listener): UnobserveCallback | |
} | |
type UnobserveCallback = () => void | |
const InsularObserver = < | |
Target extends object, | |
Options, | |
Entry extends ObserverEntry<Target>, | |
Listener extends TargetListener<Target, Entry>, | |
>( | |
ObserverConstructor: ObserverClass<Target, Options, Entry>, | |
options?: Options, | |
): ObserveCallback<Target, Options, Entry, Listener> => { | |
const listeners = keyMaster<Target, Listener[]>(() => [], new WeakMap<Target, Listener[]>()) | |
const callback = (entries: Entry[]) => { | |
entries.forEach(entry => { | |
const targetListeners = listeners.get(entry.target) | |
targetListeners.forEach(listener => listener(entry)) | |
}) | |
} | |
const observer = new ObserverConstructor(callback, options) | |
function observe ( | |
target: Target, | |
observeOptions: undefined | Options | Listener, | |
listener?: Listener, | |
): UnobserveCallback { | |
if (typeof observeOptions === 'function') { | |
listener = observeOptions as Listener | |
observeOptions = undefined | |
} | |
const targetListeners = listeners.get(target) | |
targetListeners.push(listener as Listener) | |
observer.observe(target, observeOptions) | |
return function unobserve (): void { | |
const idx = targetListeners.indexOf(listener as Listener) | |
targetListeners.splice(idx , 1) | |
if (targetListeners.length === 0) { | |
listeners.delete(target) | |
return observer.unobserve && observer.unobserve(target) | |
} | |
} | |
} | |
return observe | |
} | |
export = InsularObserver |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 1.original.ts transpiled via https://www.typescriptlang.org/play | |
import keyMaster = require('key-master'); | |
export const InsularObserver = (ObserverConstructor, options) => { | |
const listeners = keyMaster(() => [], new WeakMap()); | |
const callback = (entries) => { | |
entries.forEach(entry => { | |
const targetListeners = listeners.get(entry.target); | |
targetListeners.forEach(listener => listener(entry)); | |
}); | |
}; | |
const observer = new ObserverConstructor(callback, options); | |
function observe(target, observeOptions, listener) { | |
if (typeof observeOptions === 'function') { | |
listener = observeOptions; | |
observeOptions = undefined; | |
} | |
const targetListeners = listeners.get(target); | |
targetListeners.push(listener); | |
observer.observe(target, observeOptions); | |
return function unobserve() { | |
const idx = targetListeners.indexOf(listener); | |
targetListeners.splice(idx, 1); | |
if (targetListeners.length === 0) { | |
listeners.delete(target); | |
return observer.unobserve && observer.unobserve(target); | |
} | |
}; | |
} | |
return observe; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// rewritten | |
// uses _.memoize - makes no assumption on the type of entries | |
// uses _.isMatch - makes no assumption on the type of entries, nor observe() signature | |
// no assumption on observe() signature, neither argument count, nor argument type | |
// no assumption over constructor signature (create a class with hardcoded constructor arguments, if needed) | |
// | |
// Usage: | |
// let observer = reuseObserver(MutationObserver); | |
// canonical form: | |
// observer.observe({element: document.documentElement}, function(documentElementEntries) {...}, document.documentElement); | |
// or convenience form: | |
// observer.observe(document.documentElement, function(documentElementEntries) {...}); | |
// | |
// let observer = reuseObserver(IntervalObserver); | |
// observer.observe({interval: 1000}, function(everySecondEntries) {...}, {interval: 1000}); | |
// | |
import _ from 'lodash'; | |
let _observerIsElementObserver = function(observer) { | |
if (observer instanceof IntersectionObserver) { | |
return true; | |
} | |
if (observer instanceof MutationObserver) { | |
return true; | |
} | |
if (observer instanceof ResizeObserver) { | |
return true; | |
} | |
return false; | |
}; | |
export let reuseObserver = function(Observer, isEntryMatch = _.isMatch) { | |
let matchListenersPairs = []; // [{match, listeners}] | |
let cb = function(entries) { | |
_.forEach(matchListenersPairs, function({match, listeners}) { | |
let matchingEntries = _.filter(entries, function(entry) { | |
return isEntryMatch(entry, match); | |
}); | |
_.forEach(listeners, function(listener) { | |
listener(matchingEntries); | |
}); | |
}); | |
}; | |
let observer = new Observer(cb); | |
let originalObserve = observer.observe.bind(observer); | |
observer.observe = function(match, listener, ...observeArgs) { | |
// convenience for known element observers | |
if (_observerIsElementObserver(observer)) { | |
if (_.isElement(match)) { | |
match = { | |
target: match | |
}; | |
} | |
if (!_.isElement(observeArgs[0])) { | |
observeArgs.unshift(match.target); | |
} | |
} | |
let matchListenersPair = _.find(matchListenersPairs, { | |
match | |
}); | |
if (_.isUndefined(matchListenersPair)) { | |
matchListenersPair = { | |
match, | |
listeners: [] | |
}; | |
matchListenersPairs.push(matchListenersPair); | |
} | |
matchListenersPair.listener.push(listener); | |
originalObserve(...observeArgs); | |
}; | |
return observe; | |
}; | |
reuseObserver = _.memoize(reuseObserver, function(Observer, isEntryMatch) { | |
return Observer.toString() + isEntryMatch.toString(); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment