diff --git a/modules/rcal/components/folk-calendar-view.ts b/modules/rcal/components/folk-calendar-view.ts index 272c1b7..f611891 100644 --- a/modules/rcal/components/folk-calendar-view.ts +++ b/modules/rcal/components/folk-calendar-view.ts @@ -137,6 +137,12 @@ class FolkCalendarView extends HTMLElement { private lunarOverlayExpanded = false; private _wheelTimer: ReturnType | null = null; + // Map resize state + private mapPanelWidth = 400; + private _isResizing = false; + private _resizeStartX = 0; + private _resizeStartWidth = 0; + // Transition state private _pendingTransition: 'nav-left' | 'nav-right' | 'zoom-in' | 'zoom-out' | null = null; private _ghostHtml: string | null = null; @@ -196,6 +202,11 @@ class FolkCalendarView extends HTMLElement { this.boundKeyHandler = null; } if (this._wheelTimer) { clearTimeout(this._wheelTimer); this._wheelTimer = null; } + if (this._isResizing) { + this._isResizing = false; + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + } this._pointerCache = []; this._panStartX = this._panStartY = this._initialPinchDist = null; this._gestureMode = 'none'; @@ -861,16 +872,17 @@ class FolkCalendarView extends HTMLElement { ${this.renderSources()} -
+
${this.renderZoomController()}
+ +
${this.renderCalendarContent()}
- ${isDocked ? `
${this.renderZoomController()}
` : ""} + ${isDocked ? `
` : ""} ${this.renderMapPanel()}
- ${!isDocked ? this.renderZoomController() : ""} ${this.renderLunarOverlay()}
@@ -1883,6 +1895,62 @@ class FolkCalendarView extends HTMLElement { $("map-fab")?.addEventListener("click", () => { this.mapPanelState = "docked"; this.render(); }); $("map-minimize")?.addEventListener("click", () => { this.mapPanelState = "minimized"; this.render(); }); + // Resize handle drag + const resizeHandle = $("resize-handle"); + const mainLayout = $("main-layout"); + if (resizeHandle && mainLayout) { + const startResize = (startX: number) => { + this._isResizing = true; + this._resizeStartX = startX; + this._resizeStartWidth = this.mapPanelWidth; + resizeHandle.classList.add("active"); + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + }; + const doResize = (clientX: number) => { + if (!this._isResizing) return; + const delta = this._resizeStartX - clientX; // drag left = bigger map + const newWidth = Math.max(200, Math.min(800, this._resizeStartWidth + delta)); + this.mapPanelWidth = newWidth; + (mainLayout as HTMLElement).style.gridTemplateColumns = `1fr 6px ${newWidth}px`; + }; + const endResize = () => { + if (!this._isResizing) return; + this._isResizing = false; + resizeHandle.classList.remove("active"); + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + if (this.leafletMap) this.leafletMap.invalidateSize(); + }; + + resizeHandle.addEventListener("mousedown", (e: Event) => { + (e as MouseEvent).preventDefault(); + startResize((e as MouseEvent).clientX); + const onMouseMove = (em: MouseEvent) => doResize(em.clientX); + const onMouseUp = () => { + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + endResize(); + }; + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + }); + + resizeHandle.addEventListener("touchstart", (e: Event) => { + const te = e as TouchEvent; + te.preventDefault(); + startResize(te.touches[0].clientX); + const onTouchMove = (em: Event) => doResize((em as TouchEvent).touches[0].clientX); + const onTouchEnd = () => { + document.removeEventListener("touchmove", onTouchMove); + document.removeEventListener("touchend", onTouchEnd); + endResize(); + }; + document.addEventListener("touchmove", onTouchMove); + document.addEventListener("touchend", onTouchEnd); + }, { passive: false }); + } + // Zoom spectrum slider — click on track or tick labels const zoomTrack = $("zoom-track"); if (zoomTrack) { @@ -2452,17 +2520,23 @@ class FolkCalendarView extends HTMLElement { .src-badge:hover { filter: brightness(1.2); } .src-badge.filtered { opacity: 0.3; text-decoration: line-through; } + /* ── Zoom Bar Top ── */ + .zoom-bar--top { margin-bottom: 10px; padding: 8px 12px; background: var(--rs-bg-surface); border: 1px solid var(--rs-border-subtle); border-radius: 8px; } + .zoom-bar--top .zoom-bar__track { min-width: 200px; } + .zoom-bar--top .zoom-bar__row { gap: 12px; } + .zoom-bar--top .zoom-bar__tick-label { font-size: 10px; } + .zoom-bar--top .zoom-bar__label-end { font-size: 11px; } + /* ── Main Layout ── */ .main-layout { position: relative; min-height: 400px; } - .main-layout--docked { display: grid; grid-template-columns: 1fr auto 400px; gap: 0; min-height: 500px; } + .main-layout--docked { display: grid; grid-template-columns: 1fr 6px 400px; gap: 0; min-height: 500px; } .calendar-pane { overflow: auto; min-width: 0; touch-action: pan-y; user-select: none; } - .zoom-bar--middle { display: flex; flex-direction: column; justify-content: center; padding: 8px 2px; border-left: 1px solid var(--rs-border-subtle); border-right: 1px solid var(--rs-border-subtle); } - .zoom-bar--middle .zoom-bar { padding: 0; } - .zoom-bar--middle .zoom-bar__label-end { display: none; } - .zoom-bar--middle .zoom-bar__track { min-width: 100px; } - .zoom-bar--middle .zoom-bar__tick-label { font-size: 7px; } - .zoom-bar--middle .coupling-btn { padding: 2px 6px; font-size: 10px; } - .zoom-bar--middle .variant-indicator { display: none; } + + /* ── Resize Handle ── */ + .resize-handle { width: 6px; cursor: col-resize; background: transparent; position: relative; z-index: 10; transition: background 0.15s; } + .resize-handle:hover, .resize-handle.active { background: var(--rs-primary-hover); opacity: 0.5; } + .resize-handle::before { content: ""; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 2px; height: 32px; border-radius: 1px; background: var(--rs-border-strong); opacity: 0.4; transition: opacity 0.15s; } + .resize-handle:hover::before, .resize-handle.active::before { opacity: 0.8; } /* ── Map Panel ── */ .map-panel { background: var(--rs-bg-surface-sunken); border: 1px solid var(--rs-border-strong); border-radius: 12px; overflow: hidden; display: flex; flex-direction: column; } @@ -2753,19 +2827,20 @@ class FolkCalendarView extends HTMLElement { /* ── Tablet/Adaptive ── */ @media (max-width: 1024px) { - .main-layout--docked { grid-template-columns: 1fr auto 320px; } + .main-layout--docked { grid-template-columns: 1fr 6px 320px; } } @media (max-width: 900px) and (min-width: 769px) { - .main-layout--docked { grid-template-columns: 1fr; } - .zoom-bar--middle { display: none; } + .main-layout--docked { grid-template-columns: 1fr !important; } + .resize-handle { display: none; } .ev-label { display: none; } .day { min-height: 64px; } + .zoom-bar--top .zoom-bar__track { min-width: 140px; } } /* ── Mobile ── */ @media (max-width: 768px) { :host { padding: 0.25rem; } - .main-layout--docked { grid-template-columns: 1fr; } - .zoom-bar--middle { display: none; } + .main-layout--docked { grid-template-columns: 1fr !important; } + .resize-handle { display: none; } .year-grid { grid-template-columns: repeat(3, 1fr); } .season-grid { grid-template-columns: 1fr; } .day { min-height: 52px; padding: 4px; } @@ -2780,6 +2855,8 @@ class FolkCalendarView extends HTMLElement { .wd { font-size: 10px; padding: 3px; } .season-cities, .week-event-meta, .tl-event-desc, .yv-country { display: none; } .week-view { font-size: 10px; } + .zoom-bar--top { padding: 6px 8px; margin-bottom: 6px; } + .zoom-bar--top .zoom-bar__track { min-width: 120px; } .zoom-bar__track { min-width: 120px; } .zoom-bar__label-end { font-size: 9px; } .zoom-bar__tick-label { font-size: 8px; }