Skip to content

Commit

Permalink
Change to x-sort:item, add sorting class to body, and use `x-sort:g…
Browse files Browse the repository at this point in the history
…roup` (#4161)

* Add .sorting class to body while dragging

* wip

* fix tests
  • Loading branch information
calebporzio authored Apr 21, 2024
1 parent 816971b commit e46520b
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 65 deletions.
7 changes: 7 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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">
Expand Down
198 changes: 139 additions & 59 deletions packages/docs/src/en/plugins/sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,59 +52,59 @@ 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 -->

<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>
```
Expand All @@ -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>
Expand All @@ -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>
Expand All @@ -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 -->
Expand All @@ -217,18 +217,96 @@ 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 -->

<a name="sorting-class"></a>
## Sorting class on body

While an element is being dragged around, Alpine will automatically add a `.sorting` class to the `<body>` element of the page.

This is useful for styling any element on the page conditionally using only CSS.

For example you could have a warning that only displays while a user is sorting items:

```html
<div id="sort-warning">
Page functionality is limited while sorting
</div>
```

To show this only while sorting, you can use the `body.sorting` CSS selector:

```css
#sort-warning {
display: none;
}

body.sorting #sort-warning {
display: block;
}
```

<a name="css-hover-bug"></a>
## CSS hover bug

Currently, there is a [bug in Chrome and Safari](https://issues.chromium.org/issues/41129937) (not Firefox) that causes issues with hover styles.

Consider HTML like the following, where each item in the list is styled differently based on a hover state (here we're using Tailwind's `.hover` class to conditionally add a border):

```html
<div x-sort>
<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>
```

If you drag one of the elements in the list below you will see that the hover effect will be errantly applied to any element in the original element's place:

<!-- START_VERBATIM -->
<div x-data>
<ul x-sort class="flex flex-col items-start">
<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 -->

To fix this, you can leverage the `.sorting` class applied to the body while sorting to limit the hover effect to only be applied while `.sorting` does NOT exist on `body`.

Here is how you can do this directly inline using Tailwind arbitrary variants:

```html
<div x-sort>
<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>
```

Now you can see below that the hover effect is only applied to the dragging element and not the others in the list.

<!-- START_VERBATIM -->
<div x-data>
<ul x-sort class="flex flex-col items-start">
<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 -->
Expand All @@ -239,21 +317,23 @@ This makes it easy to add any custom styling you would like:
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)
Loading

0 comments on commit e46520b

Please sign in to comment.