From 303d66ecf92266ac443dcedb4d5897ddb36ed94d Mon Sep 17 00:00:00 2001 From: Hans Date: Fri, 3 Jan 2025 11:05:44 +0100 Subject: [PATCH] first commit --- README.md | 88 ++++--------- builds/cdn.js | 4 +- builds/module.js | 4 +- dist/FILE.esm.js | 1 - dist/FILE.min.js | 1 - dist/collapse-plus.esm.js | 1 + dist/collapse-plus.min.js | 1 + index.html | 26 +++- package-lock.json | 4 +- package.json | 17 +-- pnpm-lock.yaml | 255 ++++++++++++++++++++++++++++++++++++++ scripts/build.js | 4 +- src/index.js | 153 ++++++++++++++++++++++- 13 files changed, 467 insertions(+), 92 deletions(-) delete mode 100644 dist/FILE.esm.js delete mode 100644 dist/FILE.min.js create mode 100644 dist/collapse-plus.esm.js create mode 100644 dist/collapse-plus.min.js create mode 100644 pnpm-lock.yaml diff --git a/README.md b/README.md index c4df088..703d399 100644 --- a/README.md +++ b/README.md @@ -1,65 +1,13 @@ -**--- DELETE START ---** +# 📂 Alpine Collapse Plus 📂 -# Alpine JS Plugin Template - -This is a template repository to help developers quickly build Alpine JS -plugins. - -## How to Use - -1. Clone the repository with the "Use this template" button on GitHub -2. Run `npm install` to install ES Build -3. Build your plugin - -### Compiling - -To compile the code you run `npm run build` which will create two files in the -`/dist` directory. - -### Testing - -In this template you will find a `index.html` file that you can use for testing -how the Alpine JS plugin works. - -I recommend using [vercel/serve](https://www.npmjs.com/package/serve) to serve -this file. - -## Things to Change - -- Find and replace "PLUGIN" with the name of your plugin -- Find and replace "FILE" with the name of your compiled file -- Find and replace "DESCRIPTION" with a description of your plugin -- Uncomment "index.html" in the `.gitignore` file - -🚨 Make sure find and replace is case sensitive - -If you were creating a plugin called "Alpine JS CSV" you could do the following: - -- "PLUGIN" to "alpinejs-csv" -- "FILE" to "csv" -- "DESCRIPTION" to "Transform data into a CSV with Alpine JS 📈" - ---- - -### License - -The choice of adding a license and what license is best for your project is up -to you. - -[Adding a License on GitHub](https://docs.github.com/en/communities/setting-up-your-project-for-healthy-contributions/adding-a-license-to-a-repository) - -**--- DELETE END ---** - -# PLUGIN - -DESCRIPTION +An Alpine JS plugin to enhance the collapse directive. It seperates transform and opacity animations for a more organic transition. ## Install -### With a CDN +### Via CDN ```html - + ``` @@ -67,27 +15,35 @@ DESCRIPTION ### With a Package Manager ```shell -yarn add -D PLUGIN - -npm install -D PLUGIN +npm install -D alpine-collapse-plus ``` ```js import Alpine from 'alpinejs' -import FILE from 'PLUGIN' +import collapse from 'alpine-collapse-plus' -Alpine.plugin(FILE) +Alpine.plugin(collapse) Alpine.start() ``` ## Example -Examples of how the plugin works. +See index.html for an example with default settings. + +## Modifiers + +- `duration` - The duration of the collapse/expand transition in milliseconds. See original [Alpine.js collapse directive](https://alpinejs.dev/plugins/collapse#duration) for more information. +- `min` - The minimum height of the collapse/expand transition in pixels. See original [Alpine.js collapse directive](https://alpinejs.dev/plugins/collapse#min) for more information. + +- `content-delay` - The delay of the opacity transition in milliseconds. +- `content-duration` - The duration of the content opacity transition. +- `content-duration-out` - The duration of the content opacity transition when closing. +- `content-stagger` - The stagger interval ## Stats -![](https://img.shields.io/bundlephobia/min/PLUGIN) -![](https://img.shields.io/npm/v/PLUGIN) -![](https://img.shields.io/npm/dt/PLUGIN) -![](https://img.shields.io/github/license/markmead/PLUGIN) +![](https://img.shields.io/bundlephobia/min/alpine-collapse-plus) +![](https://img.shields.io/npm/v/alpine-collapse-plus) +![](https://img.shields.io/npm/dt/alpine-collapse-plus) +![](https://img.shields.io/github/license/markmead/alpine-collapse-plus) diff --git a/builds/cdn.js b/builds/cdn.js index 97db91f..9858ea8 100644 --- a/builds/cdn.js +++ b/builds/cdn.js @@ -1,3 +1,3 @@ -import FILE from '../src/index.js' +import collapse from '../src/index.js' -document.addEventListener('alpine:init', () => window.Alpine.plugin(FILE)) +document.addEventListener('alpine:init', () => window.Alpine.plugin(collapse)) diff --git a/builds/module.js b/builds/module.js index fe3e06f..2dad1fc 100644 --- a/builds/module.js +++ b/builds/module.js @@ -1,3 +1,3 @@ -import FILE from '../src/index.js' +import collapse from '../src/index.js' -export default FILE +export default collapse-plus diff --git a/dist/FILE.esm.js b/dist/FILE.esm.js deleted file mode 100644 index dd133d9..0000000 --- a/dist/FILE.esm.js +++ /dev/null @@ -1 +0,0 @@ -function i(e){e.directive("[name]",(t,{value:a,modifiers:f,expression:l},{Alpine:n,effect:o,cleanup:r})=>{}),e.magic("[name]",(t,{Alpine:a})=>{})}var c=i;export{c as default}; diff --git a/dist/FILE.min.js b/dist/FILE.min.js deleted file mode 100644 index 07bd082..0000000 --- a/dist/FILE.min.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{function i(e){e.directive("[name]",(n,{value:t,modifiers:l,expression:a},{Alpine:o,effect:p,cleanup:d})=>{}),e.magic("[name]",(n,{Alpine:t})=>{})}document.addEventListener("alpine:init",()=>window.Alpine.plugin(i));})(); diff --git a/dist/collapse-plus.esm.js b/dist/collapse-plus.esm.js new file mode 100644 index 0000000..60e826c --- /dev/null +++ b/dist/collapse-plus.esm.js @@ -0,0 +1 @@ +function c(t,n,r){if(t.indexOf(n)===-1)return r;let e=t[t.indexOf(n)+1];if(!e)return r;if(["duration","time","stagger"].some(i=>n.includes(i))){let i=e.match(/([0-9]+)ms/);if(i)return i[1]}if(n==="min"){let i=e.match(/([0-9]+)px/);if(i)return i[1]}return e}function y(t,{modifiers:n}){let r=c(n,"duration",250)/1e3,e=c(n,"min",0),i=!n.includes("min"),d=c(n,"content-duration",r*1e3)/1e3,x=c(n,"content-duration-out",d*300)/1e3,_=c(n,"content-stagger",50)/1e3,S=c(n,"content-delay",r*300)/1e3,u=t.querySelectorAll("[x-collapse-content] > *");t._x_isShown||(t.style.height=`${e}px`),!t._x_isShown&&i&&(t.hidden=!0),t._x_isShown||(t.style.overflow="hidden");let w=(l,a)=>{let o=Alpine.setStyles(l,a);return a.height?()=>{}:o},g={transitionProperty:"height",transitionDuration:`${r}s`,transitionTimingFunction:"cubic-bezier(0.4, 0.0, 0.2, 1)"};t._x_transition={in(l=()=>{},a=()=>{}){i&&(t.hidden=!1),i&&(t.style.display=null);for(let[h,f]of u.entries())f.style.opacity=0,f.style.transition=`opacity ${d}s cubic-bezier(0.4, 0.0, 0.2, 1)`,f.style.transitionDelay=`${S+h*_}s`;let o=t.getBoundingClientRect().height;t.style.height="auto";let s=t.getBoundingClientRect().height;o===s&&(o=e),Alpine.transition(t,Alpine.setStyles,{during:g,start:{height:`${o}px`},end:{height:`${s}px`}},()=>{t._x_isShown=!0;for(let h of u)h.style.opacity=1},()=>{Math.abs(t.getBoundingClientRect().height-s)<1&&(t.style.overflow=null)})},out(l=()=>{},a=()=>{}){let o=t.getBoundingClientRect().height;for(let s of u)s.style.transitionDelay=0,s.style.transitionDuration=`${x}s`,s.style.opacity=0;Alpine.transition(t,w,{during:g,start:{height:`${o}px`},end:{height:`${e}px`}},()=>{t.style.overflow="hidden"},()=>{t._x_isShown=!1,t.style.height===`${e}px`&&i&&(t.style.display="none",t.hidden=!0)})}}}y.inline=(t,{modifiers:n})=>{n.includes("min")&&(t._x_doShow=()=>{},t._x_doHide=()=>{})};function p(t){t.directive("collapse",y)}var D=p-plus;export{D as default}; diff --git a/dist/collapse-plus.min.js b/dist/collapse-plus.min.js new file mode 100644 index 0000000..4a8a657 --- /dev/null +++ b/dist/collapse-plus.min.js @@ -0,0 +1 @@ +(()=>{function c(t,n,r){if(t.indexOf(n)===-1)return r;let e=t[t.indexOf(n)+1];if(!e)return r;if(["duration","time","stagger"].some(i=>n.includes(i))){let i=e.match(/([0-9]+)ms/);if(i)return i[1]}if(n==="min"){let i=e.match(/([0-9]+)px/);if(i)return i[1]}return e}function p(t,{modifiers:n}){let r=c(n,"duration",250)/1e3,e=c(n,"min",0),i=!n.includes("min"),f=c(n,"content-duration",r*1e3)/1e3,x=c(n,"content-duration-out",f*300)/1e3,_=c(n,"content-stagger",50)/1e3,w=c(n,"content-delay",r*300)/1e3,u=t.querySelectorAll("[x-collapse-content] > *");t._x_isShown||(t.style.height=`${e}px`),!t._x_isShown&&i&&(t.hidden=!0),t._x_isShown||(t.style.overflow="hidden");let S=(l,a)=>{let o=Alpine.setStyles(l,a);return a.height?()=>{}:o},g={transitionProperty:"height",transitionDuration:`${r}s`,transitionTimingFunction:"cubic-bezier(0.4, 0.0, 0.2, 1)"};t._x_transition={in(l=()=>{},a=()=>{}){i&&(t.hidden=!1),i&&(t.style.display=null);for(let[h,d]of u.entries())d.style.opacity=0,d.style.transition=`opacity ${f}s cubic-bezier(0.4, 0.0, 0.2, 1)`,d.style.transitionDelay=`${w+h*_}s`;let o=t.getBoundingClientRect().height;t.style.height="auto";let s=t.getBoundingClientRect().height;o===s&&(o=e),Alpine.transition(t,Alpine.setStyles,{during:g,start:{height:`${o}px`},end:{height:`${s}px`}},()=>{t._x_isShown=!0;for(let h of u)h.style.opacity=1},()=>{Math.abs(t.getBoundingClientRect().height-s)<1&&(t.style.overflow=null)})},out(l=()=>{},a=()=>{}){let o=t.getBoundingClientRect().height;for(let s of u)s.style.transitionDelay=0,s.style.transitionDuration=`${x}s`,s.style.opacity=0;Alpine.transition(t,S,{during:g,start:{height:`${o}px`},end:{height:`${e}px`}},()=>{t.style.overflow="hidden"},()=>{t._x_isShown=!1,t.style.height===`${e}px`&&i&&(t.style.display="none",t.hidden=!0)})}}}p.inline=(t,{modifiers:n})=>{n.includes("min")&&(t._x_doShow=()=>{},t._x_doHide=()=>{})};function y(t){t.directive("collapse",p)}document.addEventListener("alpine:init",()=>window.Alpine.plugin(y));})(); diff --git a/index.html b/index.html index 7f727bb..c597b9a 100644 --- a/index.html +++ b/index.html @@ -4,17 +4,37 @@ - Alpine JS Plugin + Alpine Collapse Plus Demo - + + - + +
+ +
+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum consequat nunc ac enim consectetur, in tempor lorem facilisis. Sed vehicula, ipsum sit amet tincidunt tincidunt, nisi enim varius nisi, id efficitur nulla nulla vel tellus. Donec euismod, nisi vel tincidunt ultricies, nunc dolor ultricies nunc, vitae aliquam nunc nisi vel nunc. + Praesent consectetur libero at justo vehicula, non fermentum dolor tincidunt. Suspendisse potenti. Nullam consectetur, nunc vitae aliquam tincidunt, nisi nunc tincidunt nisi, vitae aliquam nunc nisi vel nunc. Donec euismod, nisi vel tincidunt ultricies, nunc dolor ultricies nunc, vitae aliquam nunc nisi vel nunc. +

+
+
+
+ diff --git a/package-lock.json b/package-lock.json index 03c1872..fdf486b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "PLUGIN", + "name": "alpine-collapse-plus", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "PLUGIN", + "name": "alpine-collapse-plus", "version": "1.0.0", "devDependencies": { "esbuild": "^0.21.4" diff --git a/package.json b/package.json index 3a4e545..6045c2b 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,20 @@ { - "name": "PLUGIN", - "version": "1.0.0", - "description": "DESCRIPTION", + "name": "alpine-collapse-plus", + "version": "0.0.1", + "description": "An Alpine JS plugin that replaces the collapse directive. It seperates transform and opacity animations for a more seamless transition.", "keywords": [ "Alpine", "Alpine JS", "Alpine JS Plugin", - "Alpine JS Plugins" + "Alpine JS Plugins", + "Alpine Collapse", + "Alpine Collapse Plus" ], - "module": "dist/FILE.esm.js", - "unpkg": "dist/FILE.min.js", + "module": "dist/collapse-plus.esm.js", + "unpkg": "dist/collapse-plus.min.js", "scripts": { - "build": "node scripts/build.js" + "build": "node scripts/build.js", + "preview": "npx serve" }, "devDependencies": { "esbuild": "^0.21.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..eda3abb --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,255 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + esbuild: + specifier: ^0.21.4 + version: 0.21.5 + +packages: + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + +snapshots: + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 diff --git a/scripts/build.js b/scripts/build.js index 98f44a4..d8f1f85 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,11 +1,11 @@ buildPlugin({ entryPoints: ['builds/cdn.js'], - outfile: 'dist/FILE.min.js', + outfile: 'dist/collapse-plus.min.js', }) buildPlugin({ entryPoints: ['builds/module.js'], - outfile: 'dist/FILE.esm.js', + outfile: 'dist/collapse-plus.esm.js', platform: 'neutral', mainFields: ['main', 'module'], }) diff --git a/src/index.js b/src/index.js index 3eb86b5..d43227f 100644 --- a/src/index.js +++ b/src/index.js @@ -1,8 +1,149 @@ -export default function (Alpine) { - Alpine.directive( - '[name]', - (el, { value, modifiers, expression }, { Alpine, effect, cleanup }) => {} - ) +function modifierValue(modifiers, key, fallback) { + // If the modifier isn't present, use the default. + if (modifiers.indexOf(key) === -1) return fallback + + // If it IS present, grab the value after it: x-show.transition.duration.500ms + const rawValue = modifiers[modifiers.indexOf(key) + 1] + + if (!rawValue) return fallback + + if (['duration', 'time', 'stagger'].some(k => key.includes(k))) { + // Support x-collapse.duration.500ms && duration.500 + const match = rawValue.match(/([0-9]+)ms/) + if (match) return match[1] + } - Alpine.magic('[name]', (el, { Alpine }) => {}) + if (key === 'min') { + // Support x-collapse.min.100px && min.100 + const match = rawValue.match(/([0-9]+)px/) + if (match) return match[1] + } + + return rawValue } + +function collapse(el, { modifiers }) { + const duration = modifierValue(modifiers, 'duration', 250) / 1000 + const floor = modifierValue(modifiers, 'min', 0) + const fullyHide = !modifiers.includes('min') + + const contentDuration = modifierValue(modifiers, 'content-duration', duration * 1000) / 1000 + const contentDurationOut = modifierValue(modifiers, 'content-duration-out', contentDuration * 300) / 1000 + const contentStagger = modifierValue(modifiers, 'content-stagger', 50) / 1000 + const contentDelay = modifierValue(modifiers, 'content-delay', duration * 300) / 1000 + + // actual content if specified - otherwise regular collapse/expand transition + const contentElements = el.querySelectorAll("[x-collapse-content] > *") + + if (!el._x_isShown) el.style.height = `${floor}px` + // We use the hidden attribute for the benefit of Tailwind + // users as the .space utility will ignore [hidden] elements. + // We also use display:none as the hidden attribute has very + // low CSS specificity and could be accidentally overridden + // by a user. + 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 + // revert updates to the height style. + const setFunction = (el, styles) => { + const revertFunction = Alpine.setStyles(el, styles) + + return styles.height ? () => { } : revertFunction + } + + const transitionStyles = { + transitionProperty: "height", + transitionDuration: `${duration}s`, + transitionTimingFunction: "cubic-bezier(0.4, 0.0, 0.2, 1)" + } + + el._x_transition = { + in(before = () => { }, after = () => { }) { + if (fullyHide) el.hidden = false + if (fullyHide) el.style.display = null + + for (const [index, el] of contentElements.entries()) { + el.style.opacity = 0 + el.style.transition = `opacity ${contentDuration}s cubic-bezier(0.4, 0.0, 0.2, 1)` + el.style.transitionDelay = `${contentDelay + index * contentStagger}s` + } + + let current = el.getBoundingClientRect().height + + el.style.height = "auto" + + const full = el.getBoundingClientRect().height + + if (current === full) { + current = floor + } + + Alpine.transition( + el, + Alpine.setStyles, + { + during: transitionStyles, + start: { height: `${current}px` }, + end: { height: `${full}px` } + }, + () => { + el._x_isShown = true + for (const el of contentElements) { + el.style.opacity = 1 + } + }, + () => { + if (Math.abs(el.getBoundingClientRect().height - full) < 1) { + el.style.overflow = null + } + } + ) + }, + + out(before = () => { }, after = () => { }) { + const full = el.getBoundingClientRect().height + + for (const el of contentElements) { + el.style.transitionDelay = 0 + el.style.transitionDuration = `${contentDurationOut}s` + el.style.opacity = 0 + } + + Alpine.transition( + el, + setFunction, + { + during: transitionStyles, + start: { height: `${full}px` }, + end: { height: `${floor}px` } + }, + () => { + el.style.overflow = "hidden" + }, + () => { + el._x_isShown = false + + // check if element is fully collapsed + if (el.style.height === `${floor}px` && fullyHide) { + el.style.display = "none" + el.hidden = true + } + } + ) + } + } +} + +// 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 = () => { } +} + +export default function (Alpine) { + Alpine.directive("collapse", collapse) +} \ No newline at end of file