PageSourceSearch

https://www.georgetowncoc.com/assets/controllers/ranking_input_controller-8da25811.js

js georgetowncoc.com collected 2026-09-25 04:05:44 UTC 6,168 bytes, 182 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3export default class extends Controller {
4  static targets = ["fallback", "enhanced", "list", "item", "answer", "position", "handle", "picker", "instructions", "blank", "skip", "skippedMessage", "status"]
5  static values = { skipped: Boolean }
6
7  connect() {
8    this.fallbackTarget.hidden = true
9    this.fallbackTarget.querySelectorAll("select").forEach((select) => { select.disabled = true })
10    this.enhancedTarget.hidden = false
11    this.closePicker()
12    this.update()
13  }
14
15  disconnect() {
16    this.cancelPointerDrag()
17    this.closePicker()
18  }
19
20  reorderKey(event) {
21    if (this.previewDisabled || !["ArrowUp", "ArrowDown", "Home", "End"].includes(event.key)) return
22    event.preventDefault()
23    this.closePicker()
24    const item = this.itemFor(event.currentTarget)
25    const index = this.itemTargets.indexOf(item)
26    const position = { ArrowUp: index - 1, ArrowDown: index + 1, Home: 0, End: this.itemTargets.length - 1 }[event.key]
27    this.moveTo(item, position)
28  }
29
30  togglePicker(event) {
31    if (this.previewDisabled || (event.detail > 0 && event.timeStamp - this.lastDragTime < 300)) return
32    const item = this.itemFor(event.currentTarget)
33    const picker = item.querySelector("[data-ranking-input-target='picker']")
34    const wasOpen = !picker.hidden
35    this.closePicker()
36    if (wasOpen) return
37
38    picker.hidden = false
39    event.currentTarget.setAttribute("aria-expanded", "true")
40    const select = picker.querySelector("select")
41    select.value = this.itemTargets.indexOf(item)
42    select.focus()
43  }
44
45  choosePosition(event) {
46    if (this.previewDisabled) return
47    const item = this.itemFor(event.currentTarget)
48    this.closePicker()
49    this.moveTo(item, Number(event.currentTarget.value))
50  }
51
52  closePicker() {
53    this.pickerTargets.forEach((picker) => { picker.hidden = true })
54    this.handleTargets.forEach((handle) => { handle.setAttribute("aria-expanded", "false") })
55  }
56
57  closeOutside(event) {
58    const openPicker = this.pickerTargets.find((picker) => !picker.hidden)
59    if (openPicker && !this.itemFor(openPicker).contains(event.target)) this.closePicker()
60  }
61
62  focusOut(event) {
63    const openPicker = this.pickerTargets.find((picker) => !picker.hidden)
64    if (openPicker && !this.itemFor(openPicker).contains(event.relatedTarget)) this.closePicker()
65  }
66
67  escape(event) {
68    const openPicker = this.pickerTargets.find((picker) => !picker.hidden)
69    if (!openPicker && !this.pointerDrag) return
70    event.preventDefault()
71    this.cancelPointerDrag()
72    this.closePicker()
73    if (openPicker) this.focusHandle(this.itemFor(openPicker))
74  }
75
76  startPointerDrag(event) {
77    if (this.previewDisabled || event.button !== 0 || event.isPrimary === false) return
78    this.cancelPointerDrag()
79    this.pointerDrag = {
80      pointerId: event.pointerId,
81      item: this.itemFor(event.currentTarget),
82      originalOrder: this.itemTargets,
83      startY: event.clientY,
84      moved: false
85    }
86  }
87
88  dragPointer(event) {
89    const drag = this.pointerDrag
90    if (!drag || event.pointerId !== drag.pointerId) return
91    if (!drag.moved && Math.abs(event.clientY - drag.startY) < 5) return
92    event.preventDefault()
93    if (!drag.moved) {
94      drag.moved = true
95      this.closePicker()
96      this.element.setPointerCapture(event.pointerId)
97      drag.item.classList.add("opacity-50")
98    }
99
100    const others = this.itemTargets.filter((item) => item !== drag.item)
101    const next = others.find((item) => {
102      const rect = item.getBoundingClientRect()
103      return event.clientY < rect.top + rect.height / 2
104    })
105    if (next) next.before(drag.item)
106    else this.listTarget.append(drag.item)
107    this.update()
108  }
109
110  endPointerDrag(event) {
111    if (!this.pointerDrag || event.pointerId !== this.pointerDrag.pointerId) return
112    const { item, moved } = this.pointerDrag
113    this.finishPointerDrag()
114    if (moved) {
115      this.lastDragTime = event.timeStamp
116      this.moved(item)
117    }
118  }
119
120  cancelPointerDrag() {
121    if (!this.pointerDrag) return
122    this.pointerDrag.originalOrder.forEach((item) => this.listTarget.append(item))
123    this.finishPointerDrag()
124    this.update()
125  }
126
127  finishPointerDrag() {
128    const { pointerId, item } = this.pointerDrag
129    this.pointerDrag = null
130    if (this.element.hasPointerCapture(pointerId)) this.element.releasePointerCapture(pointerId)
131    item.classList.remove("opacity-50")
132  }
133
134  moveTo(item, position) {
135    const items = this.itemTargets
136    const destination = items[Math.max(0, Math.min(position, items.length - 1))]
137    if (items.indexOf(destination) < items.indexOf(item)) destination.before(item)
138    else if (destination !== item) destination.after(item)
139    this.moved(item)
140  }
141
142  moved(item) {
143    this.update()
144    const position = this.itemTargets.indexOf(item) + 1
145    this.statusTarget.textContent = `${item.dataset.label} is in position ${position} of ${this.itemTargets.length}.`
146    this.focusHandle(item)
147  }
148
149  toggleSkipped() {
150    if (this.previewDisabled) return
151    this.closePicker()
152    this.skippedValue = !this.skippedValue
153    this.update()
154    this.statusTarget.textContent = this.skippedValue ? "Question skipped." : "The displayed order will be submitted."
155  }
156
157  update() {
158    this.answerTargets.forEach((input) => { input.disabled = this.skippedValue || this.previewDisabled })
159    this.listTarget.hidden = this.skippedValue
160    this.instructionsTarget.hidden = this.skippedValue
161    this.itemTargets.forEach((item, index) => {
162      item.querySelector("[data-ranking-input-target='position']").textContent = index + 1
163    })
164    if (this.hasSkipTarget) {
165      this.blankTarget.disabled = !this.skippedValue || this.previewDisabled
166      this.skipTarget.textContent = this.skippedValue ? "Add ranking" : "Skip this question"
167      this.skippedMessageTarget.hidden = !this.skippedValue
168    }
169  }
170
171  itemFor(element) {
172    return element.closest("[data-ranking-input-target='item']")
173  }
174
175  focusHandle(item) {
176    item.querySelector("[data-ranking-input-target='handle']").focus()
177  }
178
179  get previewDisabled() {
180    return !!this.element.closest("fieldset[disabled]")
181  }
182}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.