Browse Source

feature(add support for .camel binding modifier)

Ryan Chandler 5 years ago
parent
commit
ee5e90c8d9
6 changed files with 85 additions and 52 deletions
  1. 53 42
      dist/alpine-ie11.js
  2. 9 5
      dist/alpine.js
  3. 2 2
      src/component.js
  4. 5 3
      src/directives/bind.js
  5. 4 0
      src/utils.js
  6. 12 0
      test/bind.spec.js

+ 53 - 42
dist/alpine-ie11.js

@@ -5431,6 +5431,15 @@
   function kebabCase(subject) {
     return subject.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[_\s]/, '-').toLowerCase();
   }
+  function camelCase(subject) {
+    var _this2 = this;
+
+    return subject.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, function (match, _char) {
+      _newArrowCheck(this, _this2);
+
+      return _char.toUpperCase();
+    }.bind(this));
+  }
   function walk(el, callback) {
     if (callback(el) === false) return;
     var node = el.firstElementChild;
@@ -5489,12 +5498,12 @@
     return xAttrRE.test(name);
   }
   function getXAttrs(el, component, type) {
-    var _this2 = this;
+    var _this3 = this;
 
     var directives = Array.from(el.attributes).filter(isXAttr).map(parseHtmlAttribute); // Get an object of directives from x-spread.
 
     var spreadDirective = directives.filter(function (directive) {
-      _newArrowCheck(this, _this2);
+      _newArrowCheck(this, _this3);
 
       return directive.type === 'spread';
     }.bind(this))[0];
@@ -5503,7 +5512,7 @@
       var spreadObject = saferEval(spreadDirective.expression, component.$data); // Add x-spread directives to the pile of existing directives.
 
       directives = directives.concat(Object.entries(spreadObject).map(function (_ref) {
-        _newArrowCheck(this, _this2);
+        _newArrowCheck(this, _this3);
 
         var _ref2 = _slicedToArray(_ref, 2),
             name = _ref2[0],
@@ -5517,7 +5526,7 @@
     }
 
     return directives.filter(function (i) {
-      _newArrowCheck(this, _this2);
+      _newArrowCheck(this, _this3);
 
       // If no type is passed in for filtering, bypass filter
       if (!type) return true;
@@ -5526,7 +5535,7 @@
   }
 
   function parseHtmlAttribute(_ref3) {
-    var _this3 = this;
+    var _this4 = this;
 
     var name = _ref3.name,
         value = _ref3.value;
@@ -5538,7 +5547,7 @@
       type: typeMatch ? typeMatch[1] : null,
       value: valueMatch ? valueMatch[1] : null,
       modifiers: modifiers.map(function (i) {
-        _newArrowCheck(this, _this3);
+        _newArrowCheck(this, _this4);
 
         return i.replace('.', '');
       }.bind(this)),
@@ -5566,7 +5575,7 @@
     return classList.split(' ').filter(filterFn);
   }
   function transitionIn(el, show, component) {
-    var _this4 = this;
+    var _this5 = this;
 
     var forceSkip = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
     if (forceSkip) return show();
@@ -5580,13 +5589,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, _this4);
+        _newArrowCheck(this, _this5);
 
         return index < modifiers.indexOf('out');
       }.bind(this)) : modifiers;
       transitionHelperIn(el, modifiers, show); // Otherwise, we can assume x-transition:enter.
     } else if (attrs.filter(function (attr) {
-      _newArrowCheck(this, _this4);
+      _newArrowCheck(this, _this5);
 
       return ['enter', 'enter-start', 'enter-end'].includes(attr.value);
     }.bind(this)).length > 0) {
@@ -5597,7 +5606,7 @@
     }
   }
   function transitionOut(el, hide, 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.
@@ -5610,13 +5619,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, _this5);
+        _newArrowCheck(this, _this6);
 
         return index > modifiers.indexOf('out');
       }.bind(this)) : modifiers;
       transitionHelperOut(el, modifiers, settingBothSidesOfTransition, hide);
     } else if (attrs.filter(function (attr) {
-      _newArrowCheck(this, _this5);
+      _newArrowCheck(this, _this6);
 
       return ['leave', 'leave-start', 'leave-end'].includes(attr.value);
     }.bind(this)).length > 0) {
@@ -5626,7 +5635,7 @@
     }
   }
   function transitionHelperIn(el, modifiers, showCallback) {
-    var _this6 = this;
+    var _this7 = this;
 
     // Default values inspired by: https://material.io/design/motion/speed.html#duration
     var styleValues = {
@@ -5642,11 +5651,11 @@
       }
     };
     transitionHelper(el, modifiers, showCallback, function () {
-      _newArrowCheck(this, _this6);
+      _newArrowCheck(this, _this7);
     }.bind(this), styleValues);
   }
   function transitionHelperOut(el, modifiers, settingBothSidesOfTransition, hideCallback) {
-    var _this7 = this;
+    var _this8 = this;
 
     // Make the "out" transition .5x slower than the "in". (Visually better)
     // HOWEVER, if they explicitly set a duration for the "out" transition,
@@ -5665,7 +5674,7 @@
       }
     };
     transitionHelper(el, modifiers, function () {
-      _newArrowCheck(this, _this7);
+      _newArrowCheck(this, _this8);
     }.bind(this), hideCallback, styleValues);
   }
 
@@ -5744,65 +5753,65 @@
     transition(el, stages);
   }
   function transitionClassesIn(el, component, directives, showCallback) {
-    var _this8 = this;
+    var _this9 = this;
 
     var ensureStringExpression = function ensureStringExpression(expression) {
-      _newArrowCheck(this, _this8);
+      _newArrowCheck(this, _this9);
 
       return typeof expression === 'function' ? component.evaluateReturnExpression(el, expression) : expression;
     }.bind(this);
 
     var enter = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
-      _newArrowCheck(this, _this8);
+      _newArrowCheck(this, _this9);
 
       return i.value === 'enter';
     }.bind(this)) || {
       expression: ''
     }).expression));
     var enterStart = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
-      _newArrowCheck(this, _this8);
+      _newArrowCheck(this, _this9);
 
       return i.value === 'enter-start';
     }.bind(this)) || {
       expression: ''
     }).expression));
     var enterEnd = convertClassStringToArray(ensureStringExpression((directives.find(function (i) {
-      _newArrowCheck(this, _this8);
+      _newArrowCheck(this, _this9);
 
       return i.value === 'enter-end';
     }.bind(this)) || {
       expression: ''
     }).expression));
     transitionClasses(el, enter, enterStart, enterEnd, showCallback, function () {
-      _newArrowCheck(this, _this8);
+      _newArrowCheck(this, _this9);
     }.bind(this));
   }
   function transitionClassesOut(el, component, directives, hideCallback) {
-    var _this9 = this;
+    var _this10 = this;
 
     var leave = convertClassStringToArray((directives.find(function (i) {
-      _newArrowCheck(this, _this9);
+      _newArrowCheck(this, _this10);
 
       return i.value === 'leave';
     }.bind(this)) || {
       expression: ''
     }).expression);
     var leaveStart = convertClassStringToArray((directives.find(function (i) {
-      _newArrowCheck(this, _this9);
+      _newArrowCheck(this, _this10);
 
       return i.value === 'leave-start';
     }.bind(this)) || {
       expression: ''
     }).expression);
     var leaveEnd = convertClassStringToArray((directives.find(function (i) {
-      _newArrowCheck(this, _this9);
+      _newArrowCheck(this, _this10);
 
       return i.value === 'leave-end';
     }.bind(this)) || {
       expression: ''
     }).expression);
     transitionClasses(el, leave, leaveStart, leaveEnd, function () {
-      _newArrowCheck(this, _this9);
+      _newArrowCheck(this, _this10);
     }.bind(this), hideCallback);
   }
   function transitionClasses(el, classesDuring, classesStart, classesEnd, hook1, hook2) {
@@ -5823,12 +5832,12 @@
       },
       end: function end() {
         var _el$classList3,
-            _this10 = this,
+            _this11 = 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, _this10);
+          _newArrowCheck(this, _this11);
 
           return !originalClasses.includes(i);
         }.bind(this))));
@@ -5840,17 +5849,17 @@
       },
       cleanup: function cleanup() {
         var _el$classList5,
-            _this11 = this,
+            _this12 = this,
             _el$classList6;
 
         (_el$classList5 = el.classList).remove.apply(_el$classList5, _toConsumableArray(classesDuring.filter(function (i) {
-          _newArrowCheck(this, _this11);
+          _newArrowCheck(this, _this12);
 
           return !originalClasses.includes(i);
         }.bind(this))));
 
         (_el$classList6 = el.classList).remove.apply(_el$classList6, _toConsumableArray(classesEnd.filter(function (i) {
-          _newArrowCheck(this, _this11);
+          _newArrowCheck(this, _this12);
 
           return !originalClasses.includes(i);
         }.bind(this))));
@@ -5859,14 +5868,14 @@
     transition(el, stages);
   }
   function transition(el, stages) {
-    var _this12 = this;
+    var _this13 = this;
 
     stages.start();
     stages.during();
     requestAnimationFrame(function () {
-      var _this13 = this;
+      var _this14 = this;
 
-      _newArrowCheck(this, _this12);
+      _newArrowCheck(this, _this13);
 
       // 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.
@@ -5878,14 +5887,14 @@
 
       stages.show();
       requestAnimationFrame(function () {
-        var _this14 = this;
+        var _this15 = this;
 
-        _newArrowCheck(this, _this13);
+        _newArrowCheck(this, _this14);
 
         stages.end(); // Assign current transition to el in case we need to force it.
 
         setTimeout(function () {
-          _newArrowCheck(this, _this14);
+          _newArrowCheck(this, _this15);
 
           stages.hide(); // Adding an "isConnected" check, in case the callback
           // removed the element from the DOM.
@@ -6067,7 +6076,7 @@
     }
   });
 
-  function handleAttributeBindingDirective(component, el, attrName, expression, extraVars, attrType) {
+  function handleAttributeBindingDirective(component, el, attrName, expression, extraVars, attrType, modifiers) {
     var _this = this;
 
     var value = component.evaluateReturnExpression(el, expression, extraVars);
@@ -6151,7 +6160,8 @@
         el.setAttribute('class', arrayUnique(_originalClasses.concat(newClasses)).join(' '));
       }
     } else {
-      // If an attribute's bound value is null, undefined or false, remove the attribute
+      attrName = modifiers.includes('camel') ? camelCase(attrName) : attrName; // If an attribute's bound value is null, undefined or false, remove the attribute
+
       if ([null, undefined, false].includes(value)) {
         el.removeAttribute(attrName);
       } else {
@@ -6161,6 +6171,7 @@
   }
 
   function setIfChanged(el, attrName, value) {
+
     if (el.getAttribute(attrName) != value) {
       el.setAttribute(attrName, value);
     }
@@ -6986,13 +6997,13 @@
 
           switch (type) {
             case 'model':
-              handleAttributeBindingDirective(this, el, 'value', expression, extraVars, type);
+              handleAttributeBindingDirective(this, el, 'value', expression, extraVars, type, modifiers);
               break;
 
             case 'bind':
               // The :key binding on an x-for is special, ignore it.
               if (el.tagName.toLowerCase() === 'template' && value === 'key') return;
-              handleAttributeBindingDirective(this, el, value, expression, extraVars, type);
+              handleAttributeBindingDirective(this, el, value, expression, extraVars, type, modifiers);
               break;
 
             case 'text':

+ 9 - 5
dist/alpine.js

@@ -80,6 +80,9 @@
   function kebabCase(subject) {
     return subject.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[_\s]/, '-').toLowerCase();
   }
+  function camelCase(subject) {
+    return subject.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (match, char) => char.toUpperCase());
+  }
   function walk(el, callback) {
     if (callback(el) === false) return;
     let node = el.firstElementChild;
@@ -551,7 +554,7 @@
     }
   }
 
-  function handleAttributeBindingDirective(component, el, attrName, expression, extraVars, attrType) {
+  function handleAttributeBindingDirective(component, el, attrName, expression, extraVars, attrType, modifiers) {
     var value = component.evaluateReturnExpression(el, expression, extraVars);
 
     if (attrName === 'value') {
@@ -612,7 +615,8 @@
         el.setAttribute('class', arrayUnique(originalClasses.concat(newClasses)).join(' '));
       }
     } else {
-      // If an attribute's bound value is null, undefined or false, remove the attribute
+      attrName = modifiers.includes('camel') ? camelCase(attrName) : attrName; // If an attribute's bound value is null, undefined or false, remove the attribute
+
       if ([null, undefined, false].includes(value)) {
         el.removeAttribute(attrName);
       } else {
@@ -621,7 +625,7 @@
     }
   }
 
-  function setIfChanged(el, attrName, value) {
+  function setIfChanged(el, attrName, value, namespaced = false) {
     if (el.getAttribute(attrName) != value) {
       el.setAttribute(attrName, value);
     }
@@ -1535,13 +1539,13 @@
       }) => {
         switch (type) {
           case 'model':
-            handleAttributeBindingDirective(this, el, 'value', expression, extraVars, type);
+            handleAttributeBindingDirective(this, el, 'value', expression, extraVars, type, modifiers);
             break;
 
           case 'bind':
             // The :key binding on an x-for is special, ignore it.
             if (el.tagName.toLowerCase() === 'template' && value === 'key') return;
-            handleAttributeBindingDirective(this, el, value, expression, extraVars, type);
+            handleAttributeBindingDirective(this, el, value, expression, extraVars, type, modifiers);
             break;
 
           case 'text':

+ 2 - 2
src/component.js

@@ -253,14 +253,14 @@ export default class Component {
         attrs.forEach(({ type, value, modifiers, expression }) => {
             switch (type) {
                 case 'model':
-                    handleAttributeBindingDirective(this, el, 'value', expression, extraVars, type)
+                    handleAttributeBindingDirective(this, el, 'value', expression, extraVars, type, modifiers)
                     break;
 
                 case 'bind':
                     // The :key binding on an x-for is special, ignore it.
                     if (el.tagName.toLowerCase() === 'template' && value === 'key') return
 
-                    handleAttributeBindingDirective(this, el, value, expression, extraVars, type)
+                    handleAttributeBindingDirective(this, el, value, expression, extraVars, type, modifiers)
                     break;
 
                 case 'text':

+ 5 - 3
src/directives/bind.js

@@ -1,6 +1,6 @@
-import { arrayUnique, isBooleanAttr, convertClassStringToArray } from '../utils'
+import { arrayUnique, isBooleanAttr, convertClassStringToArray, camelCase } from '../utils'
 
-export function handleAttributeBindingDirective(component, el, attrName, expression, extraVars, attrType) {
+export function handleAttributeBindingDirective(component, el, attrName, expression, extraVars, attrType, modifiers) {
     var value = component.evaluateReturnExpression(el, expression, extraVars)
 
     if (attrName === 'value') {
@@ -63,6 +63,8 @@ export function handleAttributeBindingDirective(component, el, attrName, express
             el.setAttribute('class', arrayUnique(originalClasses.concat(newClasses)).join(' '))
         }
     } else {
+        attrName = modifiers.includes('camel') ? camelCase(attrName) : attrName
+
         // If an attribute's bound value is null, undefined or false, remove the attribute
         if ([null, undefined, false].includes(value)) {
             el.removeAttribute(attrName)
@@ -72,7 +74,7 @@ export function handleAttributeBindingDirective(component, el, attrName, express
     }
 }
 
-function setIfChanged(el, attrName, value) {
+function setIfChanged(el, attrName, value, namespaced = false) {
     if (el.getAttribute(attrName) != value){
         el.setAttribute(attrName, value)
     }

+ 4 - 0
src/utils.js

@@ -32,6 +32,10 @@ export function kebabCase(subject) {
     return subject.replace(/([a-z])([A-Z])/g, '$1-$2').replace(/[_\s]/, '-').toLowerCase()
 }
 
+export function camelCase(subject) {
+    return subject.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (match, char) => char.toUpperCase())
+}
+
 export function walk(el, callback) {
     if (callback(el) === false) return
 

+ 12 - 0
test/bind.spec.js

@@ -483,3 +483,15 @@ test('extra whitespace in class binding string syntax is ignored', async () => {
     expect(document.querySelector('span').classList.contains('foo')).toBeTruthy()
     expect(document.querySelector('span').classList.contains('bar')).toBeTruthy()
 })
+
+test('.camel modifier correctly sets name of attribute', async () => {
+    document.body.innerHTML = `
+        <div x-data>
+            <svg x-bind:view-box.camel="'0 0 42 42'"></svg>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('svg').getAttribute('viewBox')).toEqual('0 0 42 42')
+})