Comparing version 2.0.2 to 2.1.0
@@ -115,3 +115,3 @@ declare namespace onChange { | ||
let i = 0; | ||
const watchedObject = onChange(object, function (path, value, previousValue) { | ||
const watchedObject = onChange(object, function (path, value, previousValue, name) { | ||
console.log('Object changed:', ++i); | ||
@@ -122,2 +122,3 @@ console.log('this:', this); | ||
console.log('previousValue:', previousValue); | ||
console.log('name:', name); | ||
}); | ||
@@ -140,2 +141,3 @@ | ||
//=> 'previousValue: false' | ||
//=> 'name: undefined' | ||
@@ -157,3 +159,22 @@ watchedObject.a.b[0].c = true; | ||
//=> 'previousValue: false' | ||
//=> 'name: undefined' | ||
watchedObject.a.b.push(3); | ||
//=> 'Object changed: 3' | ||
//=> 'this: { | ||
// foo: true, | ||
// a: { | ||
// b: [ | ||
// { | ||
// c: true | ||
// }, | ||
// 3 | ||
// ] | ||
// } | ||
// }' | ||
//=> 'path: "a.b"' | ||
//=> 'value: [{c: true}, 3]' | ||
//=> 'previousValue: [{c: true}]' | ||
//=> 'name: "push"' | ||
// Access the original object | ||
@@ -175,3 +196,4 @@ onChange.target(watchedObject).foo = false; | ||
value: unknown, | ||
previousValue: unknown | ||
previousValue: unknown, | ||
name: string | ||
) => void, | ||
@@ -178,0 +200,0 @@ options?: onChange.Options |
31
index.js
@@ -24,7 +24,8 @@ 'use strict'; | ||
const handleChange = (changePath, property, previous, value) => { | ||
// eslint-disable-next-line max-params | ||
const handleChange = (changePath, property, previous, value, name) => { | ||
if (smartClone.isCloning) { | ||
smartClone.update(changePath, property, previous); | ||
} else { | ||
onChange(path.concat(changePath, property), value, previous); | ||
onChange(path.concat(changePath, property), value, previous, name); | ||
} | ||
@@ -54,3 +55,3 @@ }; | ||
property === 'constructor' || | ||
options.isShallow === true || | ||
(options.isShallow === true && !smartClone.isHandledMethod(target, property)) || | ||
ignoreProperty(cache, options, property) || | ||
@@ -66,4 +67,8 @@ cache.isGetInvariant(target, property) | ||
set(target, property, value, receiver) { | ||
if (value && value[proxyTarget] !== undefined) { | ||
value = value[proxyTarget]; | ||
if (value) { | ||
const valueProxyTarget = value[proxyTarget]; | ||
if (valueProxyTarget !== undefined) { | ||
value = valueProxyTarget; | ||
} | ||
} | ||
@@ -116,5 +121,6 @@ | ||
const isMutable = isBuiltin.withMutableMethods(thisArg); | ||
const thisProxyTarget = thisArg[proxyTarget] || thisArg; | ||
if (isMutable) { | ||
thisArg = thisArg[proxyTarget]; | ||
thisArg = thisProxyTarget; | ||
} | ||
@@ -133,11 +139,14 @@ | ||
) { | ||
smartClone.start(thisArg[proxyTarget] || thisArg, applyPath); | ||
smartClone.start(thisProxyTarget, applyPath); | ||
} | ||
const result = Reflect.apply(target, thisArg, argumentsList); | ||
const result = Reflect.apply( | ||
target, | ||
smartClone.preferredThisArg(target, thisArg, thisProxyTarget), | ||
argumentsList | ||
); | ||
if (smartClone.isChanged(isMutable, thisArg, equals)) { | ||
const {clone} = smartClone; | ||
smartClone.done(); | ||
handleChange(applyPath, '', clone, thisArg[proxyTarget] || thisArg); | ||
const clone = smartClone.done(); | ||
handleChange(applyPath, '', clone, thisProxyTarget, target.name); | ||
} | ||
@@ -144,0 +153,0 @@ |
@@ -6,2 +6,37 @@ 'use strict'; | ||
const shallowEqual = (clone, value) => { | ||
return clone.length !== value.length || clone.some((item, index) => value[index] !== item); | ||
}; | ||
const IMMUTABLE_OBJECT_METHODS = new Set([ | ||
'hasOwnProperty', | ||
'isPrototypeOf', | ||
'propertyIsEnumerable', | ||
'toLocaleString', | ||
'toString', | ||
'valueOf' | ||
]); | ||
const IMMUTABLE_ARRAY_METHODS = new Set([ | ||
'includes', | ||
'indexOf', | ||
'join', | ||
'keys', | ||
'lastIndexOf' | ||
]); | ||
const SHALLOW_MUTABLE_ARRAY_METHODS = { | ||
push: () => true, | ||
pop: () => true, | ||
shift: () => true, | ||
unshift: () => true, | ||
concat: (clone, value) => clone.length !== value.length, | ||
copyWithin: shallowEqual, | ||
reverse: shallowEqual, | ||
sort: shallowEqual, | ||
splice: shallowEqual, | ||
flat: shallowEqual, | ||
fill: shallowEqual | ||
}; | ||
class smartClone { | ||
@@ -38,2 +73,26 @@ constructor() { | ||
isHandledMethod(target, name) { | ||
if (isArray(target) && (IMMUTABLE_OBJECT_METHODS.has(name) || | ||
IMMUTABLE_ARRAY_METHODS.has(name) || | ||
name in SHALLOW_MUTABLE_ARRAY_METHODS)) { | ||
return true; | ||
} | ||
return toString.call(target) === '[object Object]' && IMMUTABLE_OBJECT_METHODS.has(name); | ||
} | ||
preferredThisArg(target, thisArg, thisProxyTarget) { | ||
const {name} = target; | ||
if (this.isHandledMethod(thisProxyTarget, name)) { | ||
if (isArray(thisProxyTarget)) { | ||
this._onIsChanged = SHALLOW_MUTABLE_ARRAY_METHODS[name]; | ||
} | ||
return thisProxyTarget; | ||
} | ||
return thisArg; | ||
} | ||
update(fullPath, property, value) { | ||
@@ -58,2 +117,4 @@ if (value !== undefined && property !== 'length') { | ||
done() { | ||
const {clone} = this; | ||
if (this._cache !== undefined) { | ||
@@ -66,3 +127,6 @@ this._cache.clear(); | ||
this._path = null; | ||
this._onIsChanged = null; | ||
this._isChanged = false; | ||
return clone; | ||
} | ||
@@ -75,3 +139,5 @@ | ||
return this._isChanged; | ||
return this._onIsChanged ? | ||
this._onIsChanged(this.clone, value) : | ||
this._isChanged; | ||
} | ||
@@ -78,0 +144,0 @@ } |
{ | ||
"name": "on-change", | ||
"version": "2.0.2", | ||
"version": "2.1.0", | ||
"description": "Watch an object or array for changes", | ||
@@ -5,0 +5,0 @@ "license": "MIT", |
@@ -32,3 +32,3 @@ # on-change [![Build Status](https://travis-ci.org/sindresorhus/on-change.svg?branch=master)](https://travis-ci.org/sindresorhus/on-change) | ||
let i = 0; | ||
const watchedObject = onChange(object, function (path, value, previousValue) { | ||
const watchedObject = onChange(object, function (path, value, previousValue, name) { | ||
console.log('Object changed:', ++i); | ||
@@ -39,2 +39,3 @@ console.log('this:', this); | ||
console.log('previousValue:', previousValue); | ||
console.log('name:', name); | ||
}); | ||
@@ -57,2 +58,3 @@ | ||
//=> 'previousValue: false' | ||
//=> 'name: undefined' | ||
@@ -74,3 +76,22 @@ watchedObject.a.b[0].c = true; | ||
//=> 'previousValue: false' | ||
//=> 'name: undefined' | ||
watchedObject.a.b.push(3); | ||
//=> 'Object changed: 3' | ||
//=> 'this: { | ||
// foo: true, | ||
// a: { | ||
// b: [ | ||
// { | ||
// c: true | ||
// }, | ||
// 3 | ||
// ] | ||
// } | ||
// }' | ||
//=> 'path: "a.b"' | ||
//=> 'value: [{c: true}, 3]' | ||
//=> 'previousValue: [{c: true}]' | ||
//=> 'name: "push"' | ||
// Access the original object | ||
@@ -104,6 +125,7 @@ onChange.target(watchedObject).foo = false; | ||
The function receives three arguments: | ||
The function receives four arguments: | ||
1. A path to the value that was changed. A change to `c` in the above example would return `a.b.0.c`. | ||
2. The new value at the path. | ||
3. The previous value at the path. | ||
4. The name of the method that produced the change. | ||
@@ -110,0 +132,0 @@ The context (this) is set to the original object passed to `onChange` (with Proxy). |
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
23896
621
254