Explorar el Código

Add x-collapse.min modifier

Caleb Porzio hace 3 años
padre
commit
791d3a91f6

+ 21 - 11
packages/alpinejs/src/directives/x-show.js

@@ -6,21 +6,31 @@ import { once } from '../utils/once'
 directive('show', (el, { modifiers, expression }, { effect }) => {
 directive('show', (el, { modifiers, expression }, { effect }) => {
     let evaluate = evaluateLater(el, expression)
     let evaluate = evaluateLater(el, expression)
 
 
-    let hide = () => mutateDom(() => {
-        el.style.display = 'none'
+    // We're going to set this function on the element directly so that
+    // other plugins like "Collapse" can overwrite them with their own logic.
+    if (! el._x_doHide) el._x_doHide = () => {
+        mutateDom(() => el.style.display = 'none')
+    }
 
 
-        el._x_isShown = false
-    })
+    if (! el._x_doShow) el._x_doShow = () => {
+        mutateDom(() => {
+            if (el.style.length === 1 && el.style.display === 'none') {
+                el.removeAttribute('style')
+            } else {
+                el.style.removeProperty('display')
+            }
+        })
+    }
 
 
-    let show = () => mutateDom(() => {
-        if (el.style.length === 1 && el.style.display === 'none') {
-            el.removeAttribute('style')
-        } else {
-            el.style.removeProperty('display')
-        }
+    let hide = () => {
+        el._x_doHide()
+        el._x_isShown = false
+    }
 
 
+    let show = () => {
+        el._x_doShow()
         el._x_isShown = true
         el._x_isShown = true
-    })
+    }
 
 
     // We are wrapping this function in a setTimeout here to prevent
     // We are wrapping this function in a setTimeout here to prevent
     // a race condition from happening where elements that have a
     // a race condition from happening where elements that have a

+ 25 - 7
packages/collapse/src/index.js

@@ -1,7 +1,19 @@
 export default function (Alpine) {
 export default function (Alpine) {
-    Alpine.directive('collapse', (el, { expression, modifiers }, { effect, evaluateLater }) => {
+    Alpine.directive('collapse', collapse)
+
+    // If we're using a "minimum height", we'll need to disable
+    // x-show's default behavior of setting display: 'none'.
+    collapse.inline = (el, { modifiers }) => {
+        if (! modifiers.includes('min')) return
+
+        el._x_doShow = () => {}
+        el._x_doHide = () => {}
+    }
+
+    function collapse(el, { modifiers }) {
         let duration = modifierValue(modifiers, 'duration', 250) / 1000
         let duration = modifierValue(modifiers, 'duration', 250) / 1000
-        let floor = 0
+        let floor = modifierValue(modifiers, 'min', 0)
+        let fullyHide = ! modifiers.includes('min')
 
 
         if (! el._x_isShown) el.style.height = `${floor}px`
         if (! el._x_isShown) el.style.height = `${floor}px`
         // We use the hidden attribute for the benefit of Tailwind
         // We use the hidden attribute for the benefit of Tailwind
@@ -9,7 +21,7 @@ export default function (Alpine) {
         // We also use display:none as the hidden attribute has very
         // We also use display:none as the hidden attribute has very
         // low CSS specificity and could be accidentally overriden
         // low CSS specificity and could be accidentally overriden
         // by a user.
         // by a user.
-        if (! el._x_isShown) el.hidden = true
+        if (! el._x_isShown && fullyHide) el.hidden = true
         if (! el._x_isShown) el.style.overflow = 'hidden'
         if (! el._x_isShown) el.style.overflow = 'hidden'
 
 
         // Override the setStyles function with one that won't
         // Override the setStyles function with one that won't
@@ -28,8 +40,8 @@ export default function (Alpine) {
 
 
         el._x_transition = {
         el._x_transition = {
             in(before = () => {}, after = () => {}) {
             in(before = () => {}, after = () => {}) {
-                el.hidden = false;
-                el.style.display = null
+                if (fullyHide) el.hidden = false;
+                if (fullyHide) el.style.display = null
 
 
                 let current = el.getBoundingClientRect().height
                 let current = el.getBoundingClientRect().height
 
 
@@ -61,14 +73,14 @@ export default function (Alpine) {
                     el._x_isShown = false
                     el._x_isShown = false
 
 
                     // check if element is fully collapsed
                     // check if element is fully collapsed
-                    if (el.style.height == `${floor}px`) {
+                    if (el.style.height == `${floor}px` && fullyHide) {
                         el.style.display = 'none'
                         el.style.display = 'none'
                         el.hidden = true
                         el.hidden = true
                     }
                     }
                 })
                 })
             },
             },
         }
         }
-    })
+    }
 }
 }
 
 
 function modifierValue(modifiers, key, fallback) {
 function modifierValue(modifiers, key, fallback) {
@@ -86,5 +98,11 @@ function modifierValue(modifiers, key, fallback) {
         if (match) return match[1]
         if (match) return match[1]
     }
     }
 
 
+    if (key === 'min') {
+        // Support x-collapse.min.100px && min.100
+        let match = rawValue.match(/([0-9]+)px/)
+        if (match) return match[1]
+    }
+
     return rawValue
     return rawValue
 }
 }

+ 29 - 0
packages/docs/src/en/plugins/collapse.md

@@ -107,3 +107,32 @@ You can customize the duration of the collapse/expand transition by appending th
     </div>
     </div>
 </div>
 </div>
 <!-- END_VERBATIM -->
 <!-- END_VERBATIM -->
+
+<a name="dot-min"></a>
+### .min
+
+By default, `x-collapse`'s "collapsed" state sets the height of the element to `0px` and also sets `display: none;`.
+
+Sometimes, it's helpful to "cut-off" an element rather than fully hide it. By using the `.min` modifier, you can set a minimum height for `x-collapse`'s "collapsed" state. For example:
+
+```alpine
+<div x-data="{ expanded: false }">
+    <button @click="expanded = ! expanded">Toggle Content</button>
+
+    <p x-show="expanded" x-collapse.min.50px>
+        ...
+    </p>
+</div>
+```
+
+<!-- START_VERBATIM -->
+<div x-data="{ expanded: false }" class="demo">
+    <button @click="expanded = ! expanded">Toggle Content</button>
+
+    <div x-show="expanded" x-collapse.min.50px>
+        <div class="pt-4">
+            Reprehenderit eu excepteur ullamco esse cillum reprehenderit exercitation labore non. Dolore dolore ea dolore veniam sint in sint ex Lorem ipsum. Sint laborum deserunt deserunt amet voluptate cillum deserunt. Amet nisi pariatur sit ut id. Ipsum est minim est commodo id dolor sint id quis sint Lorem.
+        </div>
+    </div>
+</div>
+<!-- END_VERBATIM -->

+ 19 - 0
tests/cypress/integration/plugins/collapse.spec.js

@@ -21,6 +21,25 @@ test('can collapse and expand element',
     },
     },
 )
 )
 
 
+test('can collapse and expand with a minimum height instead of "display: none"',
+    [html`
+        <div x-data="{ expanded: false }">
+            <button @click="expanded = ! expanded">toggle</button>
+            <h1 x-show="expanded" x-collapse.min.25px>contents <a href="#">focusable content</a></h1>
+        </div>
+    `],
+    ({ get }) => {
+        get('h1').should(haveComputedStyle('height', '25px'))
+        get('h1').should(haveAttribute('style', 'height: 25px; overflow: hidden;'))
+        get('h1').should(notHaveAttribute('hidden', 'hidden'))
+        get('button').click()
+        get('h1').should(haveAttribute('style', 'height: auto;'))
+        get('button').click()
+        get('h1').should(haveComputedStyle('height', '25px'))
+        get('h1').should(haveAttribute('style', 'height: 25px; overflow: hidden;'))
+    },
+)
+
 test('@click.away with x-collapse (prevent race condition)',
 test('@click.away with x-collapse (prevent race condition)',
     html`
     html`
         <div x-data="{ show: false }">
         <div x-data="{ show: false }">