1
0
Эх сурвалжийг харах

Merge pull request #659 from alpinejs/enforce-directive-order

Sort directives for better predictability
Caleb Porzio 5 жил өмнө
parent
commit
3d81bc8360
5 өөрчлөгдсөн 129 нэмэгдсэн , 54 устгасан
  1. 47 34
      dist/alpine-ie11.js
  2. 10 1
      dist/alpine.js
  3. 13 2
      src/utils.js
  4. 45 17
      test/model.spec.js
  5. 14 0
      test/show.spec.js

+ 47 - 34
dist/alpine-ie11.js

@@ -5491,12 +5491,25 @@
 
       return i.type === type;
     }.bind(this));
-    return directives;
+    return sortDirectives(directives);
   }
 
-  function parseHtmlAttribute(_ref3) {
+  function sortDirectives(directives) {
     var _this4 = this;
 
+    var directiveOrder = ['bind', 'model', 'show', 'catch-all'];
+    return directives.sort(function (a, b) {
+      _newArrowCheck(this, _this4);
+
+      var typeA = directiveOrder.indexOf(a.type) === -1 ? 'catch-all' : a.type;
+      var typeB = directiveOrder.indexOf(b.type) === -1 ? 'catch-all' : b.type;
+      return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB);
+    }.bind(this));
+  }
+
+  function parseHtmlAttribute(_ref3) {
+    var _this5 = this;
+
     var name = _ref3.name,
         value = _ref3.value;
     var normalizedName = replaceAtAndColonWithStandardSyntax(name);
@@ -5507,7 +5520,7 @@
       type: typeMatch ? typeMatch[1] : null,
       value: valueMatch ? valueMatch[1] : null,
       modifiers: modifiers.map(function (i) {
-        _newArrowCheck(this, _this4);
+        _newArrowCheck(this, _this5);
 
         return i.replace('.', '');
       }.bind(this)),
@@ -5537,7 +5550,7 @@
   var TRANSITION_TYPE_IN = 'in';
   var TRANSITION_TYPE_OUT = 'out';
   function transitionIn(el, show, component) {
-    var _this5 = this;
+    var _this6 = this;
 
     var forceSkip = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
     // We don't want to transition on the initial page load.
@@ -5559,13 +5572,13 @@
       var settingBothSidesOfTransition = modifiers.includes('in') && modifiers.includes('out'); // If x-show.transition.in...out... only use "in" related modifiers for this transition.
 
       modifiers = settingBothSidesOfTransition ? modifiers.filter(function (i, index) {
-        _newArrowCheck(this, _this5);
+        _newArrowCheck(this, _this6);
 
         return index < modifiers.indexOf('out');
       }.bind(this)) : modifiers;
       transitionHelperIn(el, modifiers, show); // Otherwise, we can assume x-transition:enter.
     } else if (attrs.some(function (attr) {
-      _newArrowCheck(this, _this5);
+      _newArrowCheck(this, _this6);
 
       return ['enter', 'enter-start', 'enter-end'].includes(attr.value);
     }.bind(this))) {
@@ -5576,7 +5589,7 @@
     }
   }
   function transitionOut(el, hide, component) {
-    var _this6 = this;
+    var _this7 = this;
 
     var forceSkip = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
     // We don't want to transition on the initial page load.
@@ -5596,13 +5609,13 @@
       if (modifiers.includes('in') && !modifiers.includes('out')) return hide();
       var settingBothSidesOfTransition = modifiers.includes('in') && modifiers.includes('out');
       modifiers = settingBothSidesOfTransition ? modifiers.filter(function (i, index) {
-        _newArrowCheck(this, _this6);
+        _newArrowCheck(this, _this7);
 
         return index > modifiers.indexOf('out');
       }.bind(this)) : modifiers;
       transitionHelperOut(el, modifiers, settingBothSidesOfTransition, hide);
     } else if (attrs.some(function (attr) {
-      _newArrowCheck(this, _this6);
+      _newArrowCheck(this, _this7);
 
       return ['leave', 'leave-start', 'leave-end'].includes(attr.value);
     }.bind(this))) {
@@ -5612,7 +5625,7 @@
     }
   }
   function transitionHelperIn(el, modifiers, showCallback) {
-    var _this7 = this;
+    var _this8 = this;
 
     // Default values inspired by: https://material.io/design/motion/speed.html#duration
     var styleValues = {
@@ -5628,11 +5641,11 @@
       }
     };
     transitionHelper(el, modifiers, showCallback, function () {
-      _newArrowCheck(this, _this7);
+      _newArrowCheck(this, _this8);
     }.bind(this), styleValues, TRANSITION_TYPE_IN);
   }
   function transitionHelperOut(el, modifiers, settingBothSidesOfTransition, hideCallback) {
-    var _this8 = this;
+    var _this9 = this;
 
     // Make the "out" transition .5x slower than the "in". (Visually better)
     // HOWEVER, if they explicitly set a duration for the "out" transition,
@@ -5651,7 +5664,7 @@
       }
     };
     transitionHelper(el, modifiers, function () {
-      _newArrowCheck(this, _this8);
+      _newArrowCheck(this, _this9);
     }.bind(this), hideCallback, styleValues, TRANSITION_TYPE_OUT);
   }
 
@@ -5736,65 +5749,65 @@
     transition(el, stages, type);
   }
   function transitionClassesIn(el, component, directives, showCallback) {
-    var _this9 = this;
+    var _this10 = this;
 
     var ensureStringExpression = function ensureStringExpression(expression) {
-      _newArrowCheck(this, _this9);
+      _newArrowCheck(this, _this10);
 
       return typeof expression === 'function' ? component.evaluateReturnExpression(el, expression) : expression;
     }.bind(this);
 
     var enter = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
-      _newArrowCheck(this, _this9);
+      _newArrowCheck(this, _this10);
 
       return i.value === 'enter';
     }.bind(this)) || {
       expression: ''
     }).expression));
     var enterStart = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
-      _newArrowCheck(this, _this9);
+      _newArrowCheck(this, _this10);
 
       return i.value === 'enter-start';
     }.bind(this)) || {
       expression: ''
     }).expression));
     var enterEnd = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
-      _newArrowCheck(this, _this9);
+      _newArrowCheck(this, _this10);
 
       return i.value === 'enter-end';
     }.bind(this)) || {
       expression: ''
     }).expression));
     transitionClasses(el, enter, enterStart, enterEnd, showCallback, function () {
-      _newArrowCheck(this, _this9);
+      _newArrowCheck(this, _this10);
     }.bind(this), TRANSITION_TYPE_IN);
   }
   function transitionClassesOut(el, component, directives, hideCallback) {
-    var _this10 = this;
+    var _this11 = this;
 
     var leave = convertClassStringToArray((directives.find(function (i) {
-      _newArrowCheck(this, _this10);
+      _newArrowCheck(this, _this11);
 
       return i.value === 'leave';
     }.bind(this)) || {
       expression: ''
     }).expression);
     var leaveStart = convertClassStringToArray((directives.find(function (i) {
-      _newArrowCheck(this, _this10);
+      _newArrowCheck(this, _this11);
 
       return i.value === 'leave-start';
     }.bind(this)) || {
       expression: ''
     }).expression);
     var leaveEnd = convertClassStringToArray((directives.find(function (i) {
-      _newArrowCheck(this, _this10);
+      _newArrowCheck(this, _this11);
 
       return i.value === 'leave-end';
     }.bind(this)) || {
       expression: ''
     }).expression);
     transitionClasses(el, leave, leaveStart, leaveEnd, function () {
-      _newArrowCheck(this, _this10);
+      _newArrowCheck(this, _this11);
     }.bind(this), hideCallback, TRANSITION_TYPE_OUT);
   }
   function transitionClasses(el, classesDuring, classesStart, classesEnd, hook1, hook2, type) {
@@ -5821,12 +5834,12 @@
       },
       end: function end() {
         var _el$classList3,
-            _this11 = this,
+            _this12 = this,
             _el$classList4;
 
         // Don't remove classes that were in the original class attribute.
         (_el$classList3 = el.classList).remove.apply(_el$classList3, _toConsumableArray(classesStart.filter(function (i) {
-          _newArrowCheck(this, _this11);
+          _newArrowCheck(this, _this12);
 
           return !originalClasses.includes(i);
         }.bind(this))));
@@ -5838,17 +5851,17 @@
       },
       cleanup: function cleanup() {
         var _el$classList5,
-            _this12 = this,
+            _this13 = this,
             _el$classList6;
 
         (_el$classList5 = el.classList).remove.apply(_el$classList5, _toConsumableArray(classesDuring.filter(function (i) {
-          _newArrowCheck(this, _this12);
+          _newArrowCheck(this, _this13);
 
           return !originalClasses.includes(i);
         }.bind(this))));
 
         (_el$classList6 = el.classList).remove.apply(_el$classList6, _toConsumableArray(classesEnd.filter(function (i) {
-          _newArrowCheck(this, _this12);
+          _newArrowCheck(this, _this13);
 
           return !originalClasses.includes(i);
         }.bind(this))));
@@ -5857,7 +5870,7 @@
     transition(el, stages, type);
   }
   function transition(el, stages, type) {
-    var _this13 = this;
+    var _this14 = this;
 
     el.__x_transition = {
       // Set transition type so we can avoid clearing transition if the direction is the same
@@ -5866,7 +5879,7 @@
       // from different point and early terminate it. Once will ensure that function
       // is only called one time.
       callback: once(function () {
-        _newArrowCheck(this, _this13);
+        _newArrowCheck(this, _this14);
 
         stages.hide(); // Adding an "isConnected" check, in case the callback
         // removed the element from the DOM.
@@ -5883,9 +5896,9 @@
     stages.start();
     stages.during();
     el.__x_transition.nextFrame = requestAnimationFrame(function () {
-      var _this14 = this;
+      var _this15 = this;
 
-      _newArrowCheck(this, _this13);
+      _newArrowCheck(this, _this14);
 
       // Note: Safari's transitionDuration property will list out comma separated transition durations
       // for every single transition property. Let's grab the first one and call it a day.
@@ -5897,7 +5910,7 @@
 
       stages.show();
       el.__x_transition.nextFrame = requestAnimationFrame(function () {
-        _newArrowCheck(this, _this14);
+        _newArrowCheck(this, _this15);
 
         stages.end();
         setTimeout(el.__x_transition.callback, duration);

+ 10 - 1
dist/alpine.js

@@ -151,7 +151,16 @@
     }
 
     if (type) return directives.filter(i => i.type === type);
-    return directives;
+    return sortDirectives(directives);
+  }
+
+  function sortDirectives(directives) {
+    let directiveOrder = ['bind', 'model', 'show', 'catch-all'];
+    return directives.sort((a, b) => {
+      let typeA = directiveOrder.indexOf(a.type) === -1 ? 'catch-all' : a.type;
+      let typeB = directiveOrder.indexOf(b.type) === -1 ? 'catch-all' : b.type;
+      return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB);
+    });
   }
 
   function parseHtmlAttribute({

+ 13 - 2
src/utils.js

@@ -113,10 +113,21 @@ export function getXAttrs(el, component, type) {
         // Add x-spread directives to the pile of existing directives.
         directives = directives.concat(Object.entries(spreadObject).map(([name, value]) => parseHtmlAttribute({ name, value })))
     }
-    
+
     if (type) return directives.filter(i => i.type === type)
 
-    return directives;
+    return sortDirectives(directives)
+}
+
+function sortDirectives(directives) {
+    let directiveOrder = ['bind', 'model', 'show', 'catch-all']
+
+    return directives.sort((a, b) => {
+        let typeA = directiveOrder.indexOf(a.type) === -1 ? 'catch-all' : a.type
+        let typeB = directiveOrder.indexOf(b.type) === -1 ? 'catch-all' : b.type
+
+        return directiveOrder.indexOf(typeA) - directiveOrder.indexOf(typeB)
+    })
 }
 
 function parseHtmlAttribute({ name, value }) {

+ 45 - 17
test/model.spec.js

@@ -353,7 +353,7 @@ test('x-model can listen for custom input event dispatches', async () => {
     })
 })
 
-// <input type="color"> 
+// <input type="color">
 test('x-model bind color input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '#ff0000' }">
@@ -376,7 +376,7 @@ test('x-model bind color input', async () => {
 
 })
 
-// <input type="button"> 
+// <input type="button">
 test('x-model bind button input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: 'foo' }">
@@ -398,7 +398,7 @@ test('x-model bind button input', async () => {
     })
 })
 
-// <input type="date"> 
+// <input type="date">
 test('x-model bind date input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '2020-07-10' }">
@@ -420,7 +420,7 @@ test('x-model bind date input', async () => {
     })
 })
 
-// <input type="datetime-local"> 
+// <input type="datetime-local">
 test('x-model bind datetime-local input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '2020-01-01T20:00' }">
@@ -442,11 +442,11 @@ test('x-model bind datetime-local input', async () => {
     })
 })
 
-// <input type="email"> 
+// <input type="email">
 test('x-model bind email input', async () => {
 })
 
-// <input type="month"> 
+// <input type="month">
 test('x-model bind month input', async () => {
     document.body.innerHTML = `
         <div x-data="{ key: '2020-04' }">
@@ -469,7 +469,7 @@ test('x-model bind month input', async () => {
 })
 
 
-// <input type="number"> 
+// <input type="number">
 test('x-model bind number input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '11' }">
@@ -491,7 +491,7 @@ test('x-model bind number input', async () => {
     })
 })
 
-// <input type="password"> 
+// <input type="password">
 test('x-model bind password input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: 'SecretKey' }">
@@ -513,7 +513,7 @@ test('x-model bind password input', async () => {
     })
 })
 
-// <input type="range"> 
+// <input type="range">
 test('x-model bind range input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '10' }">
@@ -535,7 +535,7 @@ test('x-model bind range input', async () => {
     })
 })
 
-// <input type="search"> 
+// <input type="search">
 test('x-model bind search input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '' }">
@@ -558,7 +558,7 @@ test('x-model bind search input', async () => {
     })
 })
 
-// <input type="tel"> 
+// <input type="tel">
 test('x-model bind tel input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '+12345678901' }">
@@ -581,7 +581,7 @@ test('x-model bind tel input', async () => {
     })
 })
 
-// <input type="tel"> 
+// <input type="tel">
 test('x-model bind tel input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '+12345678901' }">
@@ -604,7 +604,7 @@ test('x-model bind tel input', async () => {
     })
 })
 
-// <input type="tel"> 
+// <input type="tel">
 test('x-model bind time input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '22:00' }">
@@ -627,7 +627,7 @@ test('x-model bind time input', async () => {
     })
 })
 
-// <input type="time"> 
+// <input type="time">
 test('x-model bind time input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '22:00' }">
@@ -650,7 +650,7 @@ test('x-model bind time input', async () => {
     })
 })
 
-// <input type="week"> 
+// <input type="week">
 test('x-model bind week input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: '2020-W20' }">
@@ -673,7 +673,7 @@ test('x-model bind week input', async () => {
     })
 })
 
-// <input type="url"> 
+// <input type="url">
 test('x-model bind url input', async () => {
     document.body.innerHTML = `
     <div x-data="{ key: 'https://example.com' }">
@@ -694,4 +694,32 @@ test('x-model bind url input', async () => {
         expect(document.querySelector('input').value).toEqual(newValue)
         expect(document.querySelector('span').innerText).toEqual(newValue)
     })
-})
+})
+
+test('x-model sets value before x-on directive expression is processed', async () => {
+    window.selectValueA
+    window.selectValueB
+
+    document.body.innerHTML = `
+        <div x-data="{ a: 'foo', b: 'foo' }">
+            <select x-model="a" @change="window.selectValueA = a">
+                <option>foo</option>
+                <option>bar</option>
+            </select>
+            <select @change="window.selectValueB = b" x-model="b">
+                <option>foo</option>
+                <option>bar</option>
+            </select>
+        </div>
+    `
+
+    Alpine.start()
+
+    fireEvent.change(document.querySelectorAll('select')[0], { target: { value: 'bar' } });
+    fireEvent.change(document.querySelectorAll('select')[1], { target: { value: 'bar' } });
+
+    await wait(() => {
+        expect(window.selectValueA).toEqual('bar')
+        expect(window.selectValueB).toEqual('bar')
+    })
+})

+ 14 - 0
test/show.spec.js

@@ -159,3 +159,17 @@ test('x-show with x-bind:style inside x-for works correctly', async () => {
         expect(document.querySelectorAll('button')[1].style.display).toEqual('none')
     })
 })
+
+test('x-show takes precedence over style bindings for display property', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ show: false }">
+            <span x-show="show" :style="'color: red;'"></span>
+            <span :style="'color: red;'" x-show="show"></span>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelectorAll('span')[0].getAttribute('style')).toContain('display: none;')
+    expect(document.querySelectorAll('span')[1].getAttribute('style')).toContain('display: none;')
+})