forked from fixin.me/fixin.me
Add stimulus-rails gem and wire up 7 controllers: - measurements_view_controller: view toggle (compact/wide) via localStorage - measurements_controller: grouped rows MutationObserver - charts_controller: Plotly chart rendering - form_controller: keyboard shortcuts (Escape/Enter) and submit validation - details_controller: quantity picker state, focusout close, MutationObserver - readout_unit_controller: default unit button enable/disable + PATCH submission - drag_controller: drag-and-drop for quantity reparenting and unit rebasing Remove all inline onclick/onkeydown/ondrag*/onsubmit handlers from templates. Remove all window.* global exports from application.js. Remove bare <script> block from measurements/_form.html.erb. Remove turbo:load listeners for behavior now in controller connect(). application.js now only contains: Turbo Stream custom action definitions and the showPage visibility listener. Document Stimulus conventions in CLAUDE.md. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
63 lines
1.8 KiB
JavaScript
63 lines
1.8 KiB
JavaScript
import { Controller } from "@hotwired/stimulus"
|
|
|
|
// Shared across all instances — drag spans multiple elements
|
|
let lastEnterTime
|
|
|
|
export default class extends Controller {
|
|
static values = {
|
|
dragPath: String,
|
|
dropId: String,
|
|
dropIdParam: String
|
|
}
|
|
|
|
start(event) {
|
|
lastEnterTime = event.timeStamp
|
|
this.element.closest("table").querySelectorAll("thead tr").forEach(tr => {
|
|
tr.toggleAttribute("hidden")
|
|
})
|
|
event.dataTransfer.setData("text/plain", this.dragPathValue)
|
|
const rect = this.element.getBoundingClientRect()
|
|
event.dataTransfer.setDragImage(this.element, event.x - rect.left, event.y - rect.top)
|
|
event.dataTransfer.dropEffect = "move"
|
|
}
|
|
|
|
end(event) {
|
|
this.leave(event)
|
|
this.element.closest("table").querySelectorAll("thead tr").forEach(tr => {
|
|
tr.toggleAttribute("hidden")
|
|
})
|
|
}
|
|
|
|
enter(event) {
|
|
this.leave(event)
|
|
lastEnterTime = event.timeStamp
|
|
document.getElementById(this.dropIdValue)?.classList.add("dropzone")
|
|
}
|
|
|
|
over(event) {
|
|
event.preventDefault()
|
|
}
|
|
|
|
leave(event) {
|
|
if (event.timeStamp <= lastEnterTime) return
|
|
this.element.closest("table").querySelectorAll(".dropzone").forEach(tr => {
|
|
tr.classList.remove("dropzone")
|
|
})
|
|
}
|
|
|
|
drop(event) {
|
|
event.preventDefault()
|
|
const id = this.dropIdValue.split("_").pop()
|
|
const form = document.createElement('form')
|
|
form.action = event.dataTransfer.getData("text/plain")
|
|
form.method = 'post'
|
|
form.dataset.turboStream = 'true'
|
|
const input = document.createElement('input')
|
|
input.type = 'hidden'; input.name = this.dropIdParamValue; input.value = id
|
|
form.appendChild(input)
|
|
form.addEventListener('turbo:submit-end', () => form.remove())
|
|
document.body.appendChild(form)
|
|
form.requestSubmit()
|
|
}
|
|
}
|