Плашка масштаба графика — сворачиваемая с анимацией
По умолчанию плашка свёрнута (65×35) — белая плашка с фиолетовым пилюлем текущего масштаба («12М» и т.п.). При наведении разворачивается (145×80) в полный контрол (лейбл + слайдер + баббл) с плавной анимацией размеров (overflow:hidden + фикс. размеры) и кросс-фейдом содержимого. Сворачивается обратно через 1с после ухода курсора (JS-таймер в main.ts, класс .expanded; повторное наведение отменяет). В развёрнутом виде содержимое смещено на 4px вниз для вертикальной центровки. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
43bb72de61
commit
3939dbf8f5
@ -165,10 +165,13 @@
|
||||
<div id="chart-wrap">
|
||||
<div id="timeline"></div>
|
||||
<div class="field zoom-field" id="zoom-field">
|
||||
<label for="zoom-slider">Масштаб графика</label>
|
||||
<div class="zoom-row">
|
||||
<span id="zoom-label" class="zoom-bubble">12М</span>
|
||||
<input type="range" id="zoom-slider" min="0" max="4" step="1" value="1" />
|
||||
<span id="zoom-collapsed" class="zoom-collapsed">12М</span>
|
||||
<div class="zoom-full">
|
||||
<label for="zoom-slider">Масштаб графика</label>
|
||||
<div class="zoom-row">
|
||||
<span id="zoom-label" class="zoom-bubble">12М</span>
|
||||
<input type="range" id="zoom-slider" min="0" max="4" step="1" value="1" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="col-resizer" title="Потяните, чтобы изменить ширину колонки"></div>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "mapdash-frontend",
|
||||
"private": true,
|
||||
"version": "0.2.16",
|
||||
"version": "0.2.17",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@ -35,6 +35,8 @@ const el = {
|
||||
showAllSurfaces: document.getElementById('show-all-surfaces') as HTMLInputElement,
|
||||
zoomSlider: document.getElementById('zoom-slider') as HTMLInputElement,
|
||||
zoomLabel: document.getElementById('zoom-label') as HTMLElement,
|
||||
zoomCollapsed: document.getElementById('zoom-collapsed') as HTMLElement,
|
||||
zoomField: document.getElementById('zoom-field') as HTMLElement,
|
||||
chartWrap: document.getElementById('chart-wrap') as HTMLElement,
|
||||
colResizer: document.getElementById('col-resizer') as HTMLElement,
|
||||
decorBtn: document.getElementById('decor-btn') as HTMLButtonElement,
|
||||
@ -460,11 +462,26 @@ function updateZoomUi(): void {
|
||||
el.zoomSlider.addEventListener('input', () => {
|
||||
const step = zoomSteps[parseInt(el.zoomSlider.value, 10)]!;
|
||||
el.zoomLabel.textContent = step.label;
|
||||
el.zoomCollapsed.textContent = step.label;
|
||||
view.setScale(step.days);
|
||||
updateZoomUi();
|
||||
});
|
||||
window.addEventListener('resize', updateZoomUi);
|
||||
|
||||
// The scale plate is collapsed to a small value pill by default; expand on hover
|
||||
// and collapse again 1s after the pointer leaves (so it doesn't snap shut while
|
||||
// you're reaching back toward it).
|
||||
let zoomCollapseTimer = 0;
|
||||
el.zoomField.addEventListener('mouseenter', () => {
|
||||
window.clearTimeout(zoomCollapseTimer);
|
||||
el.zoomField.classList.add('expanded');
|
||||
updateZoomUi(); // realign the bubble now that the slider has a real width
|
||||
});
|
||||
el.zoomField.addEventListener('mouseleave', () => {
|
||||
window.clearTimeout(zoomCollapseTimer);
|
||||
zoomCollapseTimer = window.setTimeout(() => el.zoomField.classList.remove('expanded'), 1000);
|
||||
});
|
||||
|
||||
// Clicking a surface in the timeline's left column flies the map to it: switch to
|
||||
// the combined "Карта и график" view if only the timeline is open, then focus the
|
||||
// surface's cluster. Works for anonymous and manager users alike.
|
||||
@ -494,6 +511,7 @@ async function init(): Promise<void> {
|
||||
const defaultZoomIdx = 1; // 12М
|
||||
el.zoomSlider.value = String(defaultZoomIdx);
|
||||
el.zoomLabel.textContent = zoomSteps[defaultZoomIdx]!.label;
|
||||
el.zoomCollapsed.textContent = zoomSteps[defaultZoomIdx]!.label;
|
||||
view.setScale(zoomSteps[defaultZoomIdx]!.days);
|
||||
updateZoomUi();
|
||||
// Apply the persisted view mode (inits the map if it starts visible).
|
||||
|
||||
@ -262,16 +262,36 @@ input::placeholder { color: var(--text-muted); }
|
||||
.checkbox-field input[type=checkbox]:checked::after { left: 16px; }
|
||||
.checkbox-field input[type=checkbox]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
|
||||
|
||||
.zoom-field { gap: 4px; }
|
||||
.zoom-field label { white-space: nowrap; }
|
||||
.zoom-field .zoom-row { position: relative; width: 120px; padding-bottom: 24px; display: block; }
|
||||
/* The scale control floats over the top-right of the timeline, just below the
|
||||
time axis (JS sets `top` = axis bottom + 5px; this is only a fallback). */
|
||||
/* Scale control floats over the top-right of the timeline (JS sets `top` = axis
|
||||
bottom + 5px). Collapsed by default: a small plate showing only the value pill.
|
||||
JS toggles `.expanded` (on hover; collapse is delayed ~1s after mouseleave).
|
||||
Fixed sizes + overflow:hidden let width/height animate; the two faces cross-fade. */
|
||||
#chart-wrap #zoom-field {
|
||||
position: absolute; top: 52px; right: 16px; z-index: 20;
|
||||
background: rgba(255, 255, 255, .92); border-radius: 8px;
|
||||
padding: 3px 12px 4px; box-shadow: var(--shadow-md);
|
||||
width: 65px; height: 35px; overflow: hidden;
|
||||
background: rgba(255, 255, 255, .95); border-radius: 8px; box-shadow: var(--shadow-md);
|
||||
transition: width .24s var(--ease-3), height .24s var(--ease-3);
|
||||
}
|
||||
#chart-wrap #zoom-field.expanded { width: 145px; height: 80px; }
|
||||
/* Collapsed face: the accent value pill, centred in the small plate. */
|
||||
.zoom-collapsed {
|
||||
position: absolute; inset: 0; margin: auto; width: fit-content; height: fit-content;
|
||||
display: inline-flex; align-items: center; justify-content: center;
|
||||
background: var(--accent); color: #fff; font-size: 11px; font-weight: 600;
|
||||
padding: 3px 9px; border-radius: 6px; white-space: nowrap; cursor: pointer;
|
||||
font-variant-numeric: tabular-nums;
|
||||
opacity: 1; transition: opacity .14s ease .06s;
|
||||
}
|
||||
#chart-wrap #zoom-field.expanded .zoom-collapsed { opacity: 0; transition-delay: 0s; pointer-events: none; }
|
||||
/* Expanded face: label + slider + bubble. */
|
||||
.zoom-full {
|
||||
position: absolute; top: 4px; left: 0; padding: 3px 12px 4px;
|
||||
display: flex; flex-direction: column; gap: 4px;
|
||||
opacity: 0; pointer-events: none; transition: opacity .14s ease;
|
||||
}
|
||||
#chart-wrap #zoom-field.expanded .zoom-full { opacity: 1; pointer-events: auto; transition-delay: .08s; }
|
||||
.zoom-field input[type=range] {
|
||||
-webkit-appearance: none; appearance: none; width: 100%; height: 6px; margin: 0;
|
||||
border-radius: 4px; cursor: pointer; outline: none;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user