UNPKG

@stated-library/core

Version:

Core functionality for [`Stated Libraries`](https://github.com/bradfordlemley/stated-library)

408 lines (347 loc) 10.8 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } var throttle = _interopDefault(require('lodash/throttle')); function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } /* getValue: when an observable is fed by other observables, it might unsubscribe from those observables if it doesn't have any subscriptions itself. in that case, it won't be updationg "value", so value could be invalid if there are no subscriptions. getValue supports this case by allowing the observable to temporarily subscribe to the feeding observables in order to get the proper value. */ function createObservable(initialValue, opts) { var observers = []; var value = initialValue; var _Object$assign = Object.assign({}, opts), onSubscribe = _Object$assign.onSubscribe, onUnsubscribe = _Object$assign.onUnsubscribe, getValue = _Object$assign.getValue; return _defineProperty({ get value() { return getValue ? getValue() : value; }, subscribe: function subscribe(observer) { if (observers.length === 0 && onSubscribe) { onSubscribe(); } if (typeof observer === 'function') { observer = { next: observer }; } observers.push(observer); // behave like a BehaviorSubject and emit the current value // upon subscription // most reactive operations will need this behavior // in cases (like React) where the initial value is retrieved before // subscribing, the subscriber will get the same value upon subscribing, // assuming it hasn't changed, so this behavior should result in minimal // overhead observer.next(this.value); return { unsubscribe: function unsubscribe() { observers = observers.filter(function (l) { return l !== observer; }); if (observers.length === 0 && onUnsubscribe) { onUnsubscribe(); } } }; }, next: function next(nextValue) { value = nextValue; observers.map(function (obs) { return obs.next(value); }); } }, Symbol && Symbol.observable || '@@observable', function () { return this; }); } function isArray(obj) { return Object.prototype.toString.call(obj) === '[object Array]'; } function getValue(value$) { if (value$.hasOwnProperty('value')) { return value$.value; } var initialValue = undefined; var sub = value$.subscribe(function (v) { return initialValue = v; }); sub.unsubscribe(); return initialValue; } function getValueOrValues(streamOrStreams) { if (isArray(streamOrStreams)) { var values = []; streamOrStreams.map(function (s, i) { values[i] = getValue(s); }); return values; } return getValue(streamOrStreams); } // copied from react-redux var hasOwn = Object.prototype.hasOwnProperty; function is(x, y) { if (x === y) { return x !== 0 || y !== 0 || 1 / x === 1 / y; } else { return x !== x && y !== y; } } function shallowEqual(objA, objB) { if (is(objA, objB)) return true; if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { return false; } var keysA = Object.keys(objA); var keysB = Object.keys(objB); if (keysA.length !== keysB.length) return false; for (var i = 0; i < keysA.length; i++) { if (!hasOwn.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { return false; } } return true; } function getMappedState(stateOrStates, mapState) { return mapState(stateOrStates); } function mapState(streamOrStreams, mapState, opts) { var valueOrValues; var value; var subscriptions; var isSubscribed = false; var iOpts = Object.assign({}, opts); function updateValue() { var updateSourceValues = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; if (updateSourceValues) { valueOrValues = getValueOrValues(streamOrStreams); } var newValue = getMappedState(valueOrValues, mapState); if (!shallowEqual(newValue, value)) { value = newValue; } return value; } var mapped$ = createObservable(updateValue(true), { onUnsubscribe: function onUnsubscribe() { subscriptions.map(function (sub) { return sub.unsubscribe(); }); isSubscribed = false; }, onSubscribe: function onSubscribe() { updateValue(true); if (isArray(streamOrStreams)) { subscriptions = streamOrStreams.map(function (s, i) { valueOrValues[i] = s.value; return s.subscribe(function (val) { valueOrValues[i] = val; update(); }); }); } else { subscriptions = [streamOrStreams.subscribe(function (value) { valueOrValues = value; update(); })]; } isSubscribed = true; }, getValue: function getValue() { return isSubscribed ? value : updateValue(true); } }); function notify(newValue) { if (iOpts.async) { setTimeout(function () { if (newValue === value) { mapped$.next(value); } }, 0); } else { mapped$.next(value); } } function update() { var oldValue = value; var newValue = updateValue(); if (newValue !== oldValue) { notify(newValue); } } return mapped$; } function createMultiConnector(opts) { var onState = opts.onState, onStateEvent = opts.onStateEvent, onConnectLib = opts.onConnectLib, onDisconnectLib = opts.onDisconnectLib; var stateSubs = {}; var stateEventSubs = {}; return { connectedLibs: {}, connect: function connect(lib, key) { var _this = this; if (this.connectedLibs[key]) { throw new Error("A library is already connected with key: ".concat(key)); } var partialSub = { disconnect: function disconnect() { onDisconnectLib && onDisconnectLib(key, lib); delete _this.connectedLibs[key]; stateSubs[key].unsubscribe(); delete stateSubs[key]; } }; var sub = onConnectLib ? onConnectLib(key, lib, partialSub) : partialSub; if (onState) { stateSubs[key] = lib.state$.subscribe(function (state) { return onState(state, key, lib); }); } if (onStateEvent) { stateEventSubs[key] = lib.stateEvent$.subscribe(function (stateEvent) { return onStateEvent(stateEvent, key, lib); }); } this.connectedLibs[key] = lib; return sub; }, disconnect: function disconnect() { var _this2 = this; Object.keys(this.connectedLibs).map(function (key) { if (stateSubs[key]) { stateSubs[key].unsubscribe(); delete stateSubs[key]; } if (stateEventSubs[key]) { stateEventSubs[key].unsubscribe(); delete stateEventSubs[key]; } delete _this2.connectedLibs[key]; }); } }; } var DEVTOOLS_SET_EVENT = '**__DEVTOOLS__**'; function createDevToolsConnector() { var devTools; var combinedState = {}; var multi; // https://extension.remotedev.io/docs/API/Methods.html // @ts-ignore if (window.__REDUX_DEVTOOLS_EXTENSION__) { // @ts-ignore devTools = window.__REDUX_DEVTOOLS_EXTENSION__.connect({ name: 'StatedLibraries' }); devTools.subscribe(function (message) { if (message.type === 'DISPATCH' && message.state) { console.log('DevTools requested to change the state to', message.state); var libStates = JSON.parse(message.state); Object.keys(libStates).forEach(function (lib) { return multi.connectedLibs[lib].resetState(libStates[lib], DEVTOOLS_SET_EVENT); }); } }); devTools.init(combinedState); } multi = createMultiConnector({ onStateEvent: function onStateEvent(stateEvent, key) { var rawState = stateEvent.rawState, event = stateEvent.event; combinedState[key] = rawState; if (event === DEVTOOLS_SET_EVENT) { return; } devTools && devTools.send("".concat(key, "::").concat(event), combinedState); } }); return multi; } var devTools = createDevToolsConnector(); function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } function getStateFromLocalStorage(name) { try { var data = localStorage.getItem(name); return data ? JSON.parse(data) : undefined; } catch (_unused) { return undefined; } } function createLocalStorageConnector() { var writeFns = {}; var multi = createMultiConnector({ onConnectLib: function onConnectLib(key, lib, sub) { var initial = getStateFromLocalStorage(key); if (initial != null) { lib.resetState(initial, 'RESET_FROM_LOCAL_STATE'); } var writeState = function writeState(state) { localStorage.setItem(key, JSON.stringify(state)); }; writeFns[key] = throttle(writeState, 300, { leading: true, trailing: true }); return _objectSpread({}, sub, { clear: function clear() { return localStorage.removeItem(key); } }); }, onStateEvent: function onStateEvent(stateEvent, key) { var rawState = stateEvent.rawState; writeFns[key](rawState); } }); return _objectSpread({}, multi, { clear: function clear() { Object.keys(this.connectedLibs).map(function (key) { localStorage.removeItem(key); }); } }); } var locStorage = createLocalStorageConnector(); exports.createObservable = createObservable; exports.isArray = isArray; exports.getValue = getValue; exports.getValueOrValues = getValueOrValues; exports.shallowEqual = shallowEqual; exports.mapState = mapState; exports.createDevToolsConnector = createDevToolsConnector; exports.devTools = devTools; exports.createLocalStorageConnector = createLocalStorageConnector; exports.locStorage = locStorage; //# sourceMappingURL=index.development.js.map