ソースを参照

Add x-collapse.min modifier

Caleb Porzio 3 年 前
コミット
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 }) => {
     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
-    })
+    }
 
     // We are wrapping this function in a setTimeout here to prevent
     // 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) {
-    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 floor = 0
+        let floor = modifierValue(modifiers, 'min', 0)
+        let fullyHide = ! modifiers.includes('min')
 
         if (! el._x_isShown) el.style.height = `${floor}px`
         // 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
         // low CSS specificity and could be accidentally overriden
         // 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'
 
         // Override the setStyles function with one that won't
@@ -28,8 +40,8 @@ export default function (Alpine) {
 
         el._x_transition = {
             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
 
@@ -61,14 +73,14 @@ export default function (Alpine) {
                     el._x_isShown = false
 
                     // check if element is fully collapsed
-                    if (el.style.height == `${floor}px`) {
+                    if (el.style.height == `${floor}px` && fullyHide) {
                         el.style.display = 'none'
                         el.hidden = true
                     }
                 })
             },
         }
-    })
+    }
 }
 
 function modifierValue(modifiers, key, fallback) {
@@ -86,5 +98,11 @@ function modifierValue(modifiers, key, fallback) {
         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
 }

+ 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>
 <!-- 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)',
     html`
         <div x-data="{ show: false }">