diff --git a/README.md b/README.md
index 255b476ca..55268b3be 100644
--- a/README.md
+++ b/README.md
@@ -515,6 +515,22 @@ You can also use `$dispatch()` to trigger data updates for `x-model` bindings. F
You can "watch" a component property with the `$watch` magic method. In the above example, when the button is clicked and `open` is changed, the provided callback will fire and `console.log` the new value.
+Use dot-delimited paths to watch properties of nested objects like `foo.bar.baz`. For example:
+
+```html
+
+
+
+```
+
+You can also detect nested value changes inside objects "deep watching", By passing `{ deep: true }` as the options argument. For example:
+
+```html
+
+
+
+```
+
## v3 Roadmap
* Move from `x-ref` to `ref` for Vue parity
diff --git a/dist/alpine-ie11.js b/dist/alpine-ie11.js
index 4097a1d18..94af5e2d1 100644
--- a/dist/alpine-ie11.js
+++ b/dist/alpine-ie11.js
@@ -3200,17 +3200,42 @@
}
});
- // `SameValue` abstract operation
- // https://tc39.github.io/ecma262/#sec-samevalue
- var sameValue = Object.is || function is(x, y) {
- // eslint-disable-next-line no-self-compare
- return x === y ? x !== 0 || 1 / x === 1 / y : x != x && y != y;
- };
+ var HAS_SPECIES_SUPPORT$2 = arrayMethodHasSpeciesSupport('slice');
+ var USES_TO_LENGTH$5 = arrayMethodUsesToLength('slice', { ACCESSORS: true, 0: 0, 1: 2 });
- // `Object.is` method
- // https://tc39.github.io/ecma262/#sec-object.is
- _export({ target: 'Object', stat: true }, {
- is: sameValue
+ var SPECIES$2 = wellKnownSymbol('species');
+ var nativeSlice = [].slice;
+ var max$1 = Math.max;
+
+ // `Array.prototype.slice` method
+ // https://tc39.github.io/ecma262/#sec-array.prototype.slice
+ // fallback for not array-like ES3 strings and DOM objects
+ _export({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$2 || !USES_TO_LENGTH$5 }, {
+ slice: function slice(start, end) {
+ var O = toIndexedObject(this);
+ var length = toLength(O.length);
+ var k = toAbsoluteIndex(start, length);
+ var fin = toAbsoluteIndex(end === undefined ? length : end, length);
+ // inline `ArraySpeciesCreate` for usage native `Array#slice` where it's possible
+ var Constructor, result, n;
+ if (isArray(O)) {
+ Constructor = O.constructor;
+ // cross-realm fallback
+ if (typeof Constructor == 'function' && (Constructor === Array || isArray(Constructor.prototype))) {
+ Constructor = undefined;
+ } else if (isObject(Constructor)) {
+ Constructor = Constructor[SPECIES$2];
+ if (Constructor === null) Constructor = undefined;
+ }
+ if (Constructor === Array || Constructor === undefined) {
+ return nativeSlice.call(O, k, fin);
+ }
+ }
+ result = new (Constructor === undefined ? Array : Constructor)(max$1(fin - k, 0));
+ for (n = 0; k < fin; k++, n++) if (k in O) createProperty(result, n, O[k]);
+ result.length = n;
+ return result;
+ }
});
var FAILS_ON_PRIMITIVES = fails(function () { objectKeys(1); });
@@ -3235,6 +3260,46 @@
redefine(Object.prototype, 'toString', objectToString, { unsafe: true });
}
+ var propertyIsEnumerable = objectPropertyIsEnumerable.f;
+
+ // `Object.{ entries, values }` methods implementation
+ var createMethod$4 = function (TO_ENTRIES) {
+ return function (it) {
+ var O = toIndexedObject(it);
+ var keys = objectKeys(O);
+ var length = keys.length;
+ var i = 0;
+ var result = [];
+ var key;
+ while (length > i) {
+ key = keys[i++];
+ if (!descriptors || propertyIsEnumerable.call(O, key)) {
+ result.push(TO_ENTRIES ? [key, O[key]] : O[key]);
+ }
+ }
+ return result;
+ };
+ };
+
+ var objectToArray = {
+ // `Object.entries` method
+ // https://tc39.github.io/ecma262/#sec-object.entries
+ entries: createMethod$4(true),
+ // `Object.values` method
+ // https://tc39.github.io/ecma262/#sec-object.values
+ values: createMethod$4(false)
+ };
+
+ var $values = objectToArray.values;
+
+ // `Object.values` method
+ // https://tc39.github.io/ecma262/#sec-object.values
+ _export({ target: 'Object', stat: true }, {
+ values: function values(O) {
+ return $values(O);
+ }
+ });
+
var nativePromiseConstructor = global_1.Promise;
var redefineAll = function (target, src, options) {
@@ -3242,14 +3307,14 @@
return target;
};
- var SPECIES$2 = wellKnownSymbol('species');
+ var SPECIES$3 = wellKnownSymbol('species');
var setSpecies = function (CONSTRUCTOR_NAME) {
var Constructor = getBuiltIn(CONSTRUCTOR_NAME);
var defineProperty = objectDefineProperty.f;
- if (descriptors && Constructor && !Constructor[SPECIES$2]) {
- defineProperty(Constructor, SPECIES$2, {
+ if (descriptors && Constructor && !Constructor[SPECIES$3]) {
+ defineProperty(Constructor, SPECIES$3, {
configurable: true,
get: function () { return this; }
});
@@ -3301,14 +3366,14 @@
};
});
- var SPECIES$3 = wellKnownSymbol('species');
+ var SPECIES$4 = wellKnownSymbol('species');
// `SpeciesConstructor` abstract operation
// https://tc39.github.io/ecma262/#sec-speciesconstructor
var speciesConstructor = function (O, defaultConstructor) {
var C = anObject(O).constructor;
var S;
- return C === undefined || (S = anObject(C)[SPECIES$3]) == undefined ? defaultConstructor : aFunction$1(S);
+ return C === undefined || (S = anObject(C)[SPECIES$4]) == undefined ? defaultConstructor : aFunction$1(S);
};
var engineIsIos = /(iphone|ipod|ipad).*applewebkit/i.test(engineUserAgent);
@@ -3540,7 +3605,7 @@
- var SPECIES$4 = wellKnownSymbol('species');
+ var SPECIES$5 = wellKnownSymbol('species');
var PROMISE = 'Promise';
var getInternalState$1 = internalState.get;
var setInternalState$1 = internalState.set;
@@ -3583,7 +3648,7 @@
exec(function () { /* empty */ }, function () { /* empty */ });
};
var constructor = promise.constructor = {};
- constructor[SPECIES$4] = FakePromise;
+ constructor[SPECIES$5] = FakePromise;
return !(promise.then(function () { /* empty */ }) instanceof FakePromise);
});
@@ -4016,44 +4081,6 @@
exec: regexpExec
});
- var MATCH = wellKnownSymbol('match');
-
- // `IsRegExp` abstract operation
- // https://tc39.github.io/ecma262/#sec-isregexp
- var isRegexp = function (it) {
- var isRegExp;
- return isObject(it) && ((isRegExp = it[MATCH]) !== undefined ? !!isRegExp : classofRaw(it) == 'RegExp');
- };
-
- var notARegexp = function (it) {
- if (isRegexp(it)) {
- throw TypeError("The method doesn't accept regular expressions");
- } return it;
- };
-
- var MATCH$1 = wellKnownSymbol('match');
-
- var correctIsRegexpLogic = function (METHOD_NAME) {
- var regexp = /./;
- try {
- '/./'[METHOD_NAME](regexp);
- } catch (e) {
- try {
- regexp[MATCH$1] = false;
- return '/./'[METHOD_NAME](regexp);
- } catch (f) { /* empty */ }
- } return false;
- };
-
- // `String.prototype.includes` method
- // https://tc39.github.io/ecma262/#sec-string.prototype.includes
- _export({ target: 'String', proto: true, forced: !correctIsRegexpLogic('includes') }, {
- includes: function includes(searchString /* , position = 0 */) {
- return !!~String(requireObjectCoercible(this))
- .indexOf(notARegexp(searchString), arguments.length > 1 ? arguments[1] : undefined);
- }
- });
-
// TODO: Remove from `core-js@4` since it's moved to entry points
@@ -4062,7 +4089,7 @@
- var SPECIES$5 = wellKnownSymbol('species');
+ var SPECIES$6 = wellKnownSymbol('species');
var REPLACE_SUPPORTS_NAMED_GROUPS = !fails(function () {
// #replace needs built-in support for named groups.
@@ -4125,7 +4152,7 @@
// RegExp[@@split] doesn't call the regex's exec method, but first creates
// a new one. We need to return the patched regex when creating the new one.
re.constructor = {};
- re.constructor[SPECIES$5] = function () { return re; };
+ re.constructor[SPECIES$6] = function () { return re; };
re.flags = '';
re[SYMBOL] = /./[SYMBOL];
}
@@ -4243,6 +4270,15 @@
];
});
+ var MATCH = wellKnownSymbol('match');
+
+ // `IsRegExp` abstract operation
+ // https://tc39.github.io/ecma262/#sec-isregexp
+ var isRegexp = function (it) {
+ var isRegExp;
+ return isObject(it) && ((isRegExp = it[MATCH]) !== undefined ? !!isRegExp : classofRaw(it) == 'RegExp');
+ };
+
var arrayPush = [].push;
var min$2 = Math.min;
var MAX_UINT32 = 0xFFFFFFFF;
@@ -4366,6 +4402,57 @@
];
}, !SUPPORTS_Y);
+ var notARegexp = function (it) {
+ if (isRegexp(it)) {
+ throw TypeError("The method doesn't accept regular expressions");
+ } return it;
+ };
+
+ var MATCH$1 = wellKnownSymbol('match');
+
+ var correctIsRegexpLogic = function (METHOD_NAME) {
+ var regexp = /./;
+ try {
+ '/./'[METHOD_NAME](regexp);
+ } catch (e) {
+ try {
+ regexp[MATCH$1] = false;
+ return '/./'[METHOD_NAME](regexp);
+ } catch (f) { /* empty */ }
+ } return false;
+ };
+
+ var getOwnPropertyDescriptor$3 = objectGetOwnPropertyDescriptor.f;
+
+
+
+
+
+
+ var nativeStartsWith = ''.startsWith;
+ var min$3 = Math.min;
+
+ var CORRECT_IS_REGEXP_LOGIC = correctIsRegexpLogic('startsWith');
+ // https://github.com/zloirock/core-js/pull/702
+ var MDN_POLYFILL_BUG = !CORRECT_IS_REGEXP_LOGIC && !!function () {
+ var descriptor = getOwnPropertyDescriptor$3(String.prototype, 'startsWith');
+ return descriptor && !descriptor.writable;
+ }();
+
+ // `String.prototype.startsWith` method
+ // https://tc39.github.io/ecma262/#sec-string.prototype.startswith
+ _export({ target: 'String', proto: true, forced: !MDN_POLYFILL_BUG && !CORRECT_IS_REGEXP_LOGIC }, {
+ startsWith: function startsWith(searchString /* , position = 0 */) {
+ var that = String(requireObjectCoercible(this));
+ notARegexp(searchString);
+ var index = toLength(min$3(arguments.length > 1 ? arguments[1] : undefined, that.length));
+ var search = String(searchString);
+ return nativeStartsWith
+ ? nativeStartsWith.call(that, search, index)
+ : that.slice(index, index + search.length) === search;
+ }
+ });
+
var IS_CONCAT_SPREADABLE = wellKnownSymbol('isConcatSpreadable');
var MAX_SAFE_INTEGER = 0x1FFFFFFFFFFFFF;
var MAXIMUM_ALLOWED_INDEX_EXCEEDED = 'Maximum allowed index exceeded';
@@ -4421,14 +4508,14 @@
var FIND = 'find';
var SKIPS_HOLES = true;
- var USES_TO_LENGTH$5 = arrayMethodUsesToLength(FIND);
+ var USES_TO_LENGTH$6 = arrayMethodUsesToLength(FIND);
// Shouldn't skip holes
if (FIND in []) Array(1)[FIND](function () { SKIPS_HOLES = false; });
// `Array.prototype.find` method
// https://tc39.github.io/ecma262/#sec-array.prototype.find
- _export({ target: 'Array', proto: true, forced: SKIPS_HOLES || !USES_TO_LENGTH$5 }, {
+ _export({ target: 'Array', proto: true, forced: SKIPS_HOLES || !USES_TO_LENGTH$6 }, {
find: function find(callbackfn /* , that = undefined */) {
return $find(this, callbackfn, arguments.length > 1 ? arguments[1] : undefined);
}
@@ -4445,11 +4532,11 @@
var NEGATIVE_ZERO = !!nativeIndexOf && 1 / [1].indexOf(1, -0) < 0;
var STRICT_METHOD$2 = arrayMethodIsStrict('indexOf');
- var USES_TO_LENGTH$6 = arrayMethodUsesToLength('indexOf', { ACCESSORS: true, 1: 0 });
+ var USES_TO_LENGTH$7 = arrayMethodUsesToLength('indexOf', { ACCESSORS: true, 1: 0 });
// `Array.prototype.indexOf` method
// https://tc39.github.io/ecma262/#sec-array.prototype.indexof
- _export({ target: 'Array', proto: true, forced: NEGATIVE_ZERO || !STRICT_METHOD$2 || !USES_TO_LENGTH$6 }, {
+ _export({ target: 'Array', proto: true, forced: NEGATIVE_ZERO || !STRICT_METHOD$2 || !USES_TO_LENGTH$7 }, {
indexOf: function indexOf(searchElement /* , fromIndex = 0 */) {
return NEGATIVE_ZERO
// convert -0 to +0
@@ -4471,18 +4558,18 @@
}
});
- var HAS_SPECIES_SUPPORT$2 = arrayMethodHasSpeciesSupport('splice');
- var USES_TO_LENGTH$7 = arrayMethodUsesToLength('splice', { ACCESSORS: true, 0: 0, 1: 2 });
+ var HAS_SPECIES_SUPPORT$3 = arrayMethodHasSpeciesSupport('splice');
+ var USES_TO_LENGTH$8 = arrayMethodUsesToLength('splice', { ACCESSORS: true, 0: 0, 1: 2 });
- var max$1 = Math.max;
- var min$3 = Math.min;
+ var max$2 = Math.max;
+ var min$4 = Math.min;
var MAX_SAFE_INTEGER$1 = 0x1FFFFFFFFFFFFF;
var MAXIMUM_ALLOWED_LENGTH_EXCEEDED = 'Maximum allowed length exceeded';
// `Array.prototype.splice` method
// https://tc39.github.io/ecma262/#sec-array.prototype.splice
// with adding support of @@species
- _export({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$2 || !USES_TO_LENGTH$7 }, {
+ _export({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$3 || !USES_TO_LENGTH$8 }, {
splice: function splice(start, deleteCount /* , ...items */) {
var O = toObject(this);
var len = toLength(O.length);
@@ -4496,7 +4583,7 @@
actualDeleteCount = len - actualStart;
} else {
insertCount = argumentsLength - 2;
- actualDeleteCount = min$3(max$1(toInteger(deleteCount), 0), len - actualStart);
+ actualDeleteCount = min$4(max$2(toInteger(deleteCount), 0), len - actualStart);
}
if (len + insertCount - actualDeleteCount > MAX_SAFE_INTEGER$1) {
throw TypeError(MAXIMUM_ALLOWED_LENGTH_EXCEEDED);
@@ -4577,7 +4664,7 @@
var rtrim = RegExp(whitespace + whitespace + '*$');
// `String.prototype.{ trim, trimStart, trimEnd, trimLeft, trimRight }` methods implementation
- var createMethod$4 = function (TYPE) {
+ var createMethod$5 = function (TYPE) {
return function ($this) {
var string = String(requireObjectCoercible($this));
if (TYPE & 1) string = string.replace(ltrim, '');
@@ -4589,17 +4676,17 @@
var stringTrim = {
// `String.prototype.{ trimLeft, trimStart }` methods
// https://tc39.github.io/ecma262/#sec-string.prototype.trimstart
- start: createMethod$4(1),
+ start: createMethod$5(1),
// `String.prototype.{ trimRight, trimEnd }` methods
// https://tc39.github.io/ecma262/#sec-string.prototype.trimend
- end: createMethod$4(2),
+ end: createMethod$5(2),
// `String.prototype.trim` method
// https://tc39.github.io/ecma262/#sec-string.prototype.trim
- trim: createMethod$4(3)
+ trim: createMethod$5(3)
};
var getOwnPropertyNames = objectGetOwnPropertyNames.f;
- var getOwnPropertyDescriptor$3 = objectGetOwnPropertyDescriptor.f;
+ var getOwnPropertyDescriptor$4 = objectGetOwnPropertyDescriptor.f;
var defineProperty$3 = objectDefineProperty.f;
var trim = stringTrim.trim;
@@ -4658,7 +4745,7 @@
'MIN_SAFE_INTEGER,parseFloat,parseInt,isInteger'
).split(','), j = 0, key; keys$1.length > j; j++) {
if (has(NativeNumber, key = keys$1[j]) && !has(NumberWrapper, key)) {
- defineProperty$3(NumberWrapper, key, getOwnPropertyDescriptor$3(NativeNumber, key));
+ defineProperty$3(NumberWrapper, key, getOwnPropertyDescriptor$4(NativeNumber, key));
}
}
NumberWrapper.prototype = NumberPrototype;
@@ -4666,48 +4753,17 @@
redefine(global_1, NUMBER, NumberWrapper);
}
- var propertyIsEnumerable = objectPropertyIsEnumerable.f;
-
- // `Object.{ entries, values }` methods implementation
- var createMethod$5 = function (TO_ENTRIES) {
- return function (it) {
- var O = toIndexedObject(it);
- var keys = objectKeys(O);
- var length = keys.length;
- var i = 0;
- var result = [];
- var key;
- while (length > i) {
- key = keys[i++];
- if (!descriptors || propertyIsEnumerable.call(O, key)) {
- result.push(TO_ENTRIES ? [key, O[key]] : O[key]);
- }
- }
- return result;
- };
- };
-
- var objectToArray = {
- // `Object.entries` method
- // https://tc39.github.io/ecma262/#sec-object.entries
- entries: createMethod$5(true),
- // `Object.values` method
- // https://tc39.github.io/ecma262/#sec-object.values
- values: createMethod$5(false)
- };
-
- var $values = objectToArray.values;
-
- // `Object.values` method
- // https://tc39.github.io/ecma262/#sec-object.values
- _export({ target: 'Object', stat: true }, {
- values: function values(O) {
- return $values(O);
+ // `String.prototype.includes` method
+ // https://tc39.github.io/ecma262/#sec-string.prototype.includes
+ _export({ target: 'String', proto: true, forced: !correctIsRegexpLogic('includes') }, {
+ includes: function includes(searchString /* , position = 0 */) {
+ return !!~String(requireObjectCoercible(this))
+ .indexOf(notARegexp(searchString), arguments.length > 1 ? arguments[1] : undefined);
}
});
- var max$2 = Math.max;
- var min$4 = Math.min;
+ var max$3 = Math.max;
+ var min$5 = Math.min;
var floor$1 = Math.floor;
var SUBSTITUTION_SYMBOLS = /\$([$&'`]|\d\d?|<[^>]*>)/g;
var SUBSTITUTION_SYMBOLS_NO_NAMED = /\$([$&'`]|\d\d?)/g;
@@ -4772,7 +4828,7 @@
result = results[i];
var matched = String(result[0]);
- var position = max$2(min$4(toInteger(result.index), S.length), 0);
+ var position = max$3(min$5(toInteger(result.index), S.length), 0);
var captures = [];
// NOTE: This is equivalent to
// captures = result.slice(1).map(maybeToString)
@@ -4832,37 +4888,6 @@
}
});
- var getOwnPropertyDescriptor$4 = objectGetOwnPropertyDescriptor.f;
-
-
-
-
-
-
- var nativeStartsWith = ''.startsWith;
- var min$5 = Math.min;
-
- var CORRECT_IS_REGEXP_LOGIC = correctIsRegexpLogic('startsWith');
- // https://github.com/zloirock/core-js/pull/702
- var MDN_POLYFILL_BUG = !CORRECT_IS_REGEXP_LOGIC && !!function () {
- var descriptor = getOwnPropertyDescriptor$4(String.prototype, 'startsWith');
- return descriptor && !descriptor.writable;
- }();
-
- // `String.prototype.startsWith` method
- // https://tc39.github.io/ecma262/#sec-string.prototype.startswith
- _export({ target: 'String', proto: true, forced: !MDN_POLYFILL_BUG && !CORRECT_IS_REGEXP_LOGIC }, {
- startsWith: function startsWith(searchString /* , position = 0 */) {
- var that = String(requireObjectCoercible(this));
- notARegexp(searchString);
- var index = toLength(min$5(arguments.length > 1 ? arguments[1] : undefined, that.length));
- var search = String(searchString);
- return nativeStartsWith
- ? nativeStartsWith.call(that, search, index)
- : that.slice(index, index + search.length) === search;
- }
- });
-
var non = '\u200B\u0085\u180E';
// check that a method works with the correct list
@@ -6351,10 +6376,15 @@
this.watchers = {};
this.unobservedData.$watch = function (property, callback) {
+ var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
+
_newArrowCheck(this, _this);
- if (!this.watchers[property]) this.watchers[property] = [];
- this.watchers[property].push(callback);
+ if (!this.watchers[property]) {
+ this.watchers[property] = this.walkToConstructWatcher(property.split('.'), this.unobservedData, options);
+ }
+
+ this.watchers[property].callbacks.push(callback);
}.bind(this);
this.showDirectiveStack = [];
@@ -6405,50 +6435,27 @@
valueMutated: function valueMutated(target, key) {
var _this3 = this;
- if (self.watchers[key]) {
- // If there's a watcher for this specific key, run it.
- self.watchers[key].forEach(function (callback) {
- _newArrowCheck(this, _this3);
-
- return callback(target[key]);
- }.bind(this));
- } else {
- // Let's walk through the watchers with "dot-notation" (foo.bar) and see
- // if this mutation fits any of them.
- Object.keys(self.watchers).filter(function (i) {
- _newArrowCheck(this, _this3);
+ Object.values(self.watchers).forEach(function (watcher) {
+ var _this4 = this;
- return i.includes('.');
- }.bind(this)).forEach(function (fullDotNotationKey) {
- var _this4 = this;
-
- _newArrowCheck(this, _this3);
-
- var dotNotationParts = fullDotNotationKey.split('.'); // If this dot-notation watcher's last "part" doesn't match the current
- // key, then skip it early for performance reasons.
-
- if (key !== dotNotationParts[dotNotationParts.length - 1]) return; // Now, walk through the dot-notation "parts" recursively to find
- // a match, and call the watcher if one's found.
-
- dotNotationParts.reduce(function (comparisonData, part) {
- var _this5 = this;
+ _newArrowCheck(this, _this3);
+ if (target === watcher.target && key === watcher.key) {
+ //Callback property watcher, return property value
+ watcher.callbacks.forEach(function (callback) {
_newArrowCheck(this, _this4);
- if (Object.is(target, comparisonData)) {
- // Run the watchers.
- self.watchers[fullDotNotationKey].forEach(function (callback) {
- _newArrowCheck(this, _this5);
-
- return callback(target[key]);
- }.bind(this));
- }
-
- return comparisonData[part];
- }.bind(this), self.getUnobservedData());
- }.bind(this));
- } // Don't react to data changes for cases like the `x-created` hook.
+ return callback(target[key]);
+ }.bind(this));
+ } else if (watcher.deep && self.foundUnderObjectGraph(target, watcher.target, self)) {
+ //Callback structure watcher, return structure value
+ watcher.callbacks.forEach(function (callback) {
+ _newArrowCheck(this, _this4);
+ return callback(watcher.target);
+ }.bind(this));
+ }
+ }.bind(this)); // Don't react to data changes for cases like the `x-created` hook.
if (self.pauseReactivity) return;
debounce(function () {
@@ -6470,13 +6477,13 @@
}, {
key: "walkAndSkipNestedComponents",
value: function walkAndSkipNestedComponents(el, callback) {
- var _this6 = this;
+ var _this5 = this;
var initializeComponentCallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {
- _newArrowCheck(this, _this6);
+ _newArrowCheck(this, _this5);
}.bind(this);
walk(el, function (el) {
- _newArrowCheck(this, _this6);
+ _newArrowCheck(this, _this5);
// We've hit a component.
if (el.hasAttribute('x-data')) {
@@ -6495,19 +6502,19 @@
}, {
key: "initializeElements",
value: function initializeElements(rootEl) {
- var _this7 = this;
+ var _this6 = this;
var extraVars = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {
- _newArrowCheck(this, _this7);
+ _newArrowCheck(this, _this6);
}.bind(this);
this.walkAndSkipNestedComponents(rootEl, function (el) {
- _newArrowCheck(this, _this7);
+ _newArrowCheck(this, _this6);
// Don't touch spawns from for loop
if (el.__x_for_key !== undefined) return false;
this.initializeElement(el, extraVars);
}.bind(this), function (el) {
- _newArrowCheck(this, _this7);
+ _newArrowCheck(this, _this6);
el.__x = new Component(el);
}.bind(this));
@@ -6532,19 +6539,19 @@
}, {
key: "updateElements",
value: function updateElements(rootEl) {
- var _this8 = this;
+ var _this7 = this;
var extraVars = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () {
- _newArrowCheck(this, _this8);
+ _newArrowCheck(this, _this7);
}.bind(this);
this.walkAndSkipNestedComponents(rootEl, function (el) {
- _newArrowCheck(this, _this8);
+ _newArrowCheck(this, _this7);
// Don't touch spawns from for loop (and check if the root is actually a for loop in a parent, don't skip it.)
if (el.__x_for_key !== undefined && !el.isSameNode(this.$el)) return false;
this.updateElement(el, extraVars);
}.bind(this), function (el) {
- _newArrowCheck(this, _this8);
+ _newArrowCheck(this, _this7);
el.__x = new Component(el);
}.bind(this));
@@ -6557,45 +6564,45 @@
}, {
key: "executeAndClearRemainingShowDirectiveStack",
value: function executeAndClearRemainingShowDirectiveStack() {
- var _this9 = this;
+ var _this8 = this;
// The goal here is to start all the x-show transitions
// and build a nested promise chain so that elements
// only hide when the children are finished hiding.
this.showDirectiveStack.reverse().map(function (thing) {
- var _this10 = this;
+ var _this9 = this;
- _newArrowCheck(this, _this9);
+ _newArrowCheck(this, _this8);
return new Promise(function (resolve) {
- var _this11 = this;
+ var _this10 = this;
- _newArrowCheck(this, _this10);
+ _newArrowCheck(this, _this9);
thing(function (finish) {
- _newArrowCheck(this, _this11);
+ _newArrowCheck(this, _this10);
resolve(finish);
}.bind(this));
}.bind(this));
}.bind(this)).reduce(function (nestedPromise, promise) {
- var _this12 = this;
+ var _this11 = this;
- _newArrowCheck(this, _this9);
+ _newArrowCheck(this, _this8);
return nestedPromise.then(function () {
- var _this13 = this;
+ var _this12 = this;
- _newArrowCheck(this, _this12);
+ _newArrowCheck(this, _this11);
return promise.then(function (finish) {
- _newArrowCheck(this, _this13);
+ _newArrowCheck(this, _this12);
return finish();
}.bind(this));
}.bind(this));
}.bind(this), Promise.resolve(function () {
- _newArrowCheck(this, _this9);
+ _newArrowCheck(this, _this8);
}.bind(this))); // We've processed the handler stack. let's clear it.
this.showDirectiveStack = [];
@@ -6609,7 +6616,7 @@
}, {
key: "registerListeners",
value: function registerListeners(el, extraVars) {
- var _this14 = this;
+ var _this13 = this;
getXAttrs(el).forEach(function (_ref) {
var type = _ref.type,
@@ -6617,7 +6624,7 @@
modifiers = _ref.modifiers,
expression = _ref.expression;
- _newArrowCheck(this, _this14);
+ _newArrowCheck(this, _this13);
switch (type) {
case 'on':
@@ -6633,20 +6640,20 @@
}, {
key: "resolveBoundAttributes",
value: function resolveBoundAttributes(el) {
- var _this15 = this;
+ var _this14 = this;
var initialUpdate = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var extraVars = arguments.length > 2 ? arguments[2] : undefined;
var attrs = getXAttrs(el);
attrs.forEach(function (_ref2) {
- var _this16 = this;
+ var _this15 = this;
var type = _ref2.type,
value = _ref2.value,
modifiers = _ref2.modifiers,
expression = _ref2.expression;
- _newArrowCheck(this, _this15);
+ _newArrowCheck(this, _this14);
switch (type) {
case 'model':
@@ -6682,7 +6689,7 @@
// If this element also has x-for on it, don't process x-if.
// We will let the "x-for" directive handle the "if"ing.
if (attrs.filter(function (i) {
- _newArrowCheck(this, _this16);
+ _newArrowCheck(this, _this15);
return i.type === 'for';
}.bind(this)).length > 0) return;
@@ -6703,10 +6710,10 @@
}, {
key: "evaluateReturnExpression",
value: function evaluateReturnExpression(el, expression) {
- var _this17 = this;
+ var _this16 = this;
var extraVars = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {
- _newArrowCheck(this, _this17);
+ _newArrowCheck(this, _this16);
}.bind(this);
return saferEval(expression, this.$data, _objectSpread2({}, extraVars(), {
$dispatch: this.getDispatchFunction(el)
@@ -6715,10 +6722,10 @@
}, {
key: "evaluateCommandExpression",
value: function evaluateCommandExpression(el, expression) {
- var _this18 = this;
+ var _this17 = this;
var extraVars = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () {
- _newArrowCheck(this, _this18);
+ _newArrowCheck(this, _this17);
}.bind(this);
return saferEvalNoReturn(expression, this.$data, _objectSpread2({}, extraVars(), {
$dispatch: this.getDispatchFunction(el)
@@ -6727,12 +6734,12 @@
}, {
key: "getDispatchFunction",
value: function getDispatchFunction(el) {
- var _this19 = this;
+ var _this18 = this;
return function (event) {
var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
- _newArrowCheck(this, _this19);
+ _newArrowCheck(this, _this18);
el.dispatchEvent(new CustomEvent(event, {
detail: detail,
@@ -6743,7 +6750,7 @@
}, {
key: "listenForNewElementsToInitialize",
value: function listenForNewElementsToInitialize() {
- var _this20 = this;
+ var _this19 = this;
var targetNode = this.$el;
var observerOptions = {
@@ -6752,9 +6759,9 @@
subtree: true
};
var observer = new MutationObserver(function (mutations) {
- var _this21 = this;
+ var _this20 = this;
- _newArrowCheck(this, _this20);
+ _newArrowCheck(this, _this19);
for (var i = 0; i < mutations.length; i++) {
// Filter out mutations triggered from child components.
@@ -6763,14 +6770,14 @@
if (mutations[i].type === 'attributes' && mutations[i].attributeName === 'x-data') {
(function () {
- var _this22 = this;
+ var _this21 = this;
var rawData = saferEval(mutations[i].target.getAttribute('x-data'), {});
Object.keys(rawData).forEach(function (key) {
- _newArrowCheck(this, _this22);
+ _newArrowCheck(this, _this21);
- if (_this21.$data[key] !== rawData[key]) {
- _this21.$data[key] = rawData[key];
+ if (_this20.$data[key] !== rawData[key]) {
+ _this20.$data[key] = rawData[key];
}
}.bind(this));
})();
@@ -6778,7 +6785,7 @@
if (mutations[i].addedNodes.length > 0) {
mutations[i].addedNodes.forEach(function (node) {
- _newArrowCheck(this, _this21);
+ _newArrowCheck(this, _this20);
if (node.nodeType !== 1 || node.__x_inserted_me) return;
@@ -6797,7 +6804,7 @@
}, {
key: "getRefsProxy",
value: function getRefsProxy() {
- var _this23 = this;
+ var _this22 = this;
var self = this;
var refObj = {};
@@ -6809,7 +6816,7 @@
// we just loop on the element, look for any x-ref and create a tmp property on a fake object.
this.walkAndSkipNestedComponents(self.$el, function (el) {
- _newArrowCheck(this, _this23);
+ _newArrowCheck(this, _this22);
if (el.hasAttribute('x-ref')) {
refObj[el.getAttribute('x-ref')] = true;
@@ -6823,14 +6830,14 @@
return new Proxy(refObj, {
get: function get(object, property) {
- var _this24 = this;
+ var _this23 = this;
if (property === '$isAlpineProxy') return true;
var ref; // We can't just query the DOM because it's hard to filter out refs in
// nested components.
self.walkAndSkipNestedComponents(self.$el, function (el) {
- _newArrowCheck(this, _this24);
+ _newArrowCheck(this, _this23);
if (el.hasAttribute('x-ref') && el.getAttribute('x-ref') === property) {
ref = el;
@@ -6840,6 +6847,36 @@
}
});
}
+ }, {
+ key: "walkToConstructWatcher",
+ value: function walkToConstructWatcher(path, parent, options) {
+ var child = parent[path[0]];
+
+ if (_typeof(child) === 'object' && path.length > 1) {
+ return this.walkToConstructWatcher(path.slice(1), child, options);
+ }
+
+ var deep = options.deep && _typeof(child) === 'object';
+ return {
+ target: deep ? child : parent,
+ key: path[0],
+ deep: deep,
+ callbacks: []
+ };
+ }
+ }, {
+ key: "foundUnderObjectGraph",
+ value: function foundUnderObjectGraph(needle, object, context) {
+ if (needle === object) return true;
+
+ for (var key in object) {
+ if (object.hasOwnProperty(key) && _typeof(object[key]) === 'object' && !key.startsWith('$')) {
+ if (context.foundUnderObjectGraph(needle, object[key], context)) return true;
+ }
+ }
+
+ return false;
+ }
}]);
return Component;
diff --git a/dist/alpine.js b/dist/alpine.js
index c67dab6f5..69ac393cb 100644
--- a/dist/alpine.js
+++ b/dist/alpine.js
@@ -1247,9 +1247,12 @@
this.watchers = {};
- this.unobservedData.$watch = (property, callback) => {
- if (!this.watchers[property]) this.watchers[property] = [];
- this.watchers[property].push(callback);
+ this.unobservedData.$watch = (property, callback, options = {}) => {
+ if (!this.watchers[property]) {
+ this.watchers[property] = this.walkToConstructWatcher(property.split('.'), this.unobservedData, options);
+ }
+
+ this.watchers[property].callbacks.push(callback);
};
this.showDirectiveStack = [];
@@ -1291,30 +1294,15 @@
var self = this;
let membrane = new ReactiveMembrane({
valueMutated(target, key) {
- if (self.watchers[key]) {
- // If there's a watcher for this specific key, run it.
- self.watchers[key].forEach(callback => callback(target[key]));
- } else {
- // Let's walk through the watchers with "dot-notation" (foo.bar) and see
- // if this mutation fits any of them.
- Object.keys(self.watchers).filter(i => i.includes('.')).forEach(fullDotNotationKey => {
- let dotNotationParts = fullDotNotationKey.split('.'); // If this dot-notation watcher's last "part" doesn't match the current
- // key, then skip it early for performance reasons.
-
- if (key !== dotNotationParts[dotNotationParts.length - 1]) return; // Now, walk through the dot-notation "parts" recursively to find
- // a match, and call the watcher if one's found.
-
- dotNotationParts.reduce((comparisonData, part) => {
- if (Object.is(target, comparisonData)) {
- // Run the watchers.
- self.watchers[fullDotNotationKey].forEach(callback => callback(target[key]));
- }
-
- return comparisonData[part];
- }, self.getUnobservedData());
- });
- } // Don't react to data changes for cases like the `x-created` hook.
-
+ Object.values(self.watchers).forEach(watcher => {
+ if (target === watcher.target && key === watcher.key) {
+ //Callback property watcher, return property value
+ watcher.callbacks.forEach(callback => callback(target[key]));
+ } else if (watcher.deep && self.foundUnderObjectGraph(target, watcher.target, self)) {
+ //Callback structure watcher, return structure value
+ watcher.callbacks.forEach(callback => callback(watcher.target));
+ }
+ }); // Don't react to data changes for cases like the `x-created` hook.
if (self.pauseReactivity) return;
debounce(() => {
@@ -1576,6 +1564,34 @@
});
}
+ walkToConstructWatcher(path, parent, options) {
+ var child = parent[path[0]];
+
+ if (typeof child === 'object' && path.length > 1) {
+ return this.walkToConstructWatcher(path.slice(1), child, options);
+ }
+
+ var deep = options.deep && typeof child === 'object';
+ return {
+ target: deep ? child : parent,
+ key: path[0],
+ deep: deep,
+ callbacks: []
+ };
+ }
+
+ foundUnderObjectGraph(needle, object, context) {
+ if (needle === object) return true;
+
+ for (var key in object) {
+ if (object.hasOwnProperty(key) && typeof object[key] === 'object' && !key.startsWith('$')) {
+ if (context.foundUnderObjectGraph(needle, object[key], context)) return true;
+ }
+ }
+
+ return false;
+ }
+
}
const Alpine = {
diff --git a/src/component.js b/src/component.js
index 2b2963293..48d6621be 100644
--- a/src/component.js
+++ b/src/component.js
@@ -42,10 +42,12 @@ export default class Component {
}
this.watchers = {}
- this.unobservedData.$watch = (property, callback) => {
- if (! this.watchers[property]) this.watchers[property] = []
+ this.unobservedData.$watch = (property, callback, options = {}) => {
+ if (! this.watchers[property]) {
+ this.watchers[property] = this.walkToConstructWatcher(property.split('.'), this.unobservedData, options)
+ }
- this.watchers[property].push(callback)
+ this.watchers[property].callbacks.push(callback)
}
this.showDirectiveStack = []
@@ -93,32 +95,16 @@ export default class Component {
let membrane = new ObservableMembrane({
valueMutated(target, key) {
- if (self.watchers[key]) {
- // If there's a watcher for this specific key, run it.
- self.watchers[key].forEach(callback => callback(target[key]))
- } else {
- // Let's walk through the watchers with "dot-notation" (foo.bar) and see
- // if this mutation fits any of them.
- Object.keys(self.watchers)
- .filter(i => i.includes('.'))
- .forEach(fullDotNotationKey => {
- let dotNotationParts = fullDotNotationKey.split('.')
-
- // If this dot-notation watcher's last "part" doesn't match the current
- // key, then skip it early for performance reasons.
- if (key !== dotNotationParts[dotNotationParts.length - 1]) return
-
- // Now, walk through the dot-notation "parts" recursively to find
- // a match, and call the watcher if one's found.
- dotNotationParts.reduce((comparisonData, part) => {
- if (Object.is(target, comparisonData)) {
- // Run the watchers.
- self.watchers[fullDotNotationKey].forEach(callback => callback(target[key]))
- }
- return comparisonData[part]
- }, self.getUnobservedData())
- })
- }
+ Object.values(self.watchers)
+ .forEach(watcher => {
+ if (target === watcher.target && key === watcher.key) {
+ //Callback property watcher, return property value
+ watcher.callbacks.forEach(callback => callback(target[key]))
+ } else if (watcher.deep && self.foundUnderObjectGraph(target, watcher.target, self)) {
+ //Callback structure watcher, return structure value
+ watcher.callbacks.forEach(callback => callback(watcher.target))
+ }
+ })
// Don't react to data changes for cases like the `x-created` hook.
if (self.pauseReactivity) return
@@ -414,4 +400,33 @@ export default class Component {
}
})
}
+
+ walkToConstructWatcher(path, parent, options) {
+ var child = parent[path[0]]
+
+ if (typeof child === 'object' && path.length > 1) {
+ return this.walkToConstructWatcher(path.slice(1), child, options)
+ }
+
+ var deep = (options.deep && typeof child === 'object')
+
+ return {
+ target: deep ? child : parent,
+ key: path[0],
+ deep: deep,
+ callbacks: []
+ }
+ }
+
+ foundUnderObjectGraph(needle, object, context) {
+ if (needle === object) return true
+
+ for (var key in object) {
+ if (object.hasOwnProperty(key) && typeof object[key] === 'object' && !key.startsWith('$')) {
+ if (context.foundUnderObjectGraph(needle, object[key], context)) return true
+ }
+ }
+
+ return false
+ }
}
diff --git a/test/watch.spec.js b/test/watch.spec.js
index 658bc9415..df5deb675 100644
--- a/test/watch.spec.js
+++ b/test/watch.spec.js
@@ -30,25 +30,62 @@ test('$watch', async () => {
test('$watch nested properties', async () => {
document.body.innerHTML = `
-