diff --git a/dist/alpine-ie11.js b/dist/alpine-ie11.js index 5bf0c7c64..4097a1d18 100644 --- a/dist/alpine-ie11.js +++ b/dist/alpine-ie11.js @@ -3200,6 +3200,19 @@ } }); + // `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; + }; + + // `Object.is` method + // https://tc39.github.io/ecma262/#sec-object.is + _export({ target: 'Object', stat: true }, { + is: sameValue + }); + var FAILS_ON_PRIMITIVES = fails(function () { objectKeys(1); }); // `Object.keys` method @@ -4003,6 +4016,44 @@ 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 @@ -4192,15 +4243,6 @@ ]; }); - 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; @@ -4664,35 +4706,6 @@ } }); - 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); - } - }); - var max$2 = Math.max; var min$4 = Math.min; var floor$1 = Math.floor; @@ -6393,11 +6406,47 @@ 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); + + 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, _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. @@ -6421,13 +6470,13 @@ }, { key: "walkAndSkipNestedComponents", value: function walkAndSkipNestedComponents(el, callback) { - var _this4 = this; + var _this6 = this; var initializeComponentCallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { - _newArrowCheck(this, _this4); + _newArrowCheck(this, _this6); }.bind(this); walk(el, function (el) { - _newArrowCheck(this, _this4); + _newArrowCheck(this, _this6); // We've hit a component. if (el.hasAttribute('x-data')) { @@ -6446,19 +6495,19 @@ }, { key: "initializeElements", value: function initializeElements(rootEl) { - var _this5 = this; + var _this7 = this; var extraVars = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () { - _newArrowCheck(this, _this5); + _newArrowCheck(this, _this7); }.bind(this); this.walkAndSkipNestedComponents(rootEl, function (el) { - _newArrowCheck(this, _this5); + _newArrowCheck(this, _this7); // 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, _this5); + _newArrowCheck(this, _this7); el.__x = new Component(el); }.bind(this)); @@ -6483,19 +6532,19 @@ }, { key: "updateElements", value: function updateElements(rootEl) { - var _this6 = this; + var _this8 = this; var extraVars = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () { - _newArrowCheck(this, _this6); + _newArrowCheck(this, _this8); }.bind(this); this.walkAndSkipNestedComponents(rootEl, function (el) { - _newArrowCheck(this, _this6); + _newArrowCheck(this, _this8); // 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, _this6); + _newArrowCheck(this, _this8); el.__x = new Component(el); }.bind(this)); @@ -6508,45 +6557,45 @@ }, { key: "executeAndClearRemainingShowDirectiveStack", value: function executeAndClearRemainingShowDirectiveStack() { - var _this7 = this; + var _this9 = 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 _this8 = this; + var _this10 = this; - _newArrowCheck(this, _this7); + _newArrowCheck(this, _this9); return new Promise(function (resolve) { - var _this9 = this; + var _this11 = this; - _newArrowCheck(this, _this8); + _newArrowCheck(this, _this10); thing(function (finish) { - _newArrowCheck(this, _this9); + _newArrowCheck(this, _this11); resolve(finish); }.bind(this)); }.bind(this)); }.bind(this)).reduce(function (nestedPromise, promise) { - var _this10 = this; + var _this12 = this; - _newArrowCheck(this, _this7); + _newArrowCheck(this, _this9); return nestedPromise.then(function () { - var _this11 = this; + var _this13 = this; - _newArrowCheck(this, _this10); + _newArrowCheck(this, _this12); return promise.then(function (finish) { - _newArrowCheck(this, _this11); + _newArrowCheck(this, _this13); return finish(); }.bind(this)); }.bind(this)); }.bind(this), Promise.resolve(function () { - _newArrowCheck(this, _this7); + _newArrowCheck(this, _this9); }.bind(this))); // We've processed the handler stack. let's clear it. this.showDirectiveStack = []; @@ -6560,7 +6609,7 @@ }, { key: "registerListeners", value: function registerListeners(el, extraVars) { - var _this12 = this; + var _this14 = this; getXAttrs(el).forEach(function (_ref) { var type = _ref.type, @@ -6568,7 +6617,7 @@ modifiers = _ref.modifiers, expression = _ref.expression; - _newArrowCheck(this, _this12); + _newArrowCheck(this, _this14); switch (type) { case 'on': @@ -6584,20 +6633,20 @@ }, { key: "resolveBoundAttributes", value: function resolveBoundAttributes(el) { - var _this13 = this; + var _this15 = 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 _this14 = this; + var _this16 = this; var type = _ref2.type, value = _ref2.value, modifiers = _ref2.modifiers, expression = _ref2.expression; - _newArrowCheck(this, _this13); + _newArrowCheck(this, _this15); switch (type) { case 'model': @@ -6633,7 +6682,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, _this14); + _newArrowCheck(this, _this16); return i.type === 'for'; }.bind(this)).length > 0) return; @@ -6654,10 +6703,10 @@ }, { key: "evaluateReturnExpression", value: function evaluateReturnExpression(el, expression) { - var _this15 = this; + var _this17 = this; var extraVars = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { - _newArrowCheck(this, _this15); + _newArrowCheck(this, _this17); }.bind(this); return saferEval(expression, this.$data, _objectSpread2({}, extraVars(), { $dispatch: this.getDispatchFunction(el) @@ -6666,10 +6715,10 @@ }, { key: "evaluateCommandExpression", value: function evaluateCommandExpression(el, expression) { - var _this16 = this; + var _this18 = this; var extraVars = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { - _newArrowCheck(this, _this16); + _newArrowCheck(this, _this18); }.bind(this); return saferEvalNoReturn(expression, this.$data, _objectSpread2({}, extraVars(), { $dispatch: this.getDispatchFunction(el) @@ -6678,12 +6727,12 @@ }, { key: "getDispatchFunction", value: function getDispatchFunction(el) { - var _this17 = this; + var _this19 = this; return function (event) { var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - _newArrowCheck(this, _this17); + _newArrowCheck(this, _this19); el.dispatchEvent(new CustomEvent(event, { detail: detail, @@ -6694,7 +6743,7 @@ }, { key: "listenForNewElementsToInitialize", value: function listenForNewElementsToInitialize() { - var _this18 = this; + var _this20 = this; var targetNode = this.$el; var observerOptions = { @@ -6703,9 +6752,9 @@ subtree: true }; var observer = new MutationObserver(function (mutations) { - var _this19 = this; + var _this21 = this; - _newArrowCheck(this, _this18); + _newArrowCheck(this, _this20); for (var i = 0; i < mutations.length; i++) { // Filter out mutations triggered from child components. @@ -6714,14 +6763,14 @@ if (mutations[i].type === 'attributes' && mutations[i].attributeName === 'x-data') { (function () { - var _this20 = this; + var _this22 = this; var rawData = saferEval(mutations[i].target.getAttribute('x-data'), {}); Object.keys(rawData).forEach(function (key) { - _newArrowCheck(this, _this20); + _newArrowCheck(this, _this22); - if (_this19.$data[key] !== rawData[key]) { - _this19.$data[key] = rawData[key]; + if (_this21.$data[key] !== rawData[key]) { + _this21.$data[key] = rawData[key]; } }.bind(this)); })(); @@ -6729,7 +6778,7 @@ if (mutations[i].addedNodes.length > 0) { mutations[i].addedNodes.forEach(function (node) { - _newArrowCheck(this, _this19); + _newArrowCheck(this, _this21); if (node.nodeType !== 1 || node.__x_inserted_me) return; @@ -6748,7 +6797,7 @@ }, { key: "getRefsProxy", value: function getRefsProxy() { - var _this21 = this; + var _this23 = this; var self = this; var refObj = {}; @@ -6760,7 +6809,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, _this21); + _newArrowCheck(this, _this23); if (el.hasAttribute('x-ref')) { refObj[el.getAttribute('x-ref')] = true; @@ -6774,14 +6823,14 @@ return new Proxy(refObj, { get: function get(object, property) { - var _this22 = this; + var _this24 = 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, _this22); + _newArrowCheck(this, _this24); if (el.hasAttribute('x-ref') && el.getAttribute('x-ref') === property) { ref = el; diff --git a/dist/alpine.js b/dist/alpine.js index b2c8004c5..c67dab6f5 100644 --- a/dist/alpine.js +++ b/dist/alpine.js @@ -1292,7 +1292,27 @@ 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. diff --git a/src/component.js b/src/component.js index 99cdfa0fa..2b2963293 100644 --- a/src/component.js +++ b/src/component.js @@ -94,7 +94,30 @@ 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()) + }) } // Don't react to data changes for cases like the `x-created` hook. diff --git a/test/watch.spec.js b/test/watch.spec.js index edad2419d..658bc9415 100644 --- a/test/watch.spec.js +++ b/test/watch.spec.js @@ -27,3 +27,28 @@ test('$watch', async () => { expect(document.querySelector('h2').innerText).toEqual('baz') }) }) + +test('$watch nested properties', async () => { + document.body.innerHTML = ` +
+

+

+ + +
+ ` + + Alpine.start() + + expect(document.querySelector('h1').innerText).toEqual('baz') + expect(document.querySelector('h2').innerText).toEqual('lob') + + document.querySelector('button').click() + + await wait(() => { + expect(document.querySelector('h1').innerText).toEqual('law') + expect(document.querySelector('h2').innerText).toEqual('law') + }) +})