Browse Source

Merge branch 'master' into master-ie11

Keyur Shah 5 years ago
parent
commit
332cb0e379
7 changed files with 131 additions and 20 deletions
  1. 16 7
      README.html
  2. 1 1
      README.md
  3. 0 0
      dist/alpine.js.map
  4. 1 1
      package.json
  5. 1 1
      src/directives/bind.js
  6. 14 10
      src/directives/show.js
  7. 98 0
      test/transition.spec.js

+ 16 - 7
README.html

@@ -14,7 +14,7 @@
 </blockquote>
 <h2 id="install">Install</h2>
 <p><strong>From CDN:</strong> Add the following script to the end of your <code>&lt;head&gt;</code> section.</p>
-<pre><code class="lang-html">&lt;script src=&quot;https://cdn.jsdelivr.net/gh/alpinejs/alpine@v1.9.5/dist/alpine.js&quot; defer&gt;&lt;/script&gt;
+<pre><code class="lang-html">&lt;script src=&quot;https://cdn.jsdelivr.net/gh/alpinejs/alpine@v1.9.8/dist/alpine.js&quot; defer&gt;&lt;/script&gt;
 </code></pre>
 <p>That&#39;s it. It will initialize itself.</p>
 <p><strong>From NPM:</strong> Install the package from NPM.</p>
@@ -117,7 +117,7 @@
 </tr>
 </tbody>
 </table>
-<p>And 3 magic properties:</p>
+<p>And 5 magic properties:</p>
 <table>
 <thead>
 <tr>
@@ -132,6 +132,9 @@
 <td><a href="#refs"><code>$refs</code></a></td>
 </tr>
 <tr>
+<td><a href="#event"><code>$event</code></a></td>
+</tr>
+<tr>
 <td><a href="#dispatch"><code>$dispatch</code></a></td>
 </tr>
 <tr>
@@ -175,7 +178,7 @@
 <p><strong>Example:</strong> <code>&lt;div x-data=&quot;{ foo: &#39;bar&#39; }&quot; x-init=&quot;foo = &#39;baz&#39;&quot;&gt;&lt;/div&gt;</code></p>
 <p><strong>Structure:</strong> <code>&lt;div x-data=&quot;...&quot; x-init=&quot;[expression]&quot;&gt;&lt;/div&gt;</code></p>
 <p><code>x-init</code> runs an expression when a component is initialized.</p>
-<p>If you wish to run code AFTER Alpine has made it&#39;s initial updates to the DOM (something like a <code>mounted()</code> hook in VueJS), you can return a callback from <code>x-init</code>, and it will be run after:</p>
+<p>If you wish to run code AFTER Alpine has made its initial updates to the DOM (something like a <code>mounted()</code> hook in VueJS), you can return a callback from <code>x-init</code>, and it will be run after:</p>
 <p><code>x-init=&quot;return () =&gt; { // we have access to the post-dom-initialization state here // }&quot;</code></p>
 <hr>
 <h3 id="x-show"><code>x-show</code></h3>
@@ -189,7 +192,7 @@
 </blockquote>
 <p><strong>Example:</strong> <code>&lt;input x-bind:type=&quot;inputType&quot;&gt;</code></p>
 <p><strong>Structure:</strong> <code>&lt;input x-bind:[attribute]=&quot;[expression]&quot;&gt;</code></p>
-<p><code>x-bind</code> sets the value of an attribute to the result of a JavaScript expression. The expression has access to all the keys of the component&#39;s data object, and will update every-time it&#39;s data is updated.</p>
+<p><code>x-bind</code> sets the value of an attribute to the result of a JavaScript expression. The expression has access to all the keys of the component&#39;s data object, and will update every-time its data is updated.</p>
 <blockquote>
 <p>Note: attribute bindings ONLY update when their dependencies update. The framework is smart enough to observe data changes and detect which bindings care about them.</p>
 </blockquote>
@@ -212,7 +215,7 @@
 </blockquote>
 <p><strong>Example:</strong> <code>&lt;button x-on:click=&quot;foo = &#39;bar&#39;&quot;&gt;&lt;/button&gt;</code></p>
 <p><strong>Structure:</strong> <code>&lt;button x-on:[event]=&quot;[expression]&quot;&gt;&lt;/button&gt;</code></p>
-<p><code>x-on</code> attaches an event listener to the element it&#39;s declared on. When that event is emitted, the JavaScript expression set as it&#39;s value is executed.</p>
+<p><code>x-on</code> attaches an event listener to the element it&#39;s declared on. When that event is emitted, the JavaScript expression set as its value is executed.</p>
 <p>If any data is modified in the expression, other element attributes &quot;bound&quot; to this data, will be updated.</p>
 <p><strong><code>keydown</code> modifiers</strong></p>
 <p><strong>Example:</strong> <code>&lt;input type=&quot;text&quot; x-on:keydown.escape=&quot;open = false&quot;&gt;</code></p>
@@ -269,7 +272,7 @@
 <p><strong>Example:</strong> <code>&lt;template x-if=&quot;true&quot;&gt;&lt;div&gt;Some Element&lt;/div&gt;&lt;/template&gt;</code></p>
 <p><strong>Structure:</strong> <code>&lt;template x-if=&quot;[expression]&quot;&gt;&lt;div&gt;Some Element&lt;/div&gt;&lt;/template&gt;</code></p>
 <p>For cases where <code>x-show</code> isn&#39;t sufficient (<code>x-show</code> sets an element to <code>display: none</code> if it&#39;s false), <code>x-if</code> can be used to  actually remove an element completely from the DOM.</p>
-<p>It&#39;s important that <code>x-if</code> is used on a <code>&lt;template&gt;&lt;/template&gt;</code> tag because Alpine doesn&#39;t use a virtual DOM. This implementation allows Alpine to stay rugged and use the real DOM to work it&#39;s magic.</p>
+<p>It&#39;s important that <code>x-if</code> is used on a <code>&lt;template&gt;&lt;/template&gt;</code> tag because Alpine doesn&#39;t use a virtual DOM. This implementation allows Alpine to stay rugged and use the real DOM to work its magic.</p>
 <blockquote>
 <p>Note: <code>x-if</code> must have a single element root inside the <code>&lt;template&gt;&lt;/template&gt;</code> tag.</p>
 </blockquote>
@@ -369,6 +372,12 @@
 </code></pre>
 <p><code>$refs</code> is a magic property that can be used to retrieve DOM elements marked with <code>x-ref</code> inside the component. This is useful when you need to manually manipulate DOM elements.</p>
 <hr>
+<h3 id="-event"><code>$event</code></h3>
+<p><strong>Example:</strong></p>
+<pre><code class="lang-html">&lt;input x-on:input=&quot;alert($event.target.value)&quot;&gt;
+</code></pre>
+<p><code>$event</code> is a magic property that can be used within an event listener to retrieve the native browser &quot;Event&quot; object.</p>
+<hr>
 <h3 id="-dispatch"><code>$dispatch</code></h3>
 <p><strong>Example:</strong></p>
 <pre><code class="lang-html">&lt;div @custom-event=&quot;console.log($event.detail.foo)&quot;&gt;
@@ -399,7 +408,7 @@
     &gt;&lt;/button&gt;
 &lt;/div&gt;
 </code></pre>
-<p><code>$nextTick</code> is a magic property that allows you to only execute a given expression AFTER Alpine has made it&#39;s reactive DOM updates. This is useful for times you want to interact with the DOM state AFTER it&#39;s reflected any data updates you&#39;ve made.</p>
+<p><code>$nextTick</code> is a magic property that allows you to only execute a given expression AFTER Alpine has made its reactive DOM updates. This is useful for times you want to interact with the DOM state AFTER it&#39;s reflected any data updates you&#39;ve made.</p>
 <h2 id="license">License</h2>
 <p>Copyright © 2019-2020 Caleb Porzio and contributors</p>
 <p>Licensed under the MIT license, see <a href="LICENSE.md">LICENSE.md</a> for details.</p>

+ 1 - 1
README.md

@@ -14,7 +14,7 @@ Think of it like [Tailwind](https://tailwindcss.com/) for JavaScript.
 
 **From CDN:** Add the following script to the end of your `<head>` section.
 ```html
-<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v1.9.7/dist/alpine.js" defer></script>
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v1.9.8/dist/alpine.js" defer></script>
 ```
 
 That's it. It will initialize itself.

File diff suppressed because it is too large
+ 0 - 0
dist/alpine.js.map


+ 1 - 1
package.json

@@ -1,7 +1,7 @@
 {
   "main": "dist/alpine.js",
   "name": "alpinejs",
-  "version": "1.9.7",
+  "version": "1.9.8",
   "repository": {
     "type": "git",
     "url": "git://github.com/alpinejs/alpine.git"

+ 1 - 1
src/directives/bind.js

@@ -5,7 +5,7 @@ export function handleAttributeBindingDirective(component, el, attrName, express
 
     if (attrName === 'value') {
         // If nested model key is undefined, set the default value to empty string.
-        if (value === undefined && expression.match(/\./).length) {
+        if (value === undefined && (expression.match(/\./) || []).length) {
             value = ''
         }
 

+ 14 - 10
src/directives/show.js

@@ -2,16 +2,20 @@ import { transitionIn, transitionOut } from '../utils'
 
 export function handleShowDirective(el, value, initialUpdate = false) {
     if (! value) {
-        transitionOut(el, () => {
-            el.style.display = 'none'
-        }, initialUpdate)
+        if ( el.style.display !== 'none' ) {
+            transitionOut(el, () => {
+                el.style.display = 'none'
+            }, initialUpdate)
+        }
     } else {
-        transitionIn(el, () => {
-            if (el.style.length === 1 && el.style.display !== '') {
-                el.removeAttribute('style')
-            } else {
-                el.style.removeProperty('display')
-            }
-        }, initialUpdate)
+        if ( el.style.display !== '' ) {
+            transitionIn(el, () => {
+                if (el.style.length === 1) {
+                    el.removeAttribute('style')
+                } else {
+                    el.style.removeProperty('display')
+                }
+            }, initialUpdate)
+        }
     }
 }

+ 98 - 0
test/transition.spec.js

@@ -202,3 +202,101 @@ test('original class attribute classes are preserved after transition finishes',
         }, 10)
     )
 })
+
+test('transition in not called when item is already visible', async () => {
+    // Hijack "requestAnimationFrame" for finer-tuned control in this test.
+    var frameStack = []
+
+    jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
+        frameStack.push(callback)
+    });
+
+    // Hijack "getComputeStyle" because js-dom is weird with it.
+    // (hardcoding 10ms transition time for later assertions)
+    jest.spyOn(window, 'getComputedStyle').mockImplementation(el => {
+        return { transitionDuration: '.01s' }
+    });
+
+    document.body.innerHTML = `
+        <div x-data="{ show: true }">
+            <button x-on:click="show = true"></button>
+
+            <span
+                x-show="show"
+                x-transition:enter="enter"
+                x-transition:enter-start="enter-start"
+                x-transition:enter-end="enter-end"
+            ></span>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('span').getAttribute('style')).toEqual(null)
+
+    document.querySelector('button').click()
+
+    // Wait out the intial Alpine refresh debounce.
+    await new Promise((resolve) =>
+        setTimeout(() => {
+            resolve();
+        }, 5)
+    )
+
+    // No animation queued
+    expect(frameStack.pop()).toEqual(undefined)
+
+    expect(document.querySelector('span').classList.contains('enter')).toEqual(false)
+    expect(document.querySelector('span').classList.contains('enter-start')).toEqual(false)
+    expect(document.querySelector('span').classList.contains('enter-end')).toEqual(false)
+    expect(document.querySelector('span').getAttribute('style')).toEqual(null)
+})
+
+test('transition out not called when item is already hidden', async () => {
+    // Hijack "requestAnimationFrame" for finer-tuned control in this test.
+    var frameStack = []
+
+    jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
+        frameStack.push(callback)
+    });
+
+    // Hijack "getComputeStyle" because js-dom is weird with it.
+    // (hardcoding 10ms transition time for later assertions)
+    jest.spyOn(window, 'getComputedStyle').mockImplementation(el => {
+        return { transitionDuration: '.01s' }
+    });
+
+    document.body.innerHTML = `
+        <div x-data="{ show: false }">
+            <button x-on:click="show = false"></button>
+
+            <span
+                x-show="show"
+                x-transition:leave="leave"
+                x-transition:leave-start="leave-start"
+                x-transition:leave-end="leave-end"
+            ></span>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;')
+
+    document.querySelector('button').click()
+
+    // Wait out the intial Alpine refresh debounce.
+    await new Promise((resolve) =>
+        setTimeout(() => {
+            resolve();
+        }, 5)
+    )
+
+    // No animation queued
+    expect(frameStack.pop()).toEqual(undefined)
+
+    expect(document.querySelector('span').classList.contains('leave')).toEqual(false)
+    expect(document.querySelector('span').classList.contains('leave-start')).toEqual(false)
+    expect(document.querySelector('span').classList.contains('leave-end')).toEqual(false)
+    expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;')
+})

Some files were not shown because too many files changed in this diff