Skip to content

Commit

Permalink
reorder-list | Add commit event that only emits when dragging ends, i…
Browse files Browse the repository at this point in the history
…n case emitting several consecutive change events is undesired
  • Loading branch information
Auroratide committed Jun 7, 2024
1 parent baa079a commit d192d83
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 11 deletions.
5 changes: 5 additions & 0 deletions .changeset/small-mangos-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@auroratide/reorder-list": minor
---

Add commit event that only emits when dragging is done
5 changes: 4 additions & 1 deletion components/reorder-list/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,9 @@ The `reorder-list` element dispatches the following events:
| Name | When Triggered |
| ------------- | ------------- |
| `change` | Whenever an item in the list is reordered |
| `commit` | At the end of an item being dragged |

The `change` event contains a reference to the item that was reordered, its previous position in the list, and its new position.
Both events contain a reference to the item that was reordered, its previous position in the list, and its new position.

```js
list.addEventListener('change', e => {
Expand All @@ -210,6 +211,8 @@ list.addEventListener('change', e => {
})
```

The difference between `change` and `commit` is the `change` event happens any time an element changes order, including in the middle of a drag. The `commit` event only triggers at the end of a drag, so the old index in that event's details will represent the position of that item prior to the drag being started.

## Accessibility

This custom element is build with accessibility in mind! It follows the WAI-ARIA guidelines for [rearrangable listboxes](https://www.w3.org/WAI/ARIA/apg/example-index/listbox/listbox-rearrangeable.html) (the `listbox` and `option` roles).
Expand Down
10 changes: 10 additions & 0 deletions components/reorder-list/src/events.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { ReorderItemElement } from "./reorder-item.js"

export const CHANGED = "change"
export const COMMIT = "commit"

export type ReorderListChangeEventDetail = {
item: ReorderItemElement,
Expand All @@ -16,3 +17,12 @@ export const changeEvent = (item: ReorderItemElement, oldIndex: number, newIndex
newIndex,
},
})

export const commitEvent = (item: ReorderItemElement, oldIndex: number, newIndex: number): CustomEvent<ReorderListChangeEventDetail> =>
new CustomEvent(COMMIT, {
detail: {
item,
oldIndex,
newIndex,
},
})
18 changes: 18 additions & 0 deletions components/reorder-list/src/reorder-item.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { commitEvent } from "./events.js"
import { ReorderListElement } from "./reorder-list.js"

export class ReorderItemElement extends HTMLElement {
Expand Down Expand Up @@ -69,6 +70,8 @@ export class ReorderItemElement extends HTMLElement {
e?.preventDefault()
this.dataset.dragging = ""

this.#startCommitTracking()

document.addEventListener("pointermove", this.#onDragMove)
document.addEventListener("pointerup", this.#onDragEnd)
document.addEventListener("pointercancel", this.#onDragEnd)
Expand Down Expand Up @@ -105,12 +108,27 @@ export class ReorderItemElement extends HTMLElement {
document.removeEventListener("pointerup", this.#onDragEnd)
document.removeEventListener("pointercancel", this.#onDragEnd)
document.removeEventListener("touchmove", this.#preventScroll)

this.#endCommitTracking()
}

#preventScroll = (e: TouchEvent) => {
e.preventDefault()
}

#originalPosition: number

#startCommitTracking = () => {
this.#originalPosition = this.list().items().indexOf(this)
}

#endCommitTracking = () => {
const list = this.list()
const newPosition = list.items().indexOf(this)
list.dispatchEvent(commitEvent(this, this.#originalPosition, newPosition))
this.#originalPosition = undefined
}

attributeChangedCallback() {
this.setAttribute("tabindex",
this.getAttribute("aria-selected") === "true" ? "0" : "-1",
Expand Down
54 changes: 44 additions & 10 deletions components/reorder-list/test/index.spec.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fixture, expect } from "@open-wc/testing"
import { CHANGED } from "../lib/events"
import { CHANGED, COMMIT } from "../lib/events"
import { ReorderItemElement } from "../lib"
import "../lib/define.js"

Expand Down Expand Up @@ -48,20 +48,22 @@ const press = (key, ...modifiers) => {

const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms))

const drag = async (item, destination) => {
const drag = async (item, destination, numberOfSlots = 1) => {
item.dispatchEvent(new PointerEvent("pointerdown", {
bubbles: true,
}))

await wait(1)

item.dispatchEvent(new PointerEvent("pointermove", {
clientX: destination.x,
clientY: destination.y,
bubbles: true,
}))
await wait(ReorderItemElement.START_DRAG_DELAY_MS + 10)

await wait(1)
for (let i = 0; i < numberOfSlots; ++i) {
item.dispatchEvent(new PointerEvent("pointermove", {
clientX: destination.x,
clientY: destination.y,
bubbles: true,
}))

await wait(1)
}

item.dispatchEvent(new PointerEvent("pointerup", {
bubbles: true,
Expand All @@ -79,6 +81,10 @@ const tap = async (element) => {
}

describe("reorder-list", () => {
beforeEach(() => {
ReorderItemElement.START_DRAG_DELAY_MS = 10
})

describe("aria-requirements", () => {
it("roles", async () => {
const container = await fixture(`
Expand Down Expand Up @@ -318,5 +324,33 @@ describe("reorder-list", () => {
expect(emitted.oldIndex).to.equal(2)
expect(emitted.newIndex).to.equal(1)
})

it("committing", async () => {
const container = await fixture(`
<reorder-list>
<reorder-item>Apple</reorder-item>
<reorder-item>Orange</reorder-item>
<reorder-item>Banana</reorder-item>
</reorder-list>
`)

let emitted = undefined
container.addEventListener(COMMIT, e => {
emitted = e.detail
})

const boundingBox = container.getBoundingClientRect()
const itemHeight = boundingBox.height / 3
const epsilon = 2
let items = container.querySelectorAll("reorder-item")

await drag(items[0], { y: boundingBox.top + itemHeight * 2 + epsilon }, 2)

const expectedTarget = container.items()[2]

expect(emitted.item).to.equal(expectedTarget)
expect(emitted.oldIndex).to.equal(0)
expect(emitted.newIndex).to.equal(2)
})
})
})

0 comments on commit d192d83

Please sign in to comment.