Comparing version 2.1.4 to 2.2.0
@@ -88,2 +88,9 @@ declare namespace onChange { | ||
pathAsArray?: boolean; | ||
/** | ||
Ignore changes to objects that become detached from the watched object. | ||
@default false | ||
*/ | ||
ignoreDetached?: boolean; | ||
} | ||
@@ -90,0 +97,0 @@ } |
64
index.js
@@ -7,3 +7,2 @@ 'use strict'; | ||
const isSymbol = require('./lib/is-symbol'); | ||
const isObject = require('./lib/is-object'); | ||
const ignoreProperty = require('./lib/ignore-property'); | ||
@@ -18,3 +17,4 @@ const Cache = require('./lib/cache'); | ||
ignoreSymbols: false, | ||
ignoreUnderscores: false | ||
ignoreUnderscores: false, | ||
ignoreDetached: false | ||
}; | ||
@@ -28,3 +28,3 @@ | ||
const proxyTarget = Symbol('ProxyTarget'); | ||
const {equals, isShallow} = options; | ||
const {equals, isShallow, ignoreDetached} = options; | ||
const cache = new Cache(equals); | ||
@@ -34,3 +34,6 @@ const smartClone = new SmartClone(); | ||
const handleChangeOnTarget = (target, property, previous, value) => { | ||
if (!ignoreProperty(cache, options, property)) { | ||
if ( | ||
!ignoreProperty(cache, options, property) && | ||
!(ignoreDetached && cache.isDetached(target, object)) | ||
) { | ||
handleChange(cache.getPath(target), property, previous, value); | ||
@@ -73,5 +76,6 @@ } | ||
property === 'constructor' || | ||
(isShallow && !smartClone.isHandledMethod(target, property)) || | ||
(isShallow && !SmartClone.isHandledMethod(target, property)) || | ||
ignoreProperty(cache, options, property) || | ||
cache.isGetInvariant(target, property) | ||
cache.isGetInvariant(target, property) || | ||
(ignoreDetached && cache.isDetached(target, object)) | ||
) { | ||
@@ -85,3 +89,3 @@ return value; | ||
set(target, property, value, receiver) { | ||
if (isObject(value)) { | ||
if (value) { | ||
const valueProxyTarget = value[proxyTarget]; | ||
@@ -138,40 +142,36 @@ | ||
apply(target, thisArg, argumentsList) { | ||
const isMutable = isBuiltin.withMutableMethods(thisArg); | ||
const thisProxyTarget = thisArg[proxyTarget] || thisArg; | ||
if (isMutable) { | ||
thisArg = thisProxyTarget; | ||
} | ||
if (smartClone.isCloning || cache.isUnsubscribed) { | ||
if (cache.isUnsubscribed) { | ||
return Reflect.apply(target, thisProxyTarget, argumentsList); | ||
} | ||
const applyPath = path.initial(cache.getPath(target)); | ||
if (SmartClone.isHandledType(thisProxyTarget)) { | ||
const applyPath = path.initial(cache.getPath(target)); | ||
if (isMutable || smartClone.isHandledType(thisProxyTarget)) { | ||
smartClone.start(thisProxyTarget, applyPath, argumentsList); | ||
} | ||
const result = Reflect.apply( | ||
target, | ||
smartClone.preferredThisArg(target, thisArg, thisProxyTarget), | ||
argumentsList | ||
); | ||
const result = Reflect.apply( | ||
target, | ||
smartClone.preferredThisArg(target, thisArg, thisProxyTarget), | ||
argumentsList | ||
); | ||
if (smartClone.isChanged(isMutable, thisProxyTarget, equals, argumentsList)) { | ||
const clone = smartClone.done(); | ||
handleChange(applyPath, '', clone, thisProxyTarget, target.name); | ||
} | ||
const isChanged = smartClone.isChanged(thisProxyTarget, equals, argumentsList); | ||
const clone = smartClone.stop(); | ||
smartClone.done(); | ||
if (isChanged) { | ||
if (smartClone.isCloning) { | ||
handleChange(path.initial(applyPath), path.last(applyPath), clone, thisProxyTarget, target.name); | ||
} else { | ||
handleChange(applyPath, '', clone, thisProxyTarget, target.name); | ||
} | ||
} | ||
if ( | ||
smartClone.isHandledType(result) && | ||
smartClone.isHandledMethod(thisProxyTarget, target.name) | ||
) { | ||
return cache.getProxy(result, applyPath, handler); | ||
return (SmartClone.isHandledType(result) && SmartClone.isHandledMethod(thisProxyTarget, target.name)) ? | ||
cache.getProxy(result, applyPath, handler) : | ||
result; | ||
} | ||
return result; | ||
return Reflect.apply(target, thisArg, argumentsList); | ||
} | ||
@@ -178,0 +178,0 @@ }; |
'use strict'; | ||
const path = require('./path'); | ||
/** | ||
@@ -72,2 +74,12 @@ * @class Cache | ||
isDetached(target, object) { | ||
path.walk(this.getPath(target), key => { | ||
if (object) { | ||
object = object[key]; | ||
} | ||
}); | ||
return !Object.is(target, object); | ||
} | ||
defineProperty(target, property, descriptor) { | ||
@@ -74,0 +86,0 @@ if (!Reflect.defineProperty(target, property, descriptor)) { |
'use strict'; | ||
const isPrimitive = value => typeof value === 'object' ? value === null : typeof value !== 'function'; | ||
const isBuiltin = { | ||
withMutableMethods: value => { | ||
if (isPrimitive(value)) { | ||
return false; | ||
} | ||
return value instanceof Date || | ||
@@ -17,5 +11,5 @@ value instanceof Set || | ||
}, | ||
withoutMutableMethods: value => isPrimitive(value) || value instanceof RegExp | ||
withoutMutableMethods: value => (typeof value === 'object' ? value === null : typeof value !== 'function') || value instanceof RegExp | ||
}; | ||
module.exports = isBuiltin; |
@@ -36,3 +36,3 @@ 'use strict'; | ||
if (isSymbol(key)) { | ||
return path + 'Symbol(' + key.description + ')'; | ||
return path + key.toString(); | ||
} | ||
@@ -62,2 +62,19 @@ | ||
}, | ||
last: path => { | ||
if (isArray(path)) { | ||
return path[path.length - 1] || ''; | ||
} | ||
if (path === '') { | ||
return path; | ||
} | ||
const index = path.lastIndexOf(PATH_SEPARATOR); | ||
if (index === -1) { | ||
return path; | ||
} | ||
return path.slice(index + 1); | ||
}, | ||
walk: (path, callback) => { | ||
@@ -64,0 +81,0 @@ if (isArray(path)) { |
@@ -14,9 +14,9 @@ 'use strict'; | ||
const shallowEqualSets = (a, b) => { | ||
if (a.size !== b.size) { | ||
const shallowEqualSets = (clone, value) => { | ||
if (clone.size !== value.size) { | ||
return true; | ||
} | ||
for (const element of a) { | ||
if (!b.has(element)) { | ||
for (const element of clone) { | ||
if (!value.has(element)) { | ||
return true; | ||
@@ -29,4 +29,4 @@ } | ||
const shallowEqualMaps = (a, b) => { | ||
if (a.size !== b.size) { | ||
const shallowEqualMaps = (clone, value) => { | ||
if (clone.size !== value.size) { | ||
return true; | ||
@@ -36,6 +36,6 @@ } | ||
let bValue; | ||
for (const [key, aValue] of a) { | ||
bValue = b.get(key); | ||
for (const [key, aValue] of clone) { | ||
bValue = value.get(key); | ||
if (bValue !== aValue || (bValue === undefined && !b.has(key))) { | ||
if (bValue !== aValue || (bValue === undefined && !value.has(key))) { | ||
return true; | ||
@@ -58,2 +58,3 @@ } | ||
const IMMUTABLE_ARRAY_METHODS = new Set([ | ||
'concat', | ||
'includes', | ||
@@ -79,3 +80,2 @@ 'indexOf', | ||
unshift: certainChange, | ||
concat: (clone, value) => clone.length !== value.length, | ||
copyWithin: shallowEqualArrays, | ||
@@ -111,5 +111,15 @@ reverse: shallowEqualArrays, | ||
class smartClone { | ||
constructor() { | ||
this.done(); | ||
class Clone { | ||
constructor(value, path, argumentsList) { | ||
this._path = path; | ||
this._isChanged = false; | ||
this._clonedCache = new Set(); | ||
if (value instanceof WeakSet) { | ||
this._weakValue = value.has(argumentsList[0]); | ||
} else if (value instanceof WeakMap) { | ||
this._weakValue = value.get(argumentsList[0]); | ||
} else { | ||
this.clone = path === undefined ? value : this._shallowClone(value); | ||
} | ||
} | ||
@@ -132,3 +142,3 @@ | ||
this._cache.add(clone); | ||
this._clonedCache.add(clone); | ||
@@ -138,49 +148,6 @@ return clone; | ||
start(value, path, argumentsList) { | ||
if (this._cache === undefined) { | ||
this._cache = new Set(); | ||
} | ||
if (value instanceof WeakSet) { | ||
this._weakValue = value.has(argumentsList[0]); | ||
} else if (value instanceof WeakMap) { | ||
this._weakValue = value.get(argumentsList[0]); | ||
} else { | ||
this.clone = path === undefined ? value : this._shallowClone(value); | ||
} | ||
this._path = path; | ||
this.isCloning = true; | ||
} | ||
isHandledType(value) { | ||
return isObject(value) || | ||
isArray(value) || | ||
isBuiltin.withMutableMethods(value); | ||
} | ||
isHandledMethod(target, name) { | ||
if (isObject(target)) { | ||
return IMMUTABLE_OBJECT_METHODS.has(name); | ||
} | ||
if (isArray(target)) { | ||
return HANDLED_ARRAY_METHODS.has(name); | ||
} | ||
if (target instanceof Set) { | ||
return HANDLED_SET_METHODS.has(name); | ||
} | ||
if (target instanceof Map) { | ||
return HANDLED_MAP_METHODS.has(name); | ||
} | ||
return target instanceof WeakSet || target instanceof WeakMap; | ||
} | ||
preferredThisArg(target, thisArg, thisProxyTarget) { | ||
const {name} = target; | ||
if (this.isHandledMethod(thisProxyTarget, name)) { | ||
if (SmartClone.isHandledMethod(thisProxyTarget, name)) { | ||
if (isArray(thisProxyTarget)) { | ||
@@ -205,3 +172,3 @@ this._onIsChanged = SHALLOW_MUTABLE_ARRAY_METHODS[name]; | ||
path.walk(path.after(fullPath, this._path), key => { | ||
if (!this._cache.has(object[key])) { | ||
if (!this._clonedCache.has(object[key])) { | ||
object[key] = this._shallowClone(object[key]); | ||
@@ -219,40 +186,77 @@ } | ||
done() { | ||
const {clone} = this; | ||
isChanged(value, equals, argumentsList) { | ||
if (value instanceof Date) { | ||
return !equals(this.clone.valueOf(), value.valueOf()); | ||
} | ||
if (this._cache !== undefined) { | ||
this._cache.clear(); | ||
if (value instanceof WeakSet) { | ||
return this._weakValue !== value.has(argumentsList[0]); | ||
} | ||
this.clone = undefined; | ||
this._weakValue = undefined; | ||
this.isCloning = false; | ||
this._path = null; | ||
this._onIsChanged = null; | ||
this._isChanged = false; | ||
if (value instanceof WeakMap) { | ||
return this._weakValue !== value.get(argumentsList[0]); | ||
} | ||
return clone; | ||
return this._onIsChanged === undefined ? | ||
this._isChanged : | ||
this._onIsChanged(this.clone, value); | ||
} | ||
} | ||
isChanged(isMutable, value, equals, argumentsList) { | ||
if (isMutable) { | ||
if (value instanceof Date) { | ||
return !equals(this.clone.valueOf(), value.valueOf()); | ||
} | ||
class SmartClone { | ||
constructor() { | ||
this.stack = []; | ||
} | ||
if (value instanceof WeakSet) { | ||
return this._weakValue !== value.has(argumentsList[0]); | ||
} | ||
static isHandledType(value) { | ||
return isObject(value) || | ||
isArray(value) || | ||
isBuiltin.withMutableMethods(value); | ||
} | ||
if (value instanceof WeakMap) { | ||
return this._weakValue !== value.get(argumentsList[0]); | ||
} | ||
static isHandledMethod(target, name) { | ||
if (isObject(target)) { | ||
return IMMUTABLE_OBJECT_METHODS.has(name); | ||
} | ||
return this._onIsChanged ? | ||
this._onIsChanged(this.clone, value) : | ||
this._isChanged; | ||
if (isArray(target)) { | ||
return HANDLED_ARRAY_METHODS.has(name); | ||
} | ||
if (target instanceof Set) { | ||
return HANDLED_SET_METHODS.has(name); | ||
} | ||
if (target instanceof Map) { | ||
return HANDLED_MAP_METHODS.has(name); | ||
} | ||
return isBuiltin.withMutableMethods(target); | ||
} | ||
get isCloning() { | ||
return this.stack.length !== 0; | ||
} | ||
start(value, path, argumentsList) { | ||
this.stack.push(new Clone(value, path, argumentsList)); | ||
} | ||
update(fullPath, property, value) { | ||
this.stack[this.stack.length - 1].update(fullPath, property, value); | ||
} | ||
preferredThisArg(target, thisArg, thisProxyTarget) { | ||
return this.stack[this.stack.length - 1].preferredThisArg(target, thisArg, thisProxyTarget); | ||
} | ||
isChanged(isMutable, value, equals, argumentsList) { | ||
return this.stack[this.stack.length - 1].isChanged(isMutable, value, equals, argumentsList); | ||
} | ||
stop() { | ||
return this.stack.pop().clone; | ||
} | ||
} | ||
module.exports = smartClone; | ||
module.exports = SmartClone; |
{ | ||
"name": "on-change", | ||
"version": "2.1.4", | ||
"version": "2.2.0", | ||
"description": "Watch an object or array for changes", | ||
@@ -42,5 +42,5 @@ "license": "MIT", | ||
"devDependencies": { | ||
"ava": "^3.11.1", | ||
"display-value": "^1.7.3", | ||
"karma-webpack-bundle": "^0.5.0", | ||
"ava": "^3.13.0", | ||
"display-value": "^1.8.1", | ||
"karma-webpack-bundle": "^0.5.3", | ||
"powerset": "0.0.1", | ||
@@ -47,0 +47,0 @@ "tsd": "^0.13.1", |
@@ -1,2 +0,2 @@ | ||
# on-change [![Build Status](https://travis-ci.org/sindresorhus/on-change.svg?branch=master)](https://travis-ci.org/sindresorhus/on-change) | ||
# on-change [![Build Status](https://travis-ci.com/sindresorhus/on-change.svg?branch=master)](https://travis-ci.org/sindresorhus/on-change) | ||
@@ -175,2 +175,11 @@ > Watch an object or array for changes | ||
##### ignoreDetached | ||
Type: `boolean`\ | ||
Default: `false` | ||
Ignore changes to objects that become detached from the watched object. | ||
<br/> | ||
### onChange.target(object) | ||
@@ -177,0 +186,0 @@ |
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
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
28636
770
263