Caleb Porzio há 1 ano atrás
pai
commit
b7e2d702e1

+ 7 - 0
index.html

@@ -6,10 +6,17 @@
     <script src="./packages/mask/dist/cdn.js"></script>
     <script src="./packages/ui/dist/cdn.js" defer></script> -->
     <script src="./packages/anchor/dist/cdn.js" defer></script>
+    <script src="./packages/sort/dist/cdn.js" defer></script>
     <script src="./packages/alpinejs/dist/cdn.js" defer></script>
     <!-- <script src="//cdn.tailwindcss.com"></script> -->
     <!-- <script src="//cdn.tailwindcss.com"></script> -->
 
+    <div x-data x-sort>
+        <div x-sort:item >foo</div>
+        <div >foo</div>
+        <div x-sort:item >foo</div>
+    </div>
+
     <div x-data="{ val: true }"
     >
    <input type="text" x-model.boolean="val">

+ 74 - 72
packages/docs/src/en/plugins/sort.md

@@ -52,22 +52,22 @@ Alpine.plugin(sort)
 <a name="basic-usage"></a>
 ## Basic usage
 
-The primary API for using this plugin is the `x-sort` directive. By adding `x-sort` to an element, its children become sortable—meaning you can drag them around with your mouse, and they will change positions.
+The primary API for using this plugin is the `x-sort` directive. By adding `x-sort` to an element, its children containing `x-sort:item` become sortable—meaning you can drag them around with your mouse, and they will change positions.
 
 ```alpine
 <ul x-sort>
-    <li>foo</li>
-    <li>bar</li>
-    <li>baz</li>
+    <li x-sort:item>foo</li>
+    <li x-sort:item>bar</li>
+    <li x-sort:item>baz</li>
 </ul>
 ```
 
 <!-- START_VERBATIM -->
 <div x-data>
     <ul x-sort>
-        <li>foo</li>
-        <li>bar</li>
-        <li>baz</li>
+        <li x-sort:item>foo</li>
+        <li x-sort:item>bar</li>
+        <li x-sort:item>baz</li>
     </ul>
 </div>
 <!-- END_VERBATIM -->
@@ -75,36 +75,36 @@ The primary API for using this plugin is the `x-sort` directive. By adding `x-so
 <a name="sort-handlers"></a>
 ## Sort handlers
 
-You can react to sorting changes by passing a handler function to `x-sort` and adding keys to each item using `x-sort:key`. Here is an example of a simple handler function that shows an alert dialog with the changed item's key and its new position:
+You can react to sorting changes by passing a handler function to `x-sort` and adding keys to each item using `x-sort:item`. Here is an example of a simple handler function that shows an alert dialog with the changed item's key and its new position:
 
 ```alpine
-<ul x-sort="alert($key + ' - ' + $position)">
-    <li x-sort:key="1">foo</li>
-    <li x-sort:key="2">bar</li>
-    <li x-sort:key="3">baz</li>
+<ul x-sort="alert($item + ' - ' + $position)">
+    <li x-sort:item="1">foo</li>
+    <li x-sort:item="2">bar</li>
+    <li x-sort:item="3">baz</li>
 </ul>
 ```
 
 <!-- START_VERBATIM -->
 <div x-data>
-    <ul x-sort="alert($key + ' - ' + $position)">
-        <li x-sort:key="1">foo</li>
-        <li x-sort:key="2">bar</li>
-        <li x-sort:key="3">baz</li>
+    <ul x-sort="alert($item + ' - ' + $position)">
+        <li x-sort:item="1">foo</li>
+        <li x-sort:item="2">bar</li>
+        <li x-sort:item="3">baz</li>
     </ul>
 </div>
 <!-- END_VERBATIM -->
 
-The `x-sort` handler will be called every time the sort order of the items change. The `$key` magic will contain the key of the sorted element (derived from `x-sort:key`), and `$position` will contain the new position of the item (staring at index `0`).
+The `x-sort` handler will be called every time the sort order of the items change. The `$item` magic will contain the key of the sorted element (derived from `x-sort:item`), and `$position` will contain the new position of the item (staring at index `0`).
 
-You can also pass a handler function to `x-sort` and that function will receive the `key` and `position` as the first and second parameter:
+You can also pass a handler function to `x-sort` and that function will receive the `item` and `position` as the first and second parameter:
 
 ```alpine
-<div x-data="{ handle: (key, position) => { ... } }">
+<div x-data="{ handle: (item, position) => { ... } }">
     <ul x-sort="handle">
-        <li x-sort:key="1">foo</li>
-        <li x-sort:key="2">bar</li>
-        <li x-sort:key="3">baz</li>
+        <li x-sort:item="1">foo</li>
+        <li x-sort:item="2">bar</li>
+        <li x-sort:item="3">baz</li>
     </ul>
 </div>
 ```
@@ -114,44 +114,44 @@ Handler functions are often used to persist the new order of items in the databa
 <a name="sorting-groups"></a>
 ## Sorting groups
 
-This plugin allows you to drag items from one `x-sort` sortable list into another one by adding a matching `.group` modifier to both lists:
+This plugin allows you to drag items from one `x-sort` sortable list into another one by adding a matching `x-sort:group` value to both lists:
 
 ```alpine
 <div>
-    <ul x-sort.group.todos>
-        <li x-sort:key="1">foo</li>
-        <li x-sort:key="2">bar</li>
-        <li x-sort:key="3">baz</li>
+    <ul x-sort x-sort:group="todos">
+        <li x-sort:item="1">foo</li>
+        <li x-sort:item="2">bar</li>
+        <li x-sort:item="3">baz</li>
     </ul>
 
-    <ol x-sort.group.todos>
-        <li x-sort:key="1">foo</li>
-        <li x-sort:key="2">bar</li>
-        <li x-sort:key="3">baz</li>
+    <ol x-sort x-sort:group="todos">
+        <li x-sort:item="4">foo</li>
+        <li x-sort:item="5">bar</li>
+        <li x-sort:item="6">baz</li>
     </ol>
 </div>
 ```
 
 Because both sortable lists above use the same group name (`todos`), you can drag items from one list onto another.
 
-> When using sort handlers like `x-sort="handle"` and dragging an item from one group to another, only the destination lists handler will be called with the key and new position.
+> When using sort handlers like `x-sort="handle"` and dragging an item from one group to another, only the destination list's handler will be called with the key and new position.
 
 <a name="drag-handles"></a>
 ## Drag handles
 
-By default, each child element of `x-sort` is draggable by clicking and dragging anywhere within it. However, you may want to designate a smaller, more specific element as the "drag handle" so that the rest of the element can be interacted with like normal, and only the handle will respond to mouse dragging:
+By default, each `x-sort:item` element is draggable by clicking and dragging anywhere within it. However, you may want to designate a smaller, more specific element as the "drag handle" so that the rest of the element can be interacted with like normal, and only the handle will respond to mouse dragging:
 
 ```alpine
 <ul x-sort>
-    <li>
+    <li x-sort:item>
         <span x-sort:handle> - </span>foo
     </li>
 
-    <li>
+    <li x-sort:item>
         <span x-sort:handle> - </span>bar
     </li>
 
-    <li>
+    <li x-sort:item>
         <span x-sort:handle> - </span>baz
     </li>
 </ul>
@@ -160,13 +160,13 @@ By default, each child element of `x-sort` is draggable by clicking and dragging
 <!-- START_VERBATIM -->
 <div x-data>
     <ul x-sort>
-        <li>
+        <li x-sort:item>
             <span x-sort:handle> - </span>foo
         </li>
-        <li>
+        <li x-sort:item>
             <span x-sort:handle> - </span>bar
         </li>
-        <li>
+        <li x-sort:item>
             <span x-sort:handle> - </span>baz
         </li>
     </ul>
@@ -186,18 +186,18 @@ If you would like to show a "ghost" of the original element in its place instead
 
 ```alpine
 <ul x-sort.ghost>
-    <li>foo</li>
-    <li>bar</li>
-    <li>baz</li>
+    <li x-sort:item>foo</li>
+    <li x-sort:item>bar</li>
+    <li x-sort:item>baz</li>
 </ul>
 ```
 
 <!-- START_VERBATIM -->
 <div x-data>
     <ul x-sort.ghost>
-        <li>foo</li>
-        <li>bar</li>
-        <li>baz</li>
+        <li x-sort:item>foo</li>
+        <li x-sort:item>bar</li>
+        <li x-sort:item>baz</li>
     </ul>
 </div>
 <!-- END_VERBATIM -->
@@ -217,18 +217,18 @@ This makes it easy to add any custom styling you would like:
 </style>
 
 <ul x-sort.ghost>
-    <li>foo</li>
-    <li>bar</li>
-    <li>baz</li>
+    <li x-sort:item>foo</li>
+    <li x-sort:item>bar</li>
+    <li x-sort:item>baz</li>
 </ul>
 ```
 
 <!-- START_VERBATIM -->
 <div x-data>
     <ul x-sort.ghost x-sort:config="{ ghostClass: 'opacity-50' }">
-        <li>foo</li>
-        <li>bar</li>
-        <li>baz</li>
+        <li x-sort:item>foo</li>
+        <li x-sort:item>bar</li>
+        <li x-sort:item>baz</li>
     </ul>
 </div>
 <!-- END_VERBATIM -->
@@ -256,7 +256,7 @@ To show this only while sorting, you can use the `body.sorting` CSS selector:
 }
 
 body.sorting #sort-warning {
-    display:
+    display: block;
 }
 ```
 
@@ -269,9 +269,9 @@ Consider HTML like the following, where each item in the list is styled differen
 
 ```html
 <div x-sort>
-    <div class="hover:border">foo</div>
-    <div class="hover:border">bar</div>
-    <div class="hover:border">baz</div>
+    <div x-sort:item class="hover:border">foo</div>
+    <div x-sort:item class="hover:border">bar</div>
+    <div x-sort:item class="hover:border">baz</div>
 </div>
 ```
 
@@ -280,9 +280,9 @@ If you drag one of the elements in the list below you will see that the hover ef
 <!-- START_VERBATIM -->
 <div x-data>
     <ul x-sort class="flex flex-col items-start">
-        <li class="hover:border border-black">foo</li>
-        <li class="hover:border border-black">bar</li>
-        <li class="hover:border border-black">baz</li>
+        <li x-sort:item class="hover:border border-black">foo</li>
+        <li x-sort:item class="hover:border border-black">bar</li>
+        <li x-sort:item class="hover:border border-black">baz</li>
     </ul>
 </div>
 <!-- END_VERBATIM -->
@@ -293,9 +293,9 @@ Here is how you can do this directly inline using Tailwind arbitrary variants:
 
 ```html
 <div x-sort>
-    <div class="[body:not(.sorting)_&]:hover:border">foo</div>
-    <div class="[body:not(.sorting)_&]:hover:border">bar</div>
-    <div class="[body:not(.sorting)_&]:hover:border">baz</div>
+    <div x-sort:item class="[body:not(.sorting)_&]:hover:border">foo</div>
+    <div x-sort:item class="[body:not(.sorting)_&]:hover:border">bar</div>
+    <div x-sort:item class="[body:not(.sorting)_&]:hover:border">baz</div>
 </div>
 ```
 
@@ -304,9 +304,9 @@ Now you can see below that the hover effect is only applied to the dragging elem
 <!-- START_VERBATIM -->
 <div x-data>
     <ul x-sort class="flex flex-col items-start">
-        <li class="[body:not(.sorting)_&]:hover:border border-black">foo</li>
-        <li class="[body:not(.sorting)_&]:hover:border border-black">bar</li>
-        <li class="[body:not(.sorting)_&]:hover:border border-black">baz</li>
+        <li x-sort:item class="[body:not(.sorting)_&]:hover:border border-black">foo</li>
+        <li x-sort:item class="[body:not(.sorting)_&]:hover:border border-black">bar</li>
+        <li x-sort:item class="[body:not(.sorting)_&]:hover:border border-black">baz</li>
     </ul>
 </div>
 <!-- END_VERBATIM -->
@@ -317,21 +317,23 @@ Now you can see below that the hover effect is only applied to the dragging elem
 Alpine chooses sensible defaults for configuring [SortableJS](https://github.com/SortableJS/Sortable?tab=readme-ov-file#options) under the hood. However, you can add or override any of these options yourself using `x-sort:config`:
 
 ```alpine
-<ul x-sort x-sort:config="{ filter: '.no-drag' }">
-    <li>foo</li>
-    <li class="no-drag">bar (not dragable)</li>
-    <li>baz</li>
+<ul x-sort x-sort:config="{ animation: 0 }">
+    <li x-sort:item>foo</li>
+    <li x-sort:item>bar</li>
+    <li x-sort:item>baz</li>
 </ul>
 ```
 
 <!-- START_VERBATIM -->
 <div x-data>
-    <ul x-sort x-sort:config="{ filter: '.no-drag' }">
-        <li>foo</li>
-        <li class="no-drag">bar (not dragable)</li>
-        <li>baz</li>
+    <ul x-sort x-sort:config="{ animation: 0 }">
+        <li x-sort:item>foo</li>
+        <li x-sort:item>bar</li>
+        <li x-sort:item>baz</li>
     </ul>
 </div>
 <!-- END_VERBATIM -->
 
+> Any config options passed will overwrite Alpine defaults. In this case of `animation`, this is fine, however be aware that overwriting `handle`, `group`, `filter`, `onSort`, `onStart`, or `onEnd` may break functionality.
+
 [View the full list of SortableJS configuration options here →](https://github.com/SortableJS/Sortable?tab=readme-ov-file#options)

+ 29 - 3
packages/sort/src/index.js

@@ -10,7 +10,12 @@ export default function (Alpine) {
             return // This will get handled by the main directive...
         }
 
-        if (value === 'key') {
+        if (value === 'group') {
+            return // This will get handled by the main directive...
+        }
+
+        // Supporting both `x-sort:item` AND `x-sort:key` (key for BC)...
+        if (value === 'key' || value === 'item') {
             if ([undefined, null, ''].includes(expression)) return
 
             el._x_sort_key = evaluate(expression)
@@ -21,7 +26,7 @@ export default function (Alpine) {
         let preferences = {
             hideGhost: ! modifiers.includes('ghost'),
             useHandles: !! el.querySelector('[x-sort\\:handle]'),
-            group: modifiers.indexOf('group') !== -1 ? modifiers[modifiers.indexOf('group') + 1] : null,
+            group: getGroupName(el, modifiers),
         }
 
         let handleSort = generateSortHandler(expression, evaluateLater)
@@ -52,7 +57,9 @@ function generateSortHandler(expression, evaluateLater) {
                 },
                 // Provide $key and $position to the scope in case they want to call their own function...
                 { scope: {
+                    // Supporting both `$item` AND `$key` ($key for BC)...
                     $key: key,
+                    $item: key,
                     $position: position,
                 } },
             )
@@ -77,6 +84,17 @@ function initSortable(el, config, preferences, handle) {
 
         group: preferences.group,
 
+        filter(e) {
+            // Normally, we would just filter out any elements without `[x-sort:item]`
+            // on them, however for backwards compatibility (when we didn't require
+            // `[x-sort:item]`) we will check for x-sort\\:item being used at all
+            if (! el.querySelector('[x-sort\\:item]')) return false
+
+            let itemHasAttribute = e.target.closest('[x-sort\\:item]')
+
+            return itemHasAttribute ? false : true
+        },
+
         onSort(e) {
             // If item has been dragged between groups...
             if (e.from !== e.to) {
@@ -102,7 +120,6 @@ function initSortable(el, config, preferences, handle) {
             if (preferences.hideGhost && ghostRef) ghostRef.style.opacity = '0'
         },
 
-
         onEnd() {
             document.body.classList.remove('sorting')
 
@@ -129,3 +146,12 @@ function keepElementsWithinMorphMarkers(el) {
         cursor = cursor.nextSibling
     }
 }
+
+function getGroupName(el, modifiers)
+{
+    if (el.hasAttribute('x-sort:group')) {
+        return el.getAttribute('x-sort:group')
+    }
+
+    return modifiers.indexOf('group') !== -1 ? modifiers[modifiers.indexOf('group') + 1] : null
+}

+ 79 - 2
tests/cypress/integration/plugins/sort.spec.js

@@ -55,12 +55,12 @@ test('can use a custom handle',
 test.skip('can move items between groups',
     [html`
         <div x-data>
-            <ul x-sort.group.one>
+            <ul x-sort x-sort:group="one">
                 <li id="1">foo</li>
                 <li id="2">bar</li>
             </ul>
 
-            <ol x-sort.group.one>
+            <ol x-sort x-sort:group="one">
                 <li id="3">oof</li>
                 <li id="4">rab</li>
             </ol>
@@ -104,6 +104,29 @@ test('sort handle method',
     },
 )
 
+test('item is also supported for the key in the sort handle method',
+    [html`
+        <div x-data="{ handle(item, position) { $refs.outlet.textContent = item+'-'+position } }">
+            <ul x-sort="handle">
+                <li x-sort:item="1" id="1">foo</li>
+                <li x-sort:item="2" id="2">bar</li>
+                <li x-sort:item="3" id="3">baz</li>
+            </ul>
+
+            <h1 x-ref="outlet"></h1>
+        </div>
+    `],
+    ({ get }) => {
+        get('#1').drag('#3').then(() => {
+            get('h1').should(haveText('1-2'))
+
+            get('#3').drag('#1').then(() => {
+                get('h1').should(haveText('3-2'))
+            })
+        })
+    },
+)
+
 test('can access key and position in handler',
     [html`
         <div x-data="{ handle(key, position) { $refs.outlet.textContent = key+'-'+position } }">
@@ -127,6 +150,29 @@ test('can access key and position in handler',
     },
 )
 
+test('can access $item instead of $key',
+    [html`
+        <div x-data="{ handle(key, position) { $refs.outlet.textContent = key+'-'+position } }">
+            <ul x-sort="handle($position, $item)">
+                <li x-sort:key="1" id="1">foo</li>
+                <li x-sort:key="2" id="2">bar</li>
+                <li x-sort:key="3" id="3">baz</li>
+            </ul>
+
+            <h1 x-ref="outlet"></h1>
+        </div>
+    `],
+    ({ get }) => {
+        get('#1').drag('#3').then(() => {
+            get('h1').should(haveText('2-1'))
+
+            get('#3').drag('#1').then(() => {
+                get('h1').should(haveText('2-3'))
+            })
+        })
+    },
+)
+
 test('can use custom sortablejs configuration',
     [html`
         <div x-data>
@@ -175,3 +221,34 @@ test('works with Livewire morphing',
         })
     },
 )
+
+test('x-sort:item can be used as a filter',
+    [html`
+        <div x-data>
+            <ul x-sort>
+                <li x-sort:item id="1">foo</li>
+                <li id="2">bar</li>
+                <li x-sort:item id="3">baz</li>
+            </ul>
+        </div>
+    `],
+    ({ get }) => {
+        get('ul li').eq(0).should(haveText('foo'))
+        get('ul li').eq(1).should(haveText('bar'))
+        get('ul li').eq(2).should(haveText('baz'))
+
+        // Unfortunately, github actions doesn't like "async/await" here
+        // so we need to use .then() throughout this entire test...
+        get('#1').drag('#3').then(() => {
+            get('ul li').eq(0).should(haveText('bar'))
+            get('ul li').eq(1).should(haveText('baz'))
+            get('ul li').eq(2).should(haveText('foo'))
+
+            get('#2').drag('#1').then(() => {
+                get('ul li').eq(0).should(haveText('bar'))
+                get('ul li').eq(1).should(haveText('baz'))
+                get('ul li').eq(2).should(haveText('foo'))
+            })
+        })
+    },
+)