浏览代码

Revert transition overlap prevention system that cuased bugs

Caleb Porzio 5 年之前
父节点
当前提交
91b057d4c4
共有 6 个文件被更改,包括 132 次插入204 次删除
  1. 6 33
      dist/alpine-ie11.js
  2. 6 33
      dist/alpine.js
  3. 116 0
      examples/card-game.html
  4. 2 16
      src/directives/show.js
  5. 2 18
      src/utils.js
  6. 0 104
      test/transition.spec.js

+ 6 - 33
dist/alpine-ie11.js

@@ -5884,7 +5884,7 @@
 
         stages.end(); // Assign current transition to el in case we need to force it.
 
-        el.__x_transition_remaining = once(function () {
+        setTimeout(function () {
           _newArrowCheck(this, _this14);
 
           stages.hide(); // Adding an "isConnected" check, in case the callback
@@ -5892,27 +5892,14 @@
 
           if (el.isConnected) {
             stages.cleanup();
-          } // Safe to remove transition from el since it is completed.
-
-
-          delete el.__x_transition_remaining;
-        }.bind(this));
-        setTimeout(el.__x_transition_remaining, duration);
+          }
+        }.bind(this), duration);
       }.bind(this));
     }.bind(this));
   }
   function isNumeric(subject) {
     return !isNaN(subject);
   }
-  function once(callback) {
-    var called = false;
-    return function () {
-      if (!called) {
-        called = true;
-        callback.apply(this, arguments);
-      }
-    };
-  }
 
   function handleForDirective(component, templateEl, expression, initialUpdate, extraVars) {
     var _this = this;
@@ -6212,11 +6199,6 @@
 
     var initialUpdate = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
 
-    // if value is changed resolve any previous pending transitions before starting a new one.
-    if (el.__x_transition_remaining && el.__x_transition_last_value !== value) {
-      el.__x_transition_remaining();
-    }
-
     var hide = function hide() {
       _newArrowCheck(this, _this);
 
@@ -6234,9 +6216,6 @@
     }.bind(this);
 
     if (initialUpdate === true) {
-      // Assign current value to el to check later on for preventing transition overlaps.
-      el.__x_transition_last_value = value;
-
       if (value) {
         show();
       } else {
@@ -6278,10 +6257,7 @@
             _newArrowCheck(this, _this2);
           }.bind(this));
         }
-      } // Assign current value to el.
-
-
-      el.__x_transition_last_value = value;
+      }
     }.bind(this); // 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.
@@ -6304,11 +6280,8 @@
       component.executeAndClearRemainingShowDirectiveStack();
     }
 
-    if (el.__x_transition_last_value !== value) {
-      // We'll push the handler onto a stack to be handled later.
-      component.showDirectiveStack.push(handle);
-      component.showDirectiveLastElement = el;
-    }
+    component.showDirectiveStack.push(handle);
+    component.showDirectiveLastElement = el;
   }
 
   function handleIfDirective(component, el, expressionResult, initialUpdate, extraVars) {

+ 6 - 33
dist/alpine.js

@@ -419,33 +419,20 @@
       requestAnimationFrame(() => {
         stages.end(); // Assign current transition to el in case we need to force it.
 
-        el.__x_transition_remaining = once(() => {
+        setTimeout(() => {
           stages.hide(); // Adding an "isConnected" check, in case the callback
           // removed the element from the DOM.
 
           if (el.isConnected) {
             stages.cleanup();
-          } // Safe to remove transition from el since it is completed.
-
-
-          delete el.__x_transition_remaining;
-        });
-        setTimeout(el.__x_transition_remaining, duration);
+          }
+        }, duration);
       });
     });
   }
   function isNumeric(subject) {
     return !isNaN(subject);
   }
-  function once(callback) {
-    let called = false;
-    return function () {
-      if (!called) {
-        called = true;
-        callback.apply(this, arguments);
-      }
-    };
-  }
 
   function handleForDirective(component, templateEl, expression, initialUpdate, extraVars) {
     warnIfMalformedTemplate(templateEl, 'x-for');
@@ -663,11 +650,6 @@
   }
 
   function handleShowDirective(component, el, value, modifiers, initialUpdate = false) {
-    // if value is changed resolve any previous pending transitions before starting a new one.
-    if (el.__x_transition_remaining && el.__x_transition_last_value !== value) {
-      el.__x_transition_remaining();
-    }
-
     const hide = () => {
       el.style.display = 'none';
     };
@@ -681,9 +663,6 @@
     };
 
     if (initialUpdate === true) {
-      // Assign current value to el to check later on for preventing transition overlaps.
-      el.__x_transition_last_value = value;
-
       if (value) {
         show();
       } else {
@@ -709,10 +688,7 @@
         } else {
           resolve(() => {});
         }
-      } // Assign current value to el.
-
-
-      el.__x_transition_last_value = value;
+      }
     }; // 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.
@@ -731,11 +707,8 @@
       component.executeAndClearRemainingShowDirectiveStack();
     }
 
-    if (el.__x_transition_last_value !== value) {
-      // We'll push the handler onto a stack to be handled later.
-      component.showDirectiveStack.push(handle);
-      component.showDirectiveLastElement = el;
-    }
+    component.showDirectiveStack.push(handle);
+    component.showDirectiveLastElement = el;
   }
 
   function handleIfDirective(component, el, expressionResult, initialUpdate, extraVars) {

+ 116 - 0
examples/card-game.html

@@ -0,0 +1,116 @@
+<!doctype html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>Document</title>
+    <!-- <script src="https://cdn.jsdelivr.net/gh/alpinejs/alpine@v2.x.x/dist/alpine.min.js" defer></script> -->
+    <script src="/dist/alpine.js" defer></script>
+    <link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet">
+</head>
+
+<body>
+    <!-- Memory Game -->
+    <div x-data="game()" class="px-10 flex items-center justify-center min-h-screen">
+        <h1 class="fixed top-0 right-0 p-10 font-bold text-3xl">
+            <span x-text="points"></span>
+            <span class="text-xs">pts</span>
+        </h1>
+
+        <div class="flex-1 grid grid-cols-4 gap-10">
+            <template x-for="(card, index) in cards" :key="index">
+                <div>
+                    <button x-show="! card.cleared"
+                            :style="'background: ' + (card.flipped ? card.color : '#999')"
+                            :disabled="flippedCards.length >= 2"
+                            class="w-full h-32"
+                            @click="flipCard(card)"
+                    >
+                    </button>
+                </div>
+            </template>
+        </div>
+    </div>
+
+    <!-- Flash Message -->
+    <div x-data="{ show: false, message: '' }"
+         x-show.transition.opacity="show"
+         x-text="message"
+         @flash.window="
+            message = $event.detail.message;
+            show = true;
+            setTimeout(() => show = false, 1000)
+        "
+         class="fixed bottom-0 right-0 bg-green-500 text-white p-2 mb-4 mr-4 rounded"
+    >
+    </div>
+
+    <script>
+        function pause(milliseconds = 1000) {
+            return new Promise(resolve => setTimeout(resolve, milliseconds));
+        }
+
+        function flash(message) {
+            window.dispatchEvent(new CustomEvent('flash', {
+                detail: { message }
+            }));
+        }
+
+        function game() {
+            return {
+                cards: [
+                    { color: 'green', flipped: false, cleared: false },
+                    { color: 'red', flipped: false, cleared: false },
+                    { color: 'blue', flipped: false, cleared: false },
+                    { color: 'yellow', flipped: false, cleared: false },
+                    { color: 'green', flipped: false, cleared: false },
+                    { color: 'red', flipped: false, cleared: false },
+                    { color: 'blue', flipped: false, cleared: false },
+                    { color: 'yellow', flipped: false, cleared: false },
+                ].sort(() => Math.random() - .5),
+
+                get flippedCards() {
+                    return this.cards.filter(card => card.flipped);
+                },
+
+                get clearedCards() {
+                    return this.cards.filter(card => card.cleared);
+                },
+
+                get remainingCards() {
+                    return this.cards.filter(card => ! card.cleared);
+                },
+
+                get points() {
+                    return this.clearedCards.length;
+                },
+
+                async flipCard(card) {
+                    card.flipped = ! card.flipped;
+
+                    if (this.flippedCards.length !== 2) return;
+
+                    if (this.hasMatch()) {
+                        flash('You found a match!');
+
+                        await pause();
+
+                        this.flippedCards.forEach(card => card.cleared = true);
+
+                        if (! this.remainingCards.length) {
+                            alert('You Won!');
+                        }
+                    } else {
+                        await pause();
+                    }
+
+                    this.flippedCards.forEach(card => card.flipped = false);
+                },
+
+                hasMatch() {
+                    return this.flippedCards[0]['color'] === this.flippedCards[1]['color'];
+                }
+            };
+        }
+    </script>
+</body>
+</html>

+ 2 - 16
src/directives/show.js

@@ -1,11 +1,6 @@
 import { transitionIn, transitionOut } from '../utils'
 
 export function handleShowDirective(component, el, value, modifiers, initialUpdate = false) {
-    // if value is changed resolve any previous pending transitions before starting a new one.
-    if (el.__x_transition_remaining && el.__x_transition_last_value !== value) {
-        el.__x_transition_remaining()
-    }
-
     const hide = () => {
         el.style.display = 'none'
     }
@@ -19,9 +14,6 @@ export function handleShowDirective(component, el, value, modifiers, initialUpda
     }
 
     if (initialUpdate === true) {
-        // Assign current value to el to check later on for preventing transition overlaps.
-        el.__x_transition_last_value = value
-
         if (value) {
             show()
         } else {
@@ -47,9 +39,6 @@ export function handleShowDirective(component, el, value, modifiers, initialUpda
                 resolve(() => {})
             }
         }
-
-        // Assign current value to el.
-        el.__x_transition_last_value = value
     }
 
     // The working of x-show is a bit complex because we need to
@@ -69,10 +58,7 @@ export function handleShowDirective(component, el, value, modifiers, initialUpda
         component.executeAndClearRemainingShowDirectiveStack()
     }
 
-    if (el.__x_transition_last_value !== value) {
-        // We'll push the handler onto a stack to be handled later.
-        component.showDirectiveStack.push(handle)
+    component.showDirectiveStack.push(handle)
 
-        component.showDirectiveLastElement = el
-    }
+    component.showDirectiveLastElement = el
 }

+ 2 - 18
src/utils.js

@@ -408,7 +408,7 @@ export function transition(el, stages) {
             stages.end()
 
             // Assign current transition to el in case we need to force it.
-            el.__x_transition_remaining = once(() => {
+            setTimeout(() => {
                 stages.hide()
 
                 // Adding an "isConnected" check, in case the callback
@@ -416,12 +416,7 @@ export function transition(el, stages) {
                 if (el.isConnected) {
                     stages.cleanup()
                 }
-
-                 // Safe to remove transition from el since it is completed.
-                 delete el.__x_transition_remaining
-            })
-
-            setTimeout(el.__x_transition_remaining, duration);
+            }, duration)
         })
     });
 }
@@ -429,14 +424,3 @@ export function transition(el, stages) {
 export function isNumeric(subject){
     return ! isNaN(subject)
 }
-
-export function once(callback) {
-    let called = false
-
-    return function () {
-        if (! called) {
-            called = true
-            callback.apply(this, arguments)
-        }
-    }
-}

+ 0 - 104
test/transition.spec.js

@@ -620,107 +620,3 @@ test('x-transition supports css animation', async () => {
     )
     expect(document.querySelector('span').classList.contains('animation-leave')).toEqual(false)
 })
-
-
-test('remaining transitions forced to complete if they exists', async () => {
-    jest.spyOn(window, 'requestAnimationFrame').mockImplementation((callback) => {
-        setTimeout(callback, 0)
-    });
-
-    // Hardcoding 10ms animation time for later assertions.
-    jest.spyOn(window, 'getComputedStyle').mockImplementation(el => {
-        return {
-            transitionDuration: '0s',
-            animationDuration: '.1s'
-        }
-    });
-
-    document.body.innerHTML = `
-        <div x-data="{ show: false }">
-            <button x-on:click="show = ! show"></button>
-
-            <span
-                x-show="show"
-                x-transition:enter="animation-enter"
-                x-transition:leave="animation-leave"
-            ></span>
-        </div>
-    `
-
-    Alpine.start()
-
-    await wait(() => { expect(document.querySelector('span').getAttribute('style')).toEqual('display: none;') })
-
-    // Trigger animation in.
-    document.querySelector('button').click()
-
-    // Wait for the first requestAnimationFrame.
-    await new Promise((resolve) =>
-        setTimeout(() => {
-            resolve();
-        }, 0)
-    )
-
-    expect(document.querySelector('span').classList.contains('animation-enter')).toEqual(true)
-
-    // Trigger animation out.
-    document.querySelector('button').click()
-
-    // Wait for the next requestAnimationFrame.
-    await new Promise((resolve) =>
-        setTimeout(() => {
-            resolve();
-        }, 0)
-    )
-
-    expect(document.querySelector('span').classList.contains('animation-leave')).toEqual(true)
-
-    // Wait for the next requestAnimationFrame.
-        await new Promise((resolve) =>
-        setTimeout(() => {
-            resolve();
-        }, 0)
-    )
-
-    // Trigger animation in.
-    document.querySelector('button').click()
-
-    // Wait for the next requestAnimationFrame.
-    await new Promise((resolve) =>
-        setTimeout(() => {
-            resolve();
-        }, 0)
-    )
-
-    expect(document.querySelector('span').classList.contains('animation-enter')).toEqual(true)
-
-    // Trigger animation out.
-    document.querySelector('button').click()
-
-    // Wait for the next requestAnimationFrame.
-    await new Promise((resolve) =>
-        setTimeout(() => {
-            resolve();
-        }, 0)
-    )
-
-    expect(document.querySelector('span').classList.contains('animation-leave')).toEqual(true)
-
-    // The leave class should still be there since the animationDuration property is 100ms.
-    await new Promise((resolve) =>
-        setTimeout(() => {
-            resolve();
-        }, 99)
-    )
-
-    expect(document.querySelector('span').classList.contains('animation-leave')).toEqual(true)
-
-    // The class shouldn't be there anymore.
-    await new Promise((resolve) =>
-        setTimeout(() => {
-            resolve();
-        }, 10)
-    )
-
-    expect(document.querySelector('span').classList.contains('animation-leave')).toEqual(false)
-})