diff --git a/dist/simple-observable-proxy.js b/dist/simple-observable-proxy.js index c1e0d13..28ebd61 100644 --- a/dist/simple-observable-proxy.js +++ b/dist/simple-observable-proxy.js @@ -1,4 +1,4 @@ -/*! simple observable proxy v1.0.0 | MIT License | © 2022 Aleph1 Technologies Inc */ +/*! simple observable proxy v1.1.0 | MIT License | © 2022 Aleph1 Technologies Inc */ const observables = new Set(), observablesByProxy = new Map(), observersByProxy = new Map(), @@ -9,7 +9,9 @@ const observables = new Set(), observersByProxy.get(proxy).forEach((callback) => callback()); }), proxiesToNotify.clear(), - window.requestAnimationFrame(tick); + "undefined" != typeof window && window.requestAnimationFrame + ? window.requestAnimationFrame(tick) + : setTimeout(tick, 16); }, makeObservableProxy = (data, rootProxy) => { if (observables.has(data)) diff --git a/dist/simple-observable-proxy.min.js b/dist/simple-observable-proxy.min.js index 1b67508..5c7845f 100644 --- a/dist/simple-observable-proxy.min.js +++ b/dist/simple-observable-proxy.min.js @@ -1,2 +1,2 @@ -/*! simple observable proxy v1.0.0 | MIT License | © 2022 Aleph1 Technologies Inc */ -const e=new Set,r=new Map,t=new Map,o=new Set,n={}.constructor,a=()=>{o.forEach((e=>{t.get(e).forEach((e=>e()))})),o.clear(),window.requestAnimationFrame(a)},s=(a,c)=>{if(e.has(a))throw new Error("Can’t observe Object or Array again");if(r.has(a))throw new Error("rootProxy isn’t an observable");let i;const y=new Proxy(a,{get:(e,r)=>e[r],set(e,t,n){if(e[t]!==n){if(i){if(r.has(n))throw new Error("Can’t nest observables");o.add(c||y)}e[t]=n}return!0},deleteProperty:(e,r)=>r in e&&(delete e[r],o.add(c||y),!0)});return(Array.isArray(a)?[...Array(a.length).keys()]:Object.keys(a)).forEach((e=>{const t=a[e];if(r.has(t))throw new Error("Can’t nest observables");(e=>Array.isArray(e)||(e=>!!e&&"object"==typeof e&&e.constructor===n)(e))(t)&&(y[e]=s(t,c||y))})),i=new Set,e.add(a),r.set(y,a),t.set(y,i),y},c=e=>(e=>Array.isArray(e)||(e=>!!e&&"object"==typeof e&&e.constructor===n)(e))(e)&&s(e),i=(e,r)=>{const o=t.get(e);return!(!o||"function"!=typeof r)&&o.add(r)},y=(e,r)=>{const o=t.get(e);return!(!o||"function"!=typeof r)&&o.delete(r)},d=n=>{const a=t.get(n);return!!a&&(a.clear(),e.delete(r.get(n)),r.delete(n),t.delete(n),o.delete(n),!0)};a();export{d as destroy,c as observable,i as observe,y as unobserve}; +/*! simple observable proxy v1.1.0 | MIT License | © 2022 Aleph1 Technologies Inc */ +const e=new Set,r=new Map,t=new Map,o=new Set,n={}.constructor,a=()=>{o.forEach((e=>{t.get(e).forEach((e=>e()))})),o.clear(),"undefined"!=typeof window&&window.requestAnimationFrame?window.requestAnimationFrame(a):setTimeout(a,16)},s=(a,c)=>{if(e.has(a))throw new Error("Can’t observe Object or Array again");if(r.has(a))throw new Error("rootProxy isn’t an observable");let i;const d=new Proxy(a,{get:(e,r)=>e[r],set(e,t,n){if(e[t]!==n){if(i){if(r.has(n))throw new Error("Can’t nest observables");o.add(c||d)}e[t]=n}return!0},deleteProperty:(e,r)=>r in e&&(delete e[r],o.add(c||d),!0)});return(Array.isArray(a)?[...Array(a.length).keys()]:Object.keys(a)).forEach((e=>{const t=a[e];if(r.has(t))throw new Error("Can’t nest observables");(e=>Array.isArray(e)||(e=>!!e&&"object"==typeof e&&e.constructor===n)(e))(t)&&(d[e]=s(t,c||d))})),i=new Set,e.add(a),r.set(d,a),t.set(d,i),d},c=e=>(e=>Array.isArray(e)||(e=>!!e&&"object"==typeof e&&e.constructor===n)(e))(e)&&s(e),i=(e,r)=>{const o=t.get(e);return!(!o||"function"!=typeof r)&&o.add(r)},d=(e,r)=>{const o=t.get(e);return!(!o||"function"!=typeof r)&&o.delete(r)},w=n=>{const a=t.get(n);return!!a&&(a.clear(),e.delete(r.get(n)),r.delete(n),t.delete(n),o.delete(n),!0)};a();export{w as destroy,c as observable,i as observe,d as unobserve}; diff --git a/lib/index.js b/lib/index.js index 6a1a16b..1f9cc37 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,3 +1,6 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.destroy = exports.unobserve = exports.observe = exports.observable = void 0; // data that is being observed const observables = new Set(); // original data for each proxy @@ -14,7 +17,10 @@ const tick = () => { observersByProxy.get(proxy).forEach((callback) => callback()); }); proxiesToNotify.clear(); - window.requestAnimationFrame(tick); + if (typeof window !== "undefined" && window.requestAnimationFrame) + window.requestAnimationFrame(tick); + else + setTimeout(tick, 16); }; const makeObservableProxy = (data, rootProxy) => { if (observables.has(data)) @@ -59,18 +65,21 @@ const makeObservableProxy = (data, rootProxy) => { observersByProxy.set(proxy, observers); return proxy; }; -export const observable = (data) => { +const observable = (data) => { return /*#__INLINE__*/ canBeObservable(data) && makeObservableProxy(data); }; -export const observe = (observableProxy, callback) => { +exports.observable = observable; +const observe = (observableProxy, callback) => { const observers = observersByProxy.get(observableProxy); return observers && typeof callback === 'function' ? observers.add(callback) : false; }; -export const unobserve = (observableProxy, callback) => { +exports.observe = observe; +const unobserve = (observableProxy, callback) => { const observers = observersByProxy.get(observableProxy); return observers && typeof callback === 'function' ? observers.delete(callback) : false; }; -export const destroy = (observableProxy) => { +exports.unobserve = unobserve; +const destroy = (observableProxy) => { const observers = observersByProxy.get(observableProxy); if (observers) { observers.clear(); @@ -82,4 +91,5 @@ export const destroy = (observableProxy) => { } return false; }; +exports.destroy = destroy; tick(); diff --git a/lib/index.mjs b/lib/index.mjs new file mode 100644 index 0000000..69bf000 --- /dev/null +++ b/lib/index.mjs @@ -0,0 +1,88 @@ +// data that is being observed +const observables = new Set(); +// original data for each proxy +const observablesByProxy = new Map(); +// observers for each proxy +const observersByProxy = new Map(); +// observers to call on tick +const proxiesToNotify = new Set(); +const objectConstructor = {}.constructor; +const isPlainObject = (data) => !!data && typeof data === 'object' && data.constructor === objectConstructor; +const canBeObservable = (data) => Array.isArray(data) || /*#__INLINE__*/ isPlainObject(data); +const tick = () => { + proxiesToNotify.forEach(proxy => { + observersByProxy.get(proxy).forEach((callback) => callback()); + }); + proxiesToNotify.clear(); + if (typeof window !== "undefined" && window.requestAnimationFrame) + window.requestAnimationFrame(tick); + else + setTimeout(tick, 16); +}; +const makeObservableProxy = (data, rootProxy) => { + if (observables.has(data)) + throw new Error('Can’t observe Object or Array again'); + if (observablesByProxy.has(data)) + throw new Error('rootProxy isn’t an observable'); + let observers; + const proxy = new Proxy(data, { + get(target, key) { + return target[key]; + }, + set(target, key, value) { + if (target[key] !== value) { + if (observers) { + if (observablesByProxy.has(value)) + throw new Error('Can’t nest observables'); + proxiesToNotify.add(rootProxy || proxy); + } + target[key] = value; + } + return true; + }, + deleteProperty(target, key) { + if (key in target) { + delete target[key]; + proxiesToNotify.add(rootProxy || proxy); + return true; + } + return false; + } + }); + (Array.isArray(data) ? [...Array(data.length).keys()] : Object.keys(data)).forEach(key => { + const value = data[key]; + if (observablesByProxy.has(value)) + throw new Error('Can’t nest observables'); + if ( /*#__INLINE__*/canBeObservable(value)) + proxy[key] = makeObservableProxy(value, rootProxy || proxy); + }); + observers = new Set(); + observables.add(data); + observablesByProxy.set(proxy, data); + observersByProxy.set(proxy, observers); + return proxy; +}; +export const observable = (data) => { + return /*#__INLINE__*/ canBeObservable(data) && makeObservableProxy(data); +}; +export const observe = (observableProxy, callback) => { + const observers = observersByProxy.get(observableProxy); + return observers && typeof callback === 'function' ? observers.add(callback) : false; +}; +export const unobserve = (observableProxy, callback) => { + const observers = observersByProxy.get(observableProxy); + return observers && typeof callback === 'function' ? observers.delete(callback) : false; +}; +export const destroy = (observableProxy) => { + const observers = observersByProxy.get(observableProxy); + if (observers) { + observers.clear(); + observables.delete(observablesByProxy.get(observableProxy)); + observablesByProxy.delete(observableProxy); + observersByProxy.delete(observableProxy); + proxiesToNotify.delete(observableProxy); + return true; + } + return false; +}; +tick(); diff --git a/package.json b/package.json index e1205df..a5be65b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "simple-observable-proxy", - "version": "1.0.0", + "version": "1.1.0", "description": "Simple observable ES6 proxy.", "browser": "dist/simple-observable-proxy.js", "main": "lib/index.js",