diff --git a/app/assets/stylesheets/bubbles.css b/app/assets/stylesheets/bubbles.css
index 9b7f88d5e..b4b114e1f 100644
--- a/app/assets/stylesheets/bubbles.css
+++ b/app/assets/stylesheets/bubbles.css
@@ -397,13 +397,29 @@
--divider-color: var(--color-subtle-dark);
font-size: 1rem;
+ cursor: grab;
+ visibility: hidden;
+}
+
+.bubbles-list__divider--positioned {
+ visibility: visible;
+}
+
+.divider-drag-image {
+ background-color: var(--color-link);
+ border-radius: 1rem;
+ color: var(--color-ink-reversed);
+ inset: auto 100% 100% auto;
+ line-height: 2rem;
+ padding: 0 1rem;
+ position: fixed;
+ text-wrap: nowrap;
}
.btn--reversed.bubbles-list__handle {
--btn-background: var(--divider-color);
font-size: 0.9rem;
- cursor: grab;
}
.bubbles-list__count {
diff --git a/app/javascript/controllers/divider_controller.js b/app/javascript/controllers/divider_controller.js
new file mode 100644
index 000000000..2840a6b72
--- /dev/null
+++ b/app/javascript/controllers/divider_controller.js
@@ -0,0 +1,72 @@
+import { Controller } from "@hotwired/stimulus"
+import { get, patch } from "@rails/request.js"
+
+const MOVE_ITEM_DATA_TYPE = "x-fizzy/move"
+const DIVIDER_ITEM_NODE_NAME = "LI"
+
+export default class extends Controller {
+ static targets = [ "divider", "dragImage", "count" ]
+ static classes = [ "positioned" ]
+ static values = { startCount: Number, maxCount: Number }
+
+ connect() {
+ this.install()
+ }
+
+ install() {
+ this.#positionDividerElement(this.startCountValue)
+ }
+
+ configureDrag(event) {
+ if (event.target == this.dividerTarget) {
+ event.dataTransfer.dropEffect = "move"
+ event.dataTransfer.setData(MOVE_ITEM_DATA_TYPE, event.target)
+ event.dataTransfer.setDragImage(this.dragImageTarget, 0, 0)
+ }
+ }
+
+ moveDivider(event) {
+ if (event.target.nodeName != DIVIDER_ITEM_NODE_NAME) return
+
+ const targetIndex = this.#items.indexOf(event.target)
+
+ if (targetIndex > this.maxCountValue) return
+
+ if (this.#dividerIndex < targetIndex) {
+ event.target.after(this.dividerTarget)
+ } else {
+ event.target.before(this.dividerTarget)
+ }
+
+ this.countTarget.textContent = targetIndex
+ }
+
+ persist() {
+ // TODO
+ }
+
+ acceptDrop(event) {
+ const isDroppable = event.dataTransfer.types.includes(MOVE_ITEM_DATA_TYPE)
+ if (isDroppable) event.preventDefault()
+ }
+
+ #positionDividerElement(index) {
+ if (index == 0) {
+ this.#items[0].before(this.dividerTarget)
+ } else if (index <= this.#items.length - 1) {
+ this.#items[index - 1].after(this.dividerTarget)
+ } else {
+ this.#items[this.#items.length - 1].after(this.dividerTarget)
+ }
+
+ this.dividerTarget.classList.add(this.positionedClass)
+ }
+
+ get #items() {
+ return Array.from(this.element.children)
+ }
+
+ get #dividerIndex() {
+ return this.#items.indexOf(this.dividerTarget)
+ }
+}
diff --git a/app/javascript/controllers/query_merger_controller.js b/app/javascript/controllers/query_merger_controller.js
deleted file mode 100644
index 3681578e3..000000000
--- a/app/javascript/controllers/query_merger_controller.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Controller } from "@hotwired/stimulus"
-
-// FIXME: Can we do this without a controller? https://github.com/basecamp/fizzy/pull/130#discussion_r1833094616
-export default class extends Controller {
- merge({ params: { key, value } }) {
- const url = new URL(window.location.href)
- url.searchParams.set(key, value)
- Turbo.visit(url)
- }
-}
diff --git a/app/views/bubbles/index.html.erb b/app/views/bubbles/index.html.erb
index 814530d10..4308ddf2c 100644
--- a/app/views/bubbles/index.html.erb
+++ b/app/views/bubbles/index.html.erb
@@ -34,20 +34,14 @@
-
-
- Top 10
-
-
-
-
<%= render partial: "bubbles/list/bubble", collection: @bubbles, cached: true %>
+ <%= render "bubbles/list/divider", filter: @filter %>