Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change to x-sort:item, add sorting class to body, and use x-sort:group #4161

Merged
merged 3 commits into from
Apr 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading