Skip to content

Commit 089dc2c

Browse files
horizontal map/panel width resize handle (koala73#2217) (koala73#2445)
* feat: map width resize and desktop bottom grid for issue koala73#2217 * fix(map-resize): fix listener leak, activate user-select guard, hide handle when map is off - destroy() now removes boundMapWidthResizeMoveHandler (mousemove) and boundMapWidthEndResizeHandler (mouseup + blur) from document/window, matching the cleanup contract of all other handlers in this class - Replace inline cursor style with body.map-width-resizing class toggle so the CSS user-select:none rule actually fires during drag (was dead code) - Add display:none for .map-width-resize-handle inside .map-hidden state for both @media (min-width:1600px) and .desktop-grid contexts --------- Co-authored-by: Elie Habib <elie.habib@gmail.com>
1 parent 34aa310 commit 089dc2c

3 files changed

Lines changed: 185 additions & 5 deletions

File tree

src/app/event-handlers.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ export class EventHandlerManager implements AppModule {
100100
private boundMapResizeMoveHandler: ((e: MouseEvent) => void) | null = null;
101101
private boundMapEndResizeHandler: (() => void) | null = null;
102102
private boundMapResizeVisChangeHandler: (() => void) | null = null;
103+
private boundMapWidthResizeMoveHandler: ((e: MouseEvent) => void) | null = null;
104+
private boundMapWidthEndResizeHandler: (() => void) | null = null;
103105
private boundMapFullscreenEscHandler: ((e: KeyboardEvent) => void) | null = null;
104106
private boundMobileMenuKeyHandler: ((e: KeyboardEvent) => void) | null = null;
105107
private boundPanelCloseHandler: ((e: Event) => void) | null = null;
@@ -269,6 +271,15 @@ export class EventHandlerManager implements AppModule {
269271
window.removeEventListener('blur', this.boundMapEndResizeHandler);
270272
this.boundMapEndResizeHandler = null;
271273
}
274+
if (this.boundMapWidthResizeMoveHandler) {
275+
document.removeEventListener('mousemove', this.boundMapWidthResizeMoveHandler);
276+
this.boundMapWidthResizeMoveHandler = null;
277+
}
278+
if (this.boundMapWidthEndResizeHandler) {
279+
document.removeEventListener('mouseup', this.boundMapWidthEndResizeHandler);
280+
window.removeEventListener('blur', this.boundMapWidthEndResizeHandler);
281+
this.boundMapWidthEndResizeHandler = null;
282+
}
272283
if (this.boundMapResizeVisChangeHandler) {
273284
document.removeEventListener('visibilitychange', this.boundMapResizeVisChangeHandler);
274285
this.boundMapResizeVisChangeHandler = null;
@@ -475,6 +486,7 @@ export class EventHandlerManager implements AppModule {
475486
window.addEventListener('resize', this.boundResizeHandler);
476487

477488
this.setupMapResize();
489+
this.setupMapWidthResize();
478490
this.setupMapPin();
479491

480492
this.boundVisibilityHandler = () => {
@@ -1379,6 +1391,55 @@ export class EventHandlerManager implements AppModule {
13791391
document.addEventListener('visibilitychange', this.boundMapResizeVisChangeHandler);
13801392
}
13811393

1394+
setupMapWidthResize(): void {
1395+
const mainContent = document.querySelector<HTMLElement>('.main-content');
1396+
const widthHandle = document.getElementById('mapWidthResizeHandle');
1397+
if (!mainContent || !widthHandle) return;
1398+
1399+
const saved = localStorage.getItem('map-col-width');
1400+
if (saved) mainContent.style.setProperty('--map-col-width', saved);
1401+
1402+
let isResizing = false;
1403+
let startX = 0;
1404+
let startTotalWidth = 0;
1405+
let startColPx = 0;
1406+
1407+
this.boundMapWidthEndResizeHandler = () => {
1408+
if (!isResizing) return;
1409+
isResizing = false;
1410+
this.ctx.map?.setIsResizing(false);
1411+
this.ctx.map?.resize();
1412+
document.body.classList.remove('map-width-resizing');
1413+
widthHandle.classList.remove('resizing');
1414+
const current = mainContent.style.getPropertyValue('--map-col-width');
1415+
if (current) localStorage.setItem('map-col-width', current);
1416+
};
1417+
1418+
widthHandle.addEventListener('mousedown', (e) => {
1419+
isResizing = true;
1420+
startX = e.clientX;
1421+
startTotalWidth = mainContent.offsetWidth;
1422+
const raw = mainContent.style.getPropertyValue('--map-col-width') || '60%';
1423+
startColPx = startTotalWidth * (parseFloat(raw) / 100);
1424+
this.ctx.map?.setIsResizing(true);
1425+
document.body.classList.add('map-width-resizing');
1426+
widthHandle.classList.add('resizing');
1427+
e.preventDefault();
1428+
});
1429+
1430+
this.boundMapWidthResizeMoveHandler = (e: MouseEvent) => {
1431+
if (!isResizing) return;
1432+
const delta = e.clientX - startX;
1433+
const newPct = Math.max(25, Math.min(75, ((startColPx + delta) / startTotalWidth) * 100));
1434+
mainContent.style.setProperty('--map-col-width', `${newPct.toFixed(1)}%`);
1435+
this.ctx.map?.resize();
1436+
};
1437+
1438+
document.addEventListener('mousemove', this.boundMapWidthResizeMoveHandler);
1439+
document.addEventListener('mouseup', this.boundMapWidthEndResizeHandler);
1440+
window.addEventListener('blur', this.boundMapWidthEndResizeHandler);
1441+
}
1442+
13821443
setupMapPin(): void {
13831444
const mapSection = document.getElementById('mapSection');
13841445
const pinBtn = document.getElementById('mapPinBtn');

src/app/panel-layout.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ export class PanelLayoutManager implements AppModule {
394394
</button>`
395395
).join('')}
396396
</div>
397-
<div class="main-content">
397+
<div class="main-content${this.ctx.isDesktopApp ? ' desktop-grid' : ''}">
398398
<div class="map-section" id="mapSection">
399399
<div class="panel-header">
400400
<div class="panel-header-left">
@@ -421,6 +421,7 @@ export class PanelLayoutManager implements AppModule {
421421
<div class="map-resize-handle" id="mapResizeHandle"></div>
422422
<div class="map-bottom-grid" id="mapBottomGrid"></div>
423423
</div>
424+
<div class="map-width-resize-handle" id="mapWidthResizeHandle"></div>
424425
<div class="panels-grid" id="panelsGrid"></div>
425426
<button class="search-mobile-fab" id="searchMobileFab" aria-label="Search">\u{1F50D}</button>
426427
</div>
@@ -1522,7 +1523,8 @@ export class PanelLayoutManager implements AppModule {
15221523
private getEffectiveUltraWide(): boolean {
15231524
const mapSection = document.getElementById('mapSection');
15241525
const mapEnabled = !mapSection?.classList.contains('hidden');
1525-
return window.innerWidth >= 1600 && mapEnabled;
1526+
const minWidth = this.ctx.isDesktopApp ? 900 : 1600;
1527+
return window.innerWidth >= minWidth && mapEnabled;
15261528
}
15271529

15281530
private insertByOrder(grid: HTMLElement, el: HTMLElement, key: string): void {

src/styles/main.css

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19794,15 +19794,58 @@ body.has-breaking-alert .panels-grid {
1979419794
}
1979519795

1979619796
/* ── Ultra-wide layout: map left, panels beside it ── */
19797+
.map-width-resize-handle {
19798+
display: none;
19799+
width: 6px;
19800+
cursor: ew-resize;
19801+
background: transparent;
19802+
align-self: stretch;
19803+
position: relative;
19804+
z-index: 10;
19805+
transition: background 0.15s ease;
19806+
flex-shrink: 0;
19807+
}
19808+
19809+
.map-width-resize-handle::after {
19810+
content: '';
19811+
position: absolute;
19812+
top: 50%;
19813+
left: 50%;
19814+
transform: translate(-50%, -50%);
19815+
width: 2px;
19816+
height: 40px;
19817+
background: var(--text-dim);
19818+
border-radius: 2px;
19819+
opacity: 0.4;
19820+
transition: opacity 0.15s;
19821+
}
19822+
19823+
.map-width-resize-handle:hover::after,
19824+
.map-width-resize-handle.resizing::after {
19825+
opacity: 1;
19826+
background: var(--accent);
19827+
}
19828+
19829+
body.map-width-resizing {
19830+
cursor: ew-resize;
19831+
user-select: none;
19832+
}
19833+
1979719834
@media (min-width: 1600px) {
1979819835
.main-content {
1979919836
display: grid;
19800-
grid-template-columns: 60% 1fr;
19837+
grid-template-columns: var(--map-col-width, 60%) 6px 1fr;
1980119838
grid-template-rows: 1fr auto;
19802-
gap: 4px;
19839+
gap: 4px 0;
1980319840
overflow: hidden;
1980419841
}
1980519842

19843+
.map-width-resize-handle {
19844+
display: flex;
19845+
grid-column: 2;
19846+
grid-row: 1;
19847+
}
19848+
1980619849
.main-content.map-hidden {
1980719850
grid-template-columns: 1fr;
1980819851
}
@@ -19811,6 +19854,10 @@ body.has-breaking-alert .panels-grid {
1981119854
grid-column: 1;
1981219855
}
1981319856

19857+
.main-content.map-hidden .map-width-resize-handle {
19858+
display: none;
19859+
}
19860+
1981419861
.map-section {
1981519862
grid-column: 1;
1981619863
grid-row: 1;
@@ -19864,7 +19911,7 @@ body.has-breaking-alert .panels-grid {
1986419911
}
1986519912

1986619913
.panels-grid {
19867-
grid-column: 2;
19914+
grid-column: 3;
1986819915
grid-row: 1;
1986919916
min-height: 0;
1987019917
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
@@ -19874,6 +19921,76 @@ body.has-breaking-alert .panels-grid {
1987419921
}
1987519922
}
1987619923

19924+
/* Desktop app grid layout (active at any width >= 900px via JS class) */
19925+
.main-content.desktop-grid {
19926+
display: grid;
19927+
grid-template-columns: var(--map-col-width, 60%) 6px 1fr;
19928+
grid-template-rows: 1fr auto;
19929+
gap: 4px 0;
19930+
overflow: hidden;
19931+
}
19932+
19933+
.main-content.desktop-grid .map-width-resize-handle {
19934+
display: flex;
19935+
grid-column: 2;
19936+
grid-row: 1;
19937+
}
19938+
19939+
.main-content.desktop-grid.map-hidden {
19940+
grid-template-columns: 1fr;
19941+
}
19942+
19943+
.main-content.desktop-grid.map-hidden .panels-grid {
19944+
grid-column: 1;
19945+
}
19946+
19947+
.main-content.desktop-grid.map-hidden .map-width-resize-handle {
19948+
display: none;
19949+
}
19950+
19951+
.main-content.desktop-grid .map-section {
19952+
grid-column: 1;
19953+
grid-row: 1;
19954+
height: 100% !important;
19955+
min-height: 0;
19956+
max-height: none;
19957+
overflow: hidden;
19958+
display: flex;
19959+
flex-direction: column;
19960+
border-bottom: none;
19961+
}
19962+
19963+
.main-content.desktop-grid .panels-grid {
19964+
grid-column: 3;
19965+
grid-row: 1;
19966+
min-height: 0;
19967+
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
19968+
grid-auto-rows: minmax(200px, 380px);
19969+
align-content: start;
19970+
overflow-y: auto;
19971+
}
19972+
19973+
.main-content.desktop-grid .map-resize-handle {
19974+
position: relative;
19975+
bottom: auto;
19976+
left: auto;
19977+
right: auto;
19978+
height: 12px;
19979+
background: var(--border-subtle);
19980+
margin: 0;
19981+
cursor: ns-resize;
19982+
display: flex;
19983+
}
19984+
19985+
.main-content.desktop-grid .map-resize-handle:hover {
19986+
background: var(--accent);
19987+
}
19988+
19989+
.main-content.desktop-grid .map-bottom-grid:empty {
19990+
border-top: none;
19991+
padding: 0;
19992+
}
19993+
1987719994
/* ==========================================================================
1987819995
Telegram Intel Panel
1987919996
========================================================================== */

0 commit comments

Comments
 (0)