From 99dba892fdd26243c1cb3e4269189fe709293724 Mon Sep 17 00:00:00 2001 From: ahmedkandel Date: Thu, 19 Mar 2020 16:57:26 +0100 Subject: [PATCH 1/4] Fix $watch and Support nested properties Be more spesific on watch callbacks to fire only on correct level. Add support for nested properties using dot-delimited paths like Vue --- dist/alpine-ie11.js | 175 +++++++++++++++++++++++++++++--------------- dist/alpine.js | 21 +++++- src/component.js | 13 +++- src/utils.js | 10 +++ test/watch.spec.js | 27 ++++++- 5 files changed, 175 insertions(+), 71 deletions(-) diff --git a/dist/alpine-ie11.js b/dist/alpine-ie11.js index bea23868f..53d007fba 100644 --- a/dist/alpine-ie11.js +++ b/dist/alpine-ie11.js @@ -4429,10 +4429,48 @@ } }); - var HAS_SPECIES_SUPPORT$2 = arrayMethodHasSpeciesSupport('splice'); - var USES_TO_LENGTH$7 = arrayMethodUsesToLength('splice', { ACCESSORS: true, 0: 0, 1: 2 }); + var HAS_SPECIES_SUPPORT$2 = arrayMethodHasSpeciesSupport('slice'); + var USES_TO_LENGTH$7 = arrayMethodUsesToLength('slice', { ACCESSORS: true, 0: 0, 1: 2 }); + var SPECIES$6 = 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$7 }, { + 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$6]; + 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 HAS_SPECIES_SUPPORT$3 = arrayMethodHasSpeciesSupport('splice'); + var USES_TO_LENGTH$8 = arrayMethodUsesToLength('splice', { ACCESSORS: true, 0: 0, 1: 2 }); + + var max$2 = Math.max; var min$3 = Math.min; var MAX_SAFE_INTEGER$1 = 0x1FFFFFFFFFFFFF; var MAXIMUM_ALLOWED_LENGTH_EXCEEDED = 'Maximum allowed length exceeded'; @@ -4440,7 +4478,7 @@ // `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); @@ -4454,7 +4492,7 @@ actualDeleteCount = len - actualStart; } else { insertCount = argumentsLength - 2; - actualDeleteCount = min$3(max$1(toInteger(deleteCount), 0), len - actualStart); + actualDeleteCount = min$3(max$2(toInteger(deleteCount), 0), len - actualStart); } if (len + insertCount - actualDeleteCount > MAX_SAFE_INTEGER$1) { throw TypeError(MAXIMUM_ALLOWED_LENGTH_EXCEEDED); @@ -4693,7 +4731,7 @@ } }); - var max$2 = Math.max; + var max$3 = Math.max; var min$4 = Math.min; var floor$1 = Math.floor; var SUBSTITUTION_SYMBOLS = /\$([$&'`]|\d\d?|<[^>]*>)/g; @@ -4759,7 +4797,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$4(toInteger(result.index), S.length), 0); var captures = []; // NOTE: This is equivalent to // captures = result.slice(1).map(maybeToString) @@ -5330,6 +5368,15 @@ }.bind(this)); }.bind(this)); } + function getTargetFromPropertiesPath(path, parent, context) { + var child = parent[path[0]]; + + if (_typeof(child) === 'object' && path.length > 1) { + return getTargetFromPropertiesPath(path.slice(1), context.membrane.unwrapProxy(child), context); + } + + return _typeof(child) === 'object' ? child : parent; + } function isNumeric(subject) { return !isNaN(subject); @@ -6392,14 +6439,22 @@ valueMutated: function valueMutated(target, key) { var _this3 = this; - if (self.watchers[key]) { - self.watchers[key].forEach(function (callback) { - _newArrowCheck(this, _this3); + Object.keys(self.watchers).forEach(function (watcherKey) { + var _this4 = this; - return callback(target[key]); - }.bind(this)); - } // Don't react to data changes for cases like the `x-created` hook. + _newArrowCheck(this, _this3); + + var watcherKeyPath = watcherKey.split('.'); + var watcherTarget = getTargetFromPropertiesPath(watcherKeyPath, self.membrane.unwrapProxy(self.$data), self); + if (target === watcherTarget && key === watcherKeyPath[watcherKeyPath.length - 1]) { + self.watchers[watcherKey].forEach(function (callback) { + _newArrowCheck(this, _this4); + + return callback(target[key]); + }.bind(this)); + } + }.bind(this)); // Don't react to data changes for cases like the `x-created` hook. if (self.pauseReactivity) return; debounce(function () { @@ -6421,13 +6476,13 @@ }, { key: "walkAndSkipNestedComponents", value: function walkAndSkipNestedComponents(el, callback) { - var _this4 = this; + var _this5 = this; var initializeComponentCallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { - _newArrowCheck(this, _this4); + _newArrowCheck(this, _this5); }.bind(this); walk(el, function (el) { - _newArrowCheck(this, _this4); + _newArrowCheck(this, _this5); // We've hit a component. if (el.hasAttribute('x-data')) { @@ -6446,19 +6501,19 @@ }, { key: "initializeElements", value: function initializeElements(rootEl) { - var _this5 = this; + var _this6 = this; var extraVars = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () { - _newArrowCheck(this, _this5); + _newArrowCheck(this, _this6); }.bind(this); this.walkAndSkipNestedComponents(rootEl, function (el) { - _newArrowCheck(this, _this5); + _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, _this5); + _newArrowCheck(this, _this6); el.__x = new Component(el); }.bind(this)); @@ -6483,19 +6538,19 @@ }, { key: "updateElements", value: function updateElements(rootEl) { - var _this6 = this; + var _this7 = this; var extraVars = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () { - _newArrowCheck(this, _this6); + _newArrowCheck(this, _this7); }.bind(this); this.walkAndSkipNestedComponents(rootEl, function (el) { - _newArrowCheck(this, _this6); + _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, _this6); + _newArrowCheck(this, _this7); el.__x = new Component(el); }.bind(this)); @@ -6508,45 +6563,45 @@ }, { key: "executeAndClearRemainingShowDirectiveStack", value: function executeAndClearRemainingShowDirectiveStack() { - var _this7 = 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 _this8 = this; + var _this9 = this; - _newArrowCheck(this, _this7); + _newArrowCheck(this, _this8); return new Promise(function (resolve) { - var _this9 = this; + var _this10 = this; - _newArrowCheck(this, _this8); + _newArrowCheck(this, _this9); thing(function (finish) { - _newArrowCheck(this, _this9); + _newArrowCheck(this, _this10); resolve(finish); }.bind(this)); }.bind(this)); }.bind(this)).reduce(function (nestedPromise, promise) { - var _this10 = this; + var _this11 = this; - _newArrowCheck(this, _this7); + _newArrowCheck(this, _this8); return nestedPromise.then(function () { - var _this11 = this; + var _this12 = this; - _newArrowCheck(this, _this10); + _newArrowCheck(this, _this11); return promise.then(function (finish) { - _newArrowCheck(this, _this11); + _newArrowCheck(this, _this12); return finish(); }.bind(this)); }.bind(this)); }.bind(this), Promise.resolve(function () { - _newArrowCheck(this, _this7); + _newArrowCheck(this, _this8); }.bind(this))); // We've processed the handler stack. let's clear it. this.showDirectiveStack = []; @@ -6560,7 +6615,7 @@ }, { key: "registerListeners", value: function registerListeners(el, extraVars) { - var _this12 = this; + var _this13 = this; getXAttrs(el).forEach(function (_ref) { var type = _ref.type, @@ -6568,7 +6623,7 @@ modifiers = _ref.modifiers, expression = _ref.expression; - _newArrowCheck(this, _this12); + _newArrowCheck(this, _this13); switch (type) { case 'on': @@ -6584,20 +6639,20 @@ }, { key: "resolveBoundAttributes", value: function resolveBoundAttributes(el) { - var _this13 = 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 _this14 = this; + var _this15 = this; var type = _ref2.type, value = _ref2.value, modifiers = _ref2.modifiers, expression = _ref2.expression; - _newArrowCheck(this, _this13); + _newArrowCheck(this, _this14); switch (type) { case 'model': @@ -6633,7 +6688,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, _this15); return i.type === 'for'; }.bind(this)).length > 0) return; @@ -6654,10 +6709,10 @@ }, { key: "evaluateReturnExpression", value: function evaluateReturnExpression(el, expression) { - var _this15 = this; + var _this16 = this; var extraVars = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { - _newArrowCheck(this, _this15); + _newArrowCheck(this, _this16); }.bind(this); return saferEval(expression, this.$data, _objectSpread2({}, extraVars(), { $dispatch: this.getDispatchFunction(el) @@ -6666,10 +6721,10 @@ }, { key: "evaluateCommandExpression", value: function evaluateCommandExpression(el, expression) { - var _this16 = this; + var _this17 = this; var extraVars = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { - _newArrowCheck(this, _this16); + _newArrowCheck(this, _this17); }.bind(this); return saferEvalNoReturn(expression, this.$data, _objectSpread2({}, extraVars(), { $dispatch: this.getDispatchFunction(el) @@ -6678,12 +6733,12 @@ }, { key: "getDispatchFunction", value: function getDispatchFunction(el) { - var _this17 = this; + var _this18 = this; return function (event) { var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - _newArrowCheck(this, _this17); + _newArrowCheck(this, _this18); el.dispatchEvent(new CustomEvent(event, { detail: detail, @@ -6694,7 +6749,7 @@ }, { key: "listenForNewElementsToInitialize", value: function listenForNewElementsToInitialize() { - var _this18 = this; + var _this19 = this; var targetNode = this.$el; var observerOptions = { @@ -6703,9 +6758,9 @@ subtree: true }; var observer = new MutationObserver(function (mutations) { - var _this19 = this; + var _this20 = this; - _newArrowCheck(this, _this18); + _newArrowCheck(this, _this19); for (var i = 0; i < mutations.length; i++) { // Filter out mutations triggered from child components. @@ -6714,14 +6769,14 @@ if (mutations[i].type === 'attributes' && mutations[i].attributeName === 'x-data') { (function () { - var _this20 = this; + var _this21 = this; var rawData = saferEval(mutations[i].target.getAttribute('x-data'), {}); Object.keys(rawData).forEach(function (key) { - _newArrowCheck(this, _this20); + _newArrowCheck(this, _this21); - if (_this19.$data[key] !== rawData[key]) { - _this19.$data[key] = rawData[key]; + if (_this20.$data[key] !== rawData[key]) { + _this20.$data[key] = rawData[key]; } }.bind(this)); })(); @@ -6729,9 +6784,9 @@ if (mutations[i].addedNodes.length > 0) { mutations[i].addedNodes.forEach(function (node) { - _newArrowCheck(this, _this19); + _newArrowCheck(this, _this20); - if (node.nodeType !== 1) return; + if (node.nodeType !== 1 || node.__x_inserted_me) return; if (node.matches('[x-data]')) { node.__x = new Component(node); @@ -6748,7 +6803,7 @@ }, { key: "getRefsProxy", value: function getRefsProxy() { - var _this21 = this; + var _this22 = this; var self = this; var refObj = {}; @@ -6760,7 +6815,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, _this22); if (el.hasAttribute('x-ref')) { refObj[el.getAttribute('x-ref')] = true; @@ -6774,14 +6829,14 @@ return new Proxy(refObj, { get: function get(object, property) { - var _this22 = 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, _this22); + _newArrowCheck(this, _this23); if (el.hasAttribute('x-ref') && el.getAttribute('x-ref') === property) { ref = el; diff --git a/dist/alpine.js b/dist/alpine.js index b4f5edf37..d2ac1f543 100644 --- a/dist/alpine.js +++ b/dist/alpine.js @@ -389,6 +389,15 @@ }); }); } + function getTargetFromPropertiesPath(path, parent, context) { + var child = parent[path[0]]; + + if (typeof child === 'object' && path.length > 1) { + return getTargetFromPropertiesPath(path.slice(1), context.membrane.unwrapProxy(child), context); + } + + return typeof child === 'object' ? child : parent; + } function isNumeric(subject) { return !isNaN(subject); @@ -1291,10 +1300,14 @@ var self = this; let membrane = new ReactiveMembrane({ valueMutated(target, key) { - if (self.watchers[key]) { - self.watchers[key].forEach(callback => callback(target[key])); - } // Don't react to data changes for cases like the `x-created` hook. + Object.keys(self.watchers).forEach(watcherKey => { + const watcherKeyPath = watcherKey.split('.'); + const watcherTarget = getTargetFromPropertiesPath(watcherKeyPath, self.membrane.unwrapProxy(self.$data), self); + if (target === watcherTarget && key === watcherKeyPath[watcherKeyPath.length - 1]) { + self.watchers[watcherKey].forEach(callback => callback(target[key])); + } + }); // Don't react to data changes for cases like the `x-created` hook. if (self.pauseReactivity) return; debounce(() => { @@ -1516,7 +1529,7 @@ if (mutations[i].addedNodes.length > 0) { mutations[i].addedNodes.forEach(node => { - if (node.nodeType !== 1) return; + if (node.nodeType !== 1 || node.__x_inserted_me) return; if (node.matches('[x-data]')) { node.__x = new Component(node); diff --git a/src/component.js b/src/component.js index 99cdfa0fa..54185ef49 100644 --- a/src/component.js +++ b/src/component.js @@ -1,4 +1,4 @@ -import { walk, saferEval, saferEvalNoReturn, getXAttrs, debounce } from './utils' +import { walk, saferEval, saferEvalNoReturn, getXAttrs, debounce, getTargetFromPropertiesPath } from './utils' import { handleForDirective } from './directives/for' import { handleAttributeBindingDirective } from './directives/bind' import { handleShowDirective } from './directives/show' @@ -93,9 +93,14 @@ export default class Component { let membrane = new ObservableMembrane({ valueMutated(target, key) { - if (self.watchers[key]) { - self.watchers[key].forEach(callback => callback(target[key])) - } + Object.keys(self.watchers).forEach(watcherKey => { + const watcherKeyPath = watcherKey.split('.') + const watcherTarget = getTargetFromPropertiesPath(watcherKeyPath, self.membrane.unwrapProxy(self.$data), self) + + if (target === watcherTarget && key === watcherKeyPath[watcherKeyPath.length - 1]) { + self.watchers[watcherKey].forEach(callback => callback(target[key])) + } + }) // Don't react to data changes for cases like the `x-created` hook. if (self.pauseReactivity) return diff --git a/src/utils.js b/src/utils.js index aca04d8f9..f56aa3519 100644 --- a/src/utils.js +++ b/src/utils.js @@ -385,6 +385,16 @@ export function transition(el, stages) { }); } +export function getTargetFromPropertiesPath(path, parent, context) { + var child = parent[path[0]] + + if (typeof child === 'object' && path.length > 1) { + return getTargetFromPropertiesPath(path.slice(1), context.membrane.unwrapProxy(child), context) + } + + return typeof child === 'object' ? child : parent +} + function isNumeric(subject){ return ! isNaN(subject) } diff --git a/test/watch.spec.js b/test/watch.spec.js index edad2419d..d3de8a213 100644 --- a/test/watch.spec.js +++ b/test/watch.spec.js @@ -5,13 +5,21 @@ global.MutationObserver = class { observe() {} } -test('$watch', async () => { +test('$watch direct and nested properties', async () => { document.body.innerHTML = ` -
+

+ +

+

+ +
` @@ -19,11 +27,24 @@ test('$watch', async () => { expect(document.querySelector('h1').innerText).toEqual('bar') expect(document.querySelector('h2').innerText).toEqual('lob') + expect(document.querySelector('h3').innerText).toEqual('quux') + expect(document.querySelector('h4').innerText).toEqual('corge') + + document.querySelectorAll('button')[0].click() + + await wait(() => { + expect(document.querySelector('h1').innerText).toEqual('baz') + expect(document.querySelector('h2').innerText).toEqual('baz') + expect(document.querySelector('h3').innerText).toEqual('quux') + expect(document.querySelector('h4').innerText).toEqual('corge') + }) - document.querySelector('button').click() + document.querySelectorAll('button')[1].click() await wait(() => { expect(document.querySelector('h1').innerText).toEqual('baz') expect(document.querySelector('h2').innerText).toEqual('baz') + expect(document.querySelector('h3').innerText).toEqual('biz') + expect(document.querySelector('h4').innerText).toEqual('biz') }) }) From 6ce427874e750780d478e35ce489b87033183e53 Mon Sep 17 00:00:00 2001 From: Caleb Porzio Date: Thu, 19 Mar 2020 12:47:35 -0400 Subject: [PATCH 2/4] Isolate $watch tests --- test/watch.spec.js | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/test/watch.spec.js b/test/watch.spec.js index d3de8a213..658bc9415 100644 --- a/test/watch.spec.js +++ b/test/watch.spec.js @@ -5,21 +5,13 @@ global.MutationObserver = class { observe() {} } -test('$watch direct and nested properties', async () => { +test('$watch', async () => { document.body.innerHTML = ` -
+

- -

-

- -
` @@ -27,24 +19,36 @@ test('$watch direct and nested properties', async () => { expect(document.querySelector('h1').innerText).toEqual('bar') expect(document.querySelector('h2').innerText).toEqual('lob') - expect(document.querySelector('h3').innerText).toEqual('quux') - expect(document.querySelector('h4').innerText).toEqual('corge') - document.querySelectorAll('button')[0].click() + document.querySelector('button').click() await wait(() => { expect(document.querySelector('h1').innerText).toEqual('baz') expect(document.querySelector('h2').innerText).toEqual('baz') - expect(document.querySelector('h3').innerText).toEqual('quux') - expect(document.querySelector('h4').innerText).toEqual('corge') }) +}) + +test('$watch nested properties', async () => { + document.body.innerHTML = ` +
+

+

+ + +
+ ` + + Alpine.start() - document.querySelectorAll('button')[1].click() + 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('baz') - expect(document.querySelector('h2').innerText).toEqual('baz') - expect(document.querySelector('h3').innerText).toEqual('biz') - expect(document.querySelector('h4').innerText).toEqual('biz') + expect(document.querySelector('h1').innerText).toEqual('law') + expect(document.querySelector('h2').innerText).toEqual('law') }) }) From 9026e4e8cde920cd2c1fcc970a5d45660ef46db0 Mon Sep 17 00:00:00 2001 From: Caleb Porzio Date: Thu, 19 Mar 2020 14:34:31 -0400 Subject: [PATCH 3/4] Refactor nested watcher code --- dist/alpine-ie11.js | 292 ++++++++++++++++++++++---------------------- dist/alpine.js | 39 +++--- examples/index.html | 5 + src/component.js | 36 ++++-- src/utils.js | 10 -- 5 files changed, 198 insertions(+), 184 deletions(-) diff --git a/dist/alpine-ie11.js b/dist/alpine-ie11.js index 53d007fba..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; @@ -4429,48 +4471,10 @@ } }); - var HAS_SPECIES_SUPPORT$2 = arrayMethodHasSpeciesSupport('slice'); - var USES_TO_LENGTH$7 = arrayMethodUsesToLength('slice', { ACCESSORS: true, 0: 0, 1: 2 }); + var HAS_SPECIES_SUPPORT$2 = arrayMethodHasSpeciesSupport('splice'); + var USES_TO_LENGTH$7 = arrayMethodUsesToLength('splice', { ACCESSORS: true, 0: 0, 1: 2 }); - var SPECIES$6 = 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$7 }, { - 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$6]; - 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 HAS_SPECIES_SUPPORT$3 = arrayMethodHasSpeciesSupport('splice'); - var USES_TO_LENGTH$8 = arrayMethodUsesToLength('splice', { ACCESSORS: true, 0: 0, 1: 2 }); - - var max$2 = Math.max; var min$3 = Math.min; var MAX_SAFE_INTEGER$1 = 0x1FFFFFFFFFFFFF; var MAXIMUM_ALLOWED_LENGTH_EXCEEDED = 'Maximum allowed length exceeded'; @@ -4478,7 +4482,7 @@ // `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$3 || !USES_TO_LENGTH$8 }, { + _export({ target: 'Array', proto: true, forced: !HAS_SPECIES_SUPPORT$2 || !USES_TO_LENGTH$7 }, { splice: function splice(start, deleteCount /* , ...items */) { var O = toObject(this); var len = toLength(O.length); @@ -4492,7 +4496,7 @@ actualDeleteCount = len - actualStart; } else { insertCount = argumentsLength - 2; - actualDeleteCount = min$3(max$2(toInteger(deleteCount), 0), len - actualStart); + actualDeleteCount = min$3(max$1(toInteger(deleteCount), 0), len - actualStart); } if (len + insertCount - actualDeleteCount > MAX_SAFE_INTEGER$1) { throw TypeError(MAXIMUM_ALLOWED_LENGTH_EXCEEDED); @@ -4702,36 +4706,7 @@ } }); - 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$3 = Math.max; + var max$2 = Math.max; var min$4 = Math.min; var floor$1 = Math.floor; var SUBSTITUTION_SYMBOLS = /\$([$&'`]|\d\d?|<[^>]*>)/g; @@ -4797,7 +4772,7 @@ result = results[i]; var matched = String(result[0]); - var position = max$3(min$4(toInteger(result.index), S.length), 0); + var position = max$2(min$4(toInteger(result.index), S.length), 0); var captures = []; // NOTE: This is equivalent to // captures = result.slice(1).map(maybeToString) @@ -5368,15 +5343,6 @@ }.bind(this)); }.bind(this)); } - function getTargetFromPropertiesPath(path, parent, context) { - var child = parent[path[0]]; - - if (_typeof(child) === 'object' && path.length > 1) { - return getTargetFromPropertiesPath(path.slice(1), context.membrane.unwrapProxy(child), context); - } - - return _typeof(child) === 'object' ? child : parent; - } function isNumeric(subject) { return !isNaN(subject); @@ -6439,22 +6405,50 @@ valueMutated: function valueMutated(target, key) { var _this3 = this; - Object.keys(self.watchers).forEach(function (watcherKey) { - var _this4 = 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); - _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. - var watcherKeyPath = watcherKey.split('.'); - var watcherTarget = getTargetFromPropertiesPath(watcherKeyPath, self.membrane.unwrapProxy(self.$data), self); + 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; - if (target === watcherTarget && key === watcherKeyPath[watcherKeyPath.length - 1]) { - self.watchers[watcherKey].forEach(function (callback) { _newArrowCheck(this, _this4); - return callback(target[key]); - }.bind(this)); - } - }.bind(this)); // Don't react to data changes for cases like the `x-created` hook. + 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. + if (self.pauseReactivity) return; debounce(function () { @@ -6476,13 +6470,13 @@ }, { key: "walkAndSkipNestedComponents", value: function walkAndSkipNestedComponents(el, callback) { - var _this5 = this; + var _this6 = this; var initializeComponentCallback = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { - _newArrowCheck(this, _this5); + _newArrowCheck(this, _this6); }.bind(this); walk(el, function (el) { - _newArrowCheck(this, _this5); + _newArrowCheck(this, _this6); // We've hit a component. if (el.hasAttribute('x-data')) { @@ -6501,19 +6495,19 @@ }, { key: "initializeElements", value: function initializeElements(rootEl) { - var _this6 = this; + var _this7 = this; var extraVars = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () { - _newArrowCheck(this, _this6); + _newArrowCheck(this, _this7); }.bind(this); this.walkAndSkipNestedComponents(rootEl, function (el) { - _newArrowCheck(this, _this6); + _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, _this6); + _newArrowCheck(this, _this7); el.__x = new Component(el); }.bind(this)); @@ -6538,19 +6532,19 @@ }, { key: "updateElements", value: function updateElements(rootEl) { - var _this7 = this; + var _this8 = this; var extraVars = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : function () { - _newArrowCheck(this, _this7); + _newArrowCheck(this, _this8); }.bind(this); this.walkAndSkipNestedComponents(rootEl, function (el) { - _newArrowCheck(this, _this7); + _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, _this7); + _newArrowCheck(this, _this8); el.__x = new Component(el); }.bind(this)); @@ -6563,45 +6557,45 @@ }, { key: "executeAndClearRemainingShowDirectiveStack", value: function executeAndClearRemainingShowDirectiveStack() { - var _this8 = 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 _this9 = this; + var _this10 = this; - _newArrowCheck(this, _this8); + _newArrowCheck(this, _this9); return new Promise(function (resolve) { - var _this10 = this; + var _this11 = this; - _newArrowCheck(this, _this9); + _newArrowCheck(this, _this10); thing(function (finish) { - _newArrowCheck(this, _this10); + _newArrowCheck(this, _this11); resolve(finish); }.bind(this)); }.bind(this)); }.bind(this)).reduce(function (nestedPromise, promise) { - var _this11 = this; + var _this12 = this; - _newArrowCheck(this, _this8); + _newArrowCheck(this, _this9); return nestedPromise.then(function () { - var _this12 = this; + var _this13 = this; - _newArrowCheck(this, _this11); + _newArrowCheck(this, _this12); return promise.then(function (finish) { - _newArrowCheck(this, _this12); + _newArrowCheck(this, _this13); return finish(); }.bind(this)); }.bind(this)); }.bind(this), Promise.resolve(function () { - _newArrowCheck(this, _this8); + _newArrowCheck(this, _this9); }.bind(this))); // We've processed the handler stack. let's clear it. this.showDirectiveStack = []; @@ -6615,7 +6609,7 @@ }, { key: "registerListeners", value: function registerListeners(el, extraVars) { - var _this13 = this; + var _this14 = this; getXAttrs(el).forEach(function (_ref) { var type = _ref.type, @@ -6623,7 +6617,7 @@ modifiers = _ref.modifiers, expression = _ref.expression; - _newArrowCheck(this, _this13); + _newArrowCheck(this, _this14); switch (type) { case 'on': @@ -6639,20 +6633,20 @@ }, { key: "resolveBoundAttributes", value: function resolveBoundAttributes(el) { - var _this14 = 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 _this15 = this; + var _this16 = this; var type = _ref2.type, value = _ref2.value, modifiers = _ref2.modifiers, expression = _ref2.expression; - _newArrowCheck(this, _this14); + _newArrowCheck(this, _this15); switch (type) { case 'model': @@ -6688,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, _this15); + _newArrowCheck(this, _this16); return i.type === 'for'; }.bind(this)).length > 0) return; @@ -6709,10 +6703,10 @@ }, { key: "evaluateReturnExpression", value: function evaluateReturnExpression(el, expression) { - var _this16 = this; + var _this17 = this; var extraVars = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { - _newArrowCheck(this, _this16); + _newArrowCheck(this, _this17); }.bind(this); return saferEval(expression, this.$data, _objectSpread2({}, extraVars(), { $dispatch: this.getDispatchFunction(el) @@ -6721,10 +6715,10 @@ }, { key: "evaluateCommandExpression", value: function evaluateCommandExpression(el, expression) { - var _this17 = this; + var _this18 = this; var extraVars = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : function () { - _newArrowCheck(this, _this17); + _newArrowCheck(this, _this18); }.bind(this); return saferEvalNoReturn(expression, this.$data, _objectSpread2({}, extraVars(), { $dispatch: this.getDispatchFunction(el) @@ -6733,12 +6727,12 @@ }, { key: "getDispatchFunction", value: function getDispatchFunction(el) { - var _this18 = this; + var _this19 = this; return function (event) { var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; - _newArrowCheck(this, _this18); + _newArrowCheck(this, _this19); el.dispatchEvent(new CustomEvent(event, { detail: detail, @@ -6749,7 +6743,7 @@ }, { key: "listenForNewElementsToInitialize", value: function listenForNewElementsToInitialize() { - var _this19 = this; + var _this20 = this; var targetNode = this.$el; var observerOptions = { @@ -6758,9 +6752,9 @@ subtree: true }; var observer = new MutationObserver(function (mutations) { - var _this20 = this; + var _this21 = this; - _newArrowCheck(this, _this19); + _newArrowCheck(this, _this20); for (var i = 0; i < mutations.length; i++) { // Filter out mutations triggered from child components. @@ -6769,14 +6763,14 @@ if (mutations[i].type === 'attributes' && mutations[i].attributeName === 'x-data') { (function () { - var _this21 = this; + var _this22 = this; var rawData = saferEval(mutations[i].target.getAttribute('x-data'), {}); Object.keys(rawData).forEach(function (key) { - _newArrowCheck(this, _this21); + _newArrowCheck(this, _this22); - if (_this20.$data[key] !== rawData[key]) { - _this20.$data[key] = rawData[key]; + if (_this21.$data[key] !== rawData[key]) { + _this21.$data[key] = rawData[key]; } }.bind(this)); })(); @@ -6784,7 +6778,7 @@ if (mutations[i].addedNodes.length > 0) { mutations[i].addedNodes.forEach(function (node) { - _newArrowCheck(this, _this20); + _newArrowCheck(this, _this21); if (node.nodeType !== 1 || node.__x_inserted_me) return; @@ -6803,7 +6797,7 @@ }, { key: "getRefsProxy", value: function getRefsProxy() { - var _this22 = this; + var _this23 = this; var self = this; var refObj = {}; @@ -6815,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, _this22); + _newArrowCheck(this, _this23); if (el.hasAttribute('x-ref')) { refObj[el.getAttribute('x-ref')] = true; @@ -6829,14 +6823,14 @@ return new Proxy(refObj, { get: function get(object, property) { - var _this23 = 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, _this23); + _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 d2ac1f543..c67dab6f5 100644 --- a/dist/alpine.js +++ b/dist/alpine.js @@ -389,15 +389,6 @@ }); }); } - function getTargetFromPropertiesPath(path, parent, context) { - var child = parent[path[0]]; - - if (typeof child === 'object' && path.length > 1) { - return getTargetFromPropertiesPath(path.slice(1), context.membrane.unwrapProxy(child), context); - } - - return typeof child === 'object' ? child : parent; - } function isNumeric(subject) { return !isNaN(subject); @@ -1300,14 +1291,30 @@ var self = this; let membrane = new ReactiveMembrane({ valueMutated(target, key) { - Object.keys(self.watchers).forEach(watcherKey => { - const watcherKeyPath = watcherKey.split('.'); - const watcherTarget = getTargetFromPropertiesPath(watcherKeyPath, self.membrane.unwrapProxy(self.$data), self); + 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. - if (target === watcherTarget && key === watcherKeyPath[watcherKeyPath.length - 1]) { - self.watchers[watcherKey].forEach(callback => callback(target[key])); - } - }); // Don't react to data changes for cases like the `x-created` hook. if (self.pauseReactivity) return; debounce(() => { diff --git a/examples/index.html b/examples/index.html index 0b4748338..58aca1c83 100644 --- a/examples/index.html +++ b/examples/index.html @@ -20,6 +20,11 @@ +
+ + +
+ diff --git a/src/component.js b/src/component.js index 54185ef49..2b2963293 100644 --- a/src/component.js +++ b/src/component.js @@ -1,4 +1,4 @@ -import { walk, saferEval, saferEvalNoReturn, getXAttrs, debounce, getTargetFromPropertiesPath } from './utils' +import { walk, saferEval, saferEvalNoReturn, getXAttrs, debounce } from './utils' import { handleForDirective } from './directives/for' import { handleAttributeBindingDirective } from './directives/bind' import { handleShowDirective } from './directives/show' @@ -93,14 +93,32 @@ export default class Component { let membrane = new ObservableMembrane({ valueMutated(target, key) { - Object.keys(self.watchers).forEach(watcherKey => { - const watcherKeyPath = watcherKey.split('.') - const watcherTarget = getTargetFromPropertiesPath(watcherKeyPath, self.membrane.unwrapProxy(self.$data), self) - - if (target === watcherTarget && key === watcherKeyPath[watcherKeyPath.length - 1]) { - self.watchers[watcherKey].forEach(callback => callback(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. if (self.pauseReactivity) return diff --git a/src/utils.js b/src/utils.js index f56aa3519..aca04d8f9 100644 --- a/src/utils.js +++ b/src/utils.js @@ -385,16 +385,6 @@ export function transition(el, stages) { }); } -export function getTargetFromPropertiesPath(path, parent, context) { - var child = parent[path[0]] - - if (typeof child === 'object' && path.length > 1) { - return getTargetFromPropertiesPath(path.slice(1), context.membrane.unwrapProxy(child), context) - } - - return typeof child === 'object' ? child : parent -} - function isNumeric(subject){ return ! isNaN(subject) } From 20398b8bb3e4f5c6f0f9231d474a420435f991a2 Mon Sep 17 00:00:00 2001 From: Caleb Porzio Date: Thu, 19 Mar 2020 14:36:17 -0400 Subject: [PATCH 4/4] Revert index.html changes --- examples/index.html | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/index.html b/examples/index.html index 58aca1c83..0b4748338 100644 --- a/examples/index.html +++ b/examples/index.html @@ -20,11 +20,6 @@ -
- - -
-