import { Controller } from '@hotwired/stimulus';
/*
* The docked "Chapter notes" panel on a storyline chapter page.
*
*
*
* Drag the handle to resize; click the header to collapse. Both the pixel
* height and the collapsed flag persist per browser (localStorage).
*/
const H_KEY = 'dmtools.chapterNotesHeight';
const C_KEY = 'dmtools.chapterNotesCollapsed';
const MIN_PX = 44;
const MIN_TOP_PX = 140;
export default class extends Controller {
static targets = ['dock', 'handle', 'toggle'];
connect() {
this.onMove = this.onMove.bind(this);
this.endResize = this.endResize.bind(this);
let stored = null;
try {
stored = localStorage.getItem(H_KEY);
} catch (e) {
/* private mode */
}
if (stored) {
this.element.style.setProperty('--notes-h', `${this.clamp(parseInt(stored, 10))}px`);
}
let collapsed = false;
try {
collapsed = localStorage.getItem(C_KEY) === '1';
} catch (e) {
/* ignore */
}
this.setCollapsed(collapsed);
}
disconnect() {
this.endResize();
}
toggle() {
this.setCollapsed(!this.dockTarget.classList.contains('is-collapsed'));
}
setCollapsed(on) {
this.dockTarget.classList.toggle('is-collapsed', on);
if (this.hasToggleTarget) {
this.toggleTarget.setAttribute('aria-expanded', on ? 'false' : 'true');
}
try {
localStorage.setItem(C_KEY, on ? '1' : '0');
} catch (e) {
/* ignore */
}
}
startResize(event) {
if (this.dockTarget.classList.contains('is-collapsed')) return;
event.preventDefault();
this.resizing = true;
this.handleTarget.setPointerCapture?.(event.pointerId);
document.body.style.userSelect = 'none';
document.body.style.cursor = 'row-resize';
window.addEventListener('pointermove', this.onMove);
window.addEventListener('pointerup', this.endResize);
window.addEventListener('pointercancel', this.endResize);
}
onMove(event) {
if (!this.resizing) return;
const rect = this.element.getBoundingClientRect();
const px = this.clamp(rect.bottom - event.clientY);
this.element.style.setProperty('--notes-h', `${px}px`);
}
endResize() {
window.removeEventListener('pointermove', this.onMove);
window.removeEventListener('pointerup', this.endResize);
window.removeEventListener('pointercancel', this.endResize);
if (!this.resizing) return;
this.resizing = false;
document.body.style.userSelect = '';
document.body.style.cursor = '';
const current = this.element.style.getPropertyValue('--notes-h');
if (current.endsWith('px')) {
try {
localStorage.setItem(H_KEY, parseInt(current, 10));
} catch (e) {
/* ignore */
}
}
}
clamp(px) {
if (!Number.isFinite(px)) return MIN_PX;
const max = Math.max(MIN_PX, this.element.getBoundingClientRect().height - MIN_TOP_PX);
return Math.min(Math.max(px, MIN_PX), max);
}
}