Caleb Porzio há 3 anos atrás
pai
commit
b97f608642

+ 25 - 6
index.html

@@ -3,16 +3,35 @@
     <script src="./packages/morph/dist/cdn.js" defer></script>
     <script src="./packages/history/dist/cdn.js"></script>
     <script src="./packages/persist/dist/cdn.js"></script>
-    <script src="./packages/trap/dist/cdn.js"></script>
+    <script src="./packages/trap/dist/cdn.js" defer></script>
+    <script src="./packages/collapse/dist/cdn.js" defer></script>
     <script src="./packages/alpinejs/dist/cdn.js" defer></script>
+    <!-- <script src="https://cdn-tailwindcss.vercel.app/"></script> -->
     <!-- <script src="https://unpkg.com/alpinejs@3.0.0/dist/cdn.min.js" defer></script> -->
 
     <!-- Play around. -->
-    <div x-data="{ open: false }">
-        <button @click="open = !open">Toggle</button>
+    <div x-data="{ show: false }">
+        <button @click="show = ! show">toggle modal</button>
+    
+        <template x-portal="modals">
+            <div x-show="show" x-trap="show">
+                Hi! I'm a modal!
 
-        <span x-show="open">
-            Content...
-        </span>
+                <div x-data="{ show: false }">
+                    <button @click="show = ! show">toggle modal</button>
+                
+                    <template x-portal="modals">
+                        <div x-show="show">
+                            Hi! I'm a modal!
+                        </div>
+                    </template>
+                </div>
+            </div>
+        </template>
     </div>
+
+    <div>---</div>
+    <div>---</div>
+
+    <template x-portal-target="modals"></template>
 </html>

+ 2 - 0
packages/alpinejs/src/directives.js

@@ -168,6 +168,8 @@ let directiveOrder = [
     'show',
     'if',
     DEFAULT,
+    'portal',
+    'portal-target',
     'element',
 ]
 

+ 1 - 0
packages/alpinejs/src/directives/index.js

@@ -13,3 +13,4 @@ import './x-for'
 import './x-ref'
 import './x-if'
 import './x-on'
+import './x-portal'

+ 6 - 0
packages/alpinejs/src/directives/x-on.js

@@ -7,6 +7,12 @@ mapAttributes(startingWith('@', into(prefix('on:'))))
 
 directive('on', skipDuringClone((el, { value, modifiers, expression }, { cleanup }) => {
     let evaluate = expression ? evaluateLater(el, expression) : () => {}
+   
+    // Forward events liseners on portals.
+    if (el.tagName.toLowerCase() === 'template') {
+        if (! el._x_forwardEvents) el._x_forwardEvents = []
+        if (! el._x_forwardEvents.includes(value)) el._x_forwardEvents.push(value)
+    }
 
     let removeListener = on(el, value, modifiers, e => {
         evaluate(() => {}, { scope: { '$event': e }, params: [e] })

+ 62 - 0
packages/alpinejs/src/directives/x-portal.js

@@ -0,0 +1,62 @@
+import { addScopeToNode } from '../scope'
+import { directive, prefix } from '../directives'
+import { addInitSelector, initTree } from '../lifecycle'
+import { mutateDom } from '../mutation'
+
+class MapSet {
+    map = new Map
+
+    get(name) {
+        if (! this.map.has(name)) this.map.set(name, new Set)
+
+        return this.map.get(name)
+    }
+
+    add(name, value) { this.get(name).add(value) }
+
+    each(name, callback) { this.map.get(name).forEach(callback) }
+
+    delete(name, value) {
+        this.map.get(name).delete(value)
+    }
+}
+
+let portals = new MapSet
+
+directive('portal', (el, { expression }, { effect, cleanup }) => {
+    let init = (target) => {
+        let clone = el.content.cloneNode(true).firstElementChild
+
+        // Forward event listeners:
+        if (el._x_forwardEvents) {
+            el._x_forwardEvents.forEach(eventName => {
+                clone.addEventListener(eventName, e => {
+                    e.stopPropagation()
+                    
+                    el.dispatchEvent(new e.constructor(e.type, e))
+                })
+            })
+        }
+
+        addScopeToNode(clone, {}, el)
+
+        mutateDom(() => {
+            target.after(clone)
+
+            initTree(clone)
+        })
+
+        cleanup(() => {
+            clone.remove()
+           
+            portals.delete(expression, init) 
+        })
+    }
+
+    portals.add(expression, init)
+})
+
+addInitSelector(() => `[${prefix('portal-target')}]`)
+directive('portal-target', (el, { expression }) => {
+    portals.each(expression, initPortal => initPortal(el))
+})