Explorar el Código

Merge branch 'master' into master-ie11

Caleb Porzio hace 5 años
padre
commit
91adfa126d
Se han modificado 7 ficheros con 198 adiciones y 19 borrados
  1. 7 1
      README.md
  2. 0 0
      dist/alpine.js.map
  3. 21 0
      examples/index.html
  4. 34 1
      src/component.js
  5. 47 16
      src/directives/show.js
  6. 87 1
      test/show.spec.js
  7. 2 0
      test/transition.spec.js

+ 7 - 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.10.1/dist/alpine.js" defer></script>
+<script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v1.11.1/dist/alpine.js" defer></script>
 ```
 
 That's it. It will initialize itself.
@@ -179,6 +179,12 @@ If you wish to run code AFTER Alpine has made its initial updates to the DOM (so
 
 `x-show` toggles the `display: none;` style on the element depending if the expression resolves to `true` or `false`.
 
+> Note: `x-show` will wait for any children to finish transitioning out. If you want to bypass this behavior, add the `.immediate` modifer:
+```html
+<div x-show.immediate="open">
+    <div x-show="open" x-transition:leave="transition-out">
+</div>
+```
 ---
 
 ### `x-bind`

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
dist/alpine.js.map


+ 21 - 0
examples/index.html

@@ -304,6 +304,27 @@
                     </td>
                 </tr>
 
+                <tr>
+                    <td>Nested Transitions (with x-show)</td>
+                    <td>
+                        <div x-data="{ open: false }">
+                            <button x-on:click="open= ! open">
+                                Open Modal
+                            </button>
+
+                            <div x-show="open">
+                                I shouldn't leave until the transition finishes.
+                                <div x-show="open"
+                                    x-transition:leave-start="opacity-100 scale-100"
+                                    x-transition:leave="ease-in transition-slow"
+                                    x-transition:leave-end="opacity-0 scale-90">
+                                        I'm transitioning
+                                </div>
+                            </div>
+                        </div>
+                    </td>
+                </tr>
+
                 <tr>
                     <td>Init function callback access refs and mutate data</td>
                     <td>

+ 34 - 1
src/component.js

@@ -39,6 +39,9 @@ export default class Component {
             this.nextTickStack.push(callback)
         }
 
+        this.showDirectiveStack = []
+        this.showDirectiveLastElement
+
         var initReturnedCallback
         if (initExpression) {
             // We want to allow data manipulation, but not trigger DOM updates just yet.
@@ -139,6 +142,8 @@ export default class Component {
             el.__x = new Component(el)
         })
 
+        this.executeAndClearRemainingShowDirectiveStack()
+
         // Walk through the $nextTick stack and clear it as we go.
         while (this.nextTickStack.length > 0) {
             this.nextTickStack.shift()()
@@ -165,6 +170,34 @@ export default class Component {
         }, el => {
             el.__x = new Component(el)
         })
+
+        this.executeAndClearRemainingShowDirectiveStack()
+
+        // Walk through the $nextTick stack and clear it as we go.
+        while (this.nextTickStack.length > 0) {
+            this.nextTickStack.shift()()
+        }
+    }
+
+    executeAndClearRemainingShowDirectiveStack() {
+        // 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(thing => {
+            return new Promise(resolve => {
+                thing(finish => {
+                    resolve(finish)
+                })
+            })
+        }).reduce((nestedPromise, promise) => {
+            return nestedPromise.then(() => {
+                return promise.then(finish => finish())
+            })
+        }, Promise.resolve(() => {}))
+
+        // We've processed the handler stack. let's clear it.
+        this.showDirectiveStack = []
+        this.showDirectiveLastElement = undefined
     }
 
     updateElement(el, extraVars) {
@@ -219,7 +252,7 @@ export default class Component {
                 case 'show':
                     var output = this.evaluateReturnExpression(el, expression, extraVars)
 
-                    handleShowDirective(el, output, initialUpdate)
+                    handleShowDirective(this, el, output, modifiers, initialUpdate)
                     break;
 
                 case 'if':

+ 47 - 16
src/directives/show.js

@@ -1,21 +1,52 @@
 import { transitionIn, transitionOut } from '../utils'
 
-export function handleShowDirective(el, value, initialUpdate = false) {
-    if (! value) {
-        if ( el.style.display !== 'none' ) {
-            transitionOut(el, () => {
-                el.style.display = 'none'
-            }, initialUpdate)
-        }
-    } else {
-        if ( el.style.display !== '' ) {
-            transitionIn(el, () => {
-                if (el.style.length === 1) {
-                    el.removeAttribute('style')
-                } else {
-                    el.style.removeProperty('display')
-                }
-            }, initialUpdate)
+export function handleShowDirective(component, el, value, modifiers, initialUpdate = false) {
+    const handle = (resolve) => {
+        if (! value) {
+            if ( el.style.display !== 'none' ) {
+                transitionOut(el, () => {
+                    resolve(() => {
+                        el.style.display = 'none'
+                    })
+                }, initialUpdate)
+            } else {
+                resolve(() => {})
+            }
+        } else {
+            if ( el.style.display !== '' ) {
+                transitionIn(el, () => {
+                    if (el.style.length === 1) {
+                        el.removeAttribute('style')
+                    } else {
+                        el.style.removeProperty('display')
+                    }
+                }, initialUpdate)
+            }
+
+            // Resolve immediately, only hold up parent `x-show`s for hidin.
+            resolve(() => {})
         }
     }
+
+    // The working of x-show is a bit complex because we need to
+    // wait for any child transitions to finish before hiding
+    // some element. Also, this has to be done recursively.
+
+    // If x-show.immediate, foregoe the waiting.
+    if (modifiers.includes('immediate')) {
+        handle(finish => finish())
+        return
+    }
+
+    // x-show is encountered during a DOM tree walk. If an element
+    // we encounter is NOT a child of another x-show element we
+    // can execute the previous x-show stack (if one exists).
+    if (component.showDirectiveLastElement && ! component.showDirectiveLastElement.contains(el)) {
+        component.executeAndClearRemainingShowDirectiveStack()
+    }
+
+    // We'll push the handler onto a stack to be handled later.
+    component.showDirectiveStack.push(handle)
+
+    component.showDirectiveLastElement = el
 }

+ 87 - 1
test/show.spec.js

@@ -23,7 +23,7 @@ test('x-show toggles display: none; with no other style attributes', async () =>
     await wait(() => { expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;') })
 })
 
-test('x-show toggles display: none; with no other style attributes', async () => {
+test('x-show toggles display: none; with other style attributes', async () => {
     document.body.innerHTML = `
         <div x-data="{ show: true }">
             <span x-show="show" style="color: blue;"></span>
@@ -40,3 +40,89 @@ test('x-show toggles display: none; with no other style attributes', async () =>
 
     await wait(() => { expect(document.querySelector('span').getAttribute('style')).toEqual('color: blue; display: none;') })
 })
+
+test('x-show waits for transitions within it to finish before hiding an elements', async () => {
+    document.body.innerHTML = `
+        <style>
+            .transition { transition-property: background-color,border-color,color,fill,stroke,opacity,box-shadow,transform; }
+            .duration-75 {
+                transition-duration: 75ms;
+            }
+        </style>
+        <div x-data="{ show: true }">
+            <span x-show="show">
+                <h1 x-show="show" x-transition:leave="transition duration-75"></h1>
+            </span>
+
+            <button x-on:click="show = false"></button>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('span').getAttribute('style')).toEqual(null)
+
+    document.querySelector('button').click()
+
+    await new Promise((resolve) => setTimeout(() => { resolve(); }, 50))
+
+    await wait(() => {
+        expect(document.querySelector('span').getAttribute('style')).toEqual(null)
+        expect(document.querySelector('h1').getAttribute('style')).toEqual(null)
+    })
+})
+
+test('x-show does NOT wait for transitions to finish if .immediate is present', async () => {
+    document.body.innerHTML = `
+        <style>
+            .transition { transition-property: background-color,border-color,color,fill,stroke,opacity,box-shadow,transform; }
+            .duration-75 {
+                transition-duration: 75ms;
+            }
+        </style>
+        <div x-data="{ show: true }">
+            <span x-show.immediate="show">
+                <h1 x-show="show" x-transition:leave="transition duration-75"></h1>
+            </span>
+
+            <button x-on:click="show = false"></button>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('span').getAttribute('style')).toEqual(null)
+
+    document.querySelector('button').click()
+
+    await new Promise(resolve => setTimeout(resolve, 1))
+
+    expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;')
+    expect(document.querySelector('h1').getAttribute('style')).toEqual(null)
+})
+
+test('x-show works with nested x-shows of different functions (hiding vs showing)', async () => {
+    document.body.innerHTML = `
+        <div x-data="{ show1: true, show2: true }">
+            <span x-show="show1">
+                <h1 x-show="show2"></h1>
+            </span>
+
+            <button x-on:click="show1 = false"></button>
+        </div>
+    `
+
+    Alpine.start()
+
+    expect(document.querySelector('span').getAttribute('style')).toEqual(null)
+    expect(document.querySelector('h1').getAttribute('style')).toEqual(null)
+
+    document.querySelector('button').click()
+
+    await new Promise((resolve) => setTimeout(() => { resolve(); }, 50))
+
+    await wait(() => {
+        expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;')
+        expect(document.querySelector('h1').getAttribute('style')).toEqual(null)
+    })
+})

+ 2 - 0
test/transition.spec.js

@@ -281,6 +281,8 @@ test('transition out not called when item is already hidden', async () => {
 
     Alpine.start()
 
+    await new Promise(resolve => setTimeout(resolve, 1))
+
     expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;')
 
     document.querySelector('button').click()

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio