Give the dashboard its own height handle, separate from the map/table split
Some checks are pending
build-gate / build (push) Waiting to run
Some checks are pending
build-gate / build (push) Waiting to run
Dragging the map/table divider in page-scroll mode was also resizing the whole element, because --rd-map-h alone answered both "how is the map/table space split" and "how tall is the element" -- Driver: "you improperly bound that control to the separator line between the map and the table... that's not the request I was making to resize the height of the page." --rd-shell-h is now the independent total-height budget; the new bottom handle (.rd-rz-shell) is the only thing that writes it. The existing map/table divider still drives --rd-map-h, but only as the map's share of that fixed budget, so it redistributes between map and table without changing the sum. Table/facets/rail max-height all re-derive from --rd-shell-h so the sidebars keep spanning exactly map+table at every drag. Also closes a 28-30px sidebar gap the re-derivation exposed: the table budget only ever subtracted the header bar's height, never the summary footer's, so facets/rail undershot the table's real bottom edge.
This commit is contained in:
parent
5d87bd9db1
commit
eaa2c35286
2 changed files with 176 additions and 32 deletions
|
|
@ -166,7 +166,7 @@ const { shell, isNarrow, slimRail, slimFacet, mobileView, SEG, tableCollapsed, f
|
|||
// TopBar's "Align Dashboard" button via bus.emit('resetLayout')) snaps back to the exact defaults.
|
||||
// Defaults MUST match the CSS spine below (facet 248, rail 312, table 34%) so an untouched board is
|
||||
// pixel-identical and the responsive slim-rail classes keep working until the user drags.
|
||||
const { startFacetResize, startRailResize, startTableResize, onResizeKeydown, facetW, railW, tableH, mapH } = useDashboardLayout({
|
||||
const { startFacetResize, startRailResize, startTableResize, startShellResize, onResizeKeydown, facetW, railW, tableH, mapH, shellH } = useDashboardLayout({
|
||||
shellRef: shell,
|
||||
bus,
|
||||
isNarrow,
|
||||
|
|
@ -180,7 +180,11 @@ const { startFacetResize, startRailResize, startTableResize, onResizeKeydown, fa
|
|||
// mapH mirrors the page-scroll CSS fallback 40svh (~360px at a 900px viewport — see
|
||||
// BUG-DASHBOARD-BAND-GROWS-ENORMOUS: 58svh left the default band taller than one viewport;
|
||||
// 40svh is the value that makes chrome(137px) + map(X) + table(X+32px bar) sum to <=900px).
|
||||
defaults: { facetW: 296, railW: 264, tableH: '34%', mapH: 360 },
|
||||
// shellH default 720 = the map+table combined budget that mapH:360 used to imply on its own
|
||||
// (360 map + 360 table-with-bar) — see useDashboardLayout.js's DEFAULTS comment for the exact
|
||||
// accounting. Must mirror --rd-shell-h's CSS fallback below for the same reset()-vs-fresh-load
|
||||
// reason as the other three.
|
||||
defaults: { facetW: 296, railW: 264, tableH: '34%', mapH: 360, shellH: 720 },
|
||||
})
|
||||
|
||||
// KPI strip — generic formatting per kind (mirrors the original inline strip).
|
||||
|
|
@ -323,6 +327,18 @@ useShadowStyles(`
|
|||
.rd-rz-table{ grid-area:map; align-self:end; justify-self:stretch; height:8px; margin-bottom:-4px; cursor:row-resize; }
|
||||
.rd-rz-facet::after, .rd-rz-rail::after{ top:50%; left:50%; transform:translate(-50%,-50%); width:3px; height:34px; }
|
||||
.rd-rz-table::after{ top:50%; left:50%; transform:translate(-50%,-50%); width:34px; height:3px; }
|
||||
/* ── SHELL HANDLE (page-scroll mode only) — the boundary between the WHOLE research-dashboard
|
||||
element and whatever a host stacks below it (BUG-SHELL-HANDLE-BOUND-TO-WRONG-DIVIDER: this is
|
||||
the control the Driver actually asked for — "resize the height of the page" — which a previous
|
||||
pass mistakenly bound to .rd-rz-table above instead). Positioned OUTSIDE the grid (the shell is
|
||||
already position:relative) rather than given a grid-area, because it must span all THREE
|
||||
columns (facets + map/table + rail) regardless of their widths, and no single grid-area does
|
||||
that without also claiming cells .rd-rz-table already owns. Pinned to the shell's own bottom
|
||||
edge — not the table cell's — so it tracks whichever row happens to be last. Grid-mode has no
|
||||
concept of "the element's own height" (the shell IS the viewport there), so this handle only
|
||||
ever renders in .rd-page-scroll — see the template's v-show. */
|
||||
.rd-rz-shell{ position:absolute; left:0; right:0; bottom:0; height:10px; margin-bottom:-5px; cursor:row-resize; }
|
||||
.rd-rz-shell::after{ top:50%; left:50%; transform:translate(-50%,-50%); width:48px; height:3px; }
|
||||
.rd-rz:hover::after{ background:var(--rd-accent,#e67e22); }
|
||||
.rd-rz:active{ background:color-mix(in srgb, var(--rd-accent,#e67e22) 18%, transparent); }
|
||||
.rd-rz:active::after{ background:var(--rd-accent,#e67e22); }
|
||||
|
|
@ -357,7 +373,18 @@ useShadowStyles(`
|
|||
is the ORIGINAL defect the 88svh pin was introduced to avoid; giving
|
||||
the map an explicit height keeps it while freeing the table.
|
||||
The table's own overflow is released to visible in this mode, which is the actual fix: with
|
||||
no internal scrollport under the cursor there is nothing to capture the wheel. */
|
||||
no internal scrollport under the cursor there is nothing to capture the wheel.
|
||||
|
||||
★ --rd-shell-h vs --rd-map-h (BUG-SHELL-HANDLE-BOUND-TO-WRONG-DIVIDER). Two INDEPENDENT
|
||||
variables, answering two different questions, where there used to be only one:
|
||||
--rd-shell-h = the ELEMENT's own total height (map + table combined) — the NEW bottom
|
||||
handle (.rd-rz-shell) is the only thing that writes this.
|
||||
--rd-map-h = the map's SHARE of that total — the existing map/table divider
|
||||
(.rd-rz-table) writes this, redistributing within --rd-shell-h without
|
||||
changing it (useDashboardLayout.js clamps mapH so it can never exceed the
|
||||
shell budget). Every derived measurement below (table budget, facets/rail
|
||||
span) now roots in --rd-shell-h so the total stays the single source of
|
||||
truth and the two handles cannot fight over the same pixels. */
|
||||
.rd-shell.rd-page-scroll{ height:auto; --table-h:auto; }
|
||||
.rd-shell.rd-page-scroll > [data-region="map"]{ height:var(--rd-map-h, 40svh); overflow:hidden; }
|
||||
.rd-shell.rd-page-scroll > [data-region="table"]{ overflow:visible; }
|
||||
|
|
@ -394,9 +421,27 @@ useShadowStyles(`
|
|||
taller than where the gesture happens). 40svh (~360px at 900px) brings chrome + map(360) +
|
||||
table(392) to ~889px, clearing all three clauses with an 11px margin. The resize handle
|
||||
(useDashboardLayout's mapH drag) still lets a large-monitor user drag it back up afterward —
|
||||
this only changes what a FIRST LOAD shows. */
|
||||
this only changes what a FIRST LOAD shows.
|
||||
★ RE-ROOTED IN --rd-shell-h (BUG-SHELL-HANDLE-BOUND-TO-WRONG-DIVIDER). The table's budget used
|
||||
to be map-h minus the bar, which made the map/table divider ALSO the element's total-height
|
||||
control (growing the map grew the total, since nothing capped it). Now the table's budget is
|
||||
the SHELL's total minus whatever the map is currently taking minus the bar — so dragging the
|
||||
map/table divider (which changes --rd-map-h, never --rd-shell-h) redistributes between the two
|
||||
rows while their SUM stays exactly --rd-shell-h. Fallback 80svh mirrors useDashboardLayout.js's
|
||||
shellH default (720px ≈ 2×40svh at 900px) — the map+table combined budget --rd-map-h alone used
|
||||
to imply, so first paint is unchanged.
|
||||
★ ALSO SUBTRACTS --rd-table-foot-h NOW — MEASURED, a real gap this exposed. The table cell has
|
||||
THREE stacked children (.rd-table-bar, .rd-grid, .rd-foot — the "Σ" summary row,
|
||||
DashboardDataTable.vue's height:28px), but the old formula only ever budgeted for the bar. That
|
||||
was invisible before this card: with nothing else depending on map-h + table-cell summing to an
|
||||
exact number, the extra 28px just meant the table cell was quietly 28px taller than the map row
|
||||
beside it. It became visible the moment facets/rail needed to span EXACTLY map+table (below) —
|
||||
without this term they undershot by the footer's height, leaving them 28-30px short of the
|
||||
table's real bottom edge, i.e. the sidebar grey-gap bug this card is fixing. --rd-table-foot-h
|
||||
is a var (not a literal) for the same reason --rd-table-bar-h is: DashboardDataTable can correct
|
||||
it if the footer's height ever changes, without this rule going stale. */
|
||||
.rd-shell.rd-page-scroll .rd-grid{
|
||||
max-height:var(--rd-table-max-h, calc(var(--rd-map-h, 40svh) - var(--rd-table-bar-h, 32px))); }
|
||||
max-height:var(--rd-table-max-h, calc(var(--rd-shell-h, 80svh) - var(--rd-map-h, 40svh) - var(--rd-table-bar-h, 32px) - var(--rd-table-foot-h, 28px))); }
|
||||
/* ── FACETS/RAIL COLUMNS BOUNDED IN PAGE-SCROLL MODE (BUG-DASHBOARD-BAND-GROWS-ENORMOUS) ──────
|
||||
THE ROOT CAUSE, one level up from the table rule above. .rd-facets/.rd-rail are authored
|
||||
height:100%, which is correct in the FIXED-VIEWPORT grid (rows sum to a real, bounded
|
||||
|
|
@ -449,10 +494,16 @@ useShadowStyles(`
|
|||
already owns) rather than map alone — that is what actually matches "span the map AND the
|
||||
table". --rd-table-max-h already carries the table's real budget (line 399); reusing it here
|
||||
keeps the two in lockstep by construction instead of a second hardcoded number that could drift.
|
||||
+1px absorbs the 1px grid gap between the two rows this element spans. */
|
||||
+1px absorbs the 1px grid gap between the two rows this element spans.
|
||||
★ RE-ROOTED IN --rd-shell-h, SAME CARD AS THE TABLE RULE ABOVE. map-h + table-cell(bar +
|
||||
grid-max-h) now sums to EXACTLY --rd-shell-h by construction (table-max-h is defined as
|
||||
shell-h - map-h - bar-h, so bar-h + table-max-h = shell-h - map-h, and adding map-h back
|
||||
cancels it) — so the two-row span the sidebars must cover is just the shell's own total, not a
|
||||
re-derived sum of its two parts. This also means the sidebars automatically track the NEW
|
||||
shell handle (.rd-rz-shell) the same tick it moves, with no separate wiring. */
|
||||
.rd-shell.rd-page-scroll > [data-region="facets"],
|
||||
.rd-shell.rd-page-scroll > [data-region="rail"]{ min-height:0; overflow:hidden;
|
||||
max-height:calc(var(--rd-map-h, 40svh) + var(--rd-table-max-h, calc(var(--rd-map-h, 40svh) - var(--rd-table-bar-h, 32px))) + 1px); }
|
||||
max-height:calc(var(--rd-shell-h, 80svh) + 1px); }
|
||||
|
||||
.rd-shell.rd-narrow{ grid-template-columns:1fr; grid-template-rows:52px auto var(--rd-stats-h,30px) minmax(0,1fr) 46px;
|
||||
grid-template-areas:"topbar" "filters" "stats" "body" "seg"; }
|
||||
|
|
@ -543,16 +594,32 @@ useShadowStyles(`
|
|||
title="Drag or use arrow keys to resize the charts rail" aria-label="Resize charts rail"
|
||||
:aria-valuenow="railW" aria-valuemin="200" aria-valuemax="520"
|
||||
@pointerdown="startRailResize" @keydown="onResizeKeydown('rail', $event)"></div>
|
||||
<!-- Title/label flip in page-scroll mode: this handle drives --rd-map-h there (the dashboard
|
||||
band height, vs. the grid below it), not --table-h (force-set to `auto` and no longer
|
||||
drives any layout) — see the PAGE-SCROLL AXIS SWITCH note in useDashboardLayout.js. -->
|
||||
<!-- ★ FIXED (BUG-SHELL-HANDLE-BOUND-TO-WRONG-DIVIDER): this handle is the MAP/TABLE SPLIT
|
||||
ONLY, in both modes — it must never change the element's overall height, which is now the
|
||||
NEW .rd-rz-shell handle's job below. In page-scroll mode it still drives --rd-map-h (the
|
||||
map's SHARE of --rd-shell-h, not the total — see the CSS note above .rd-shell.rd-page-scroll),
|
||||
because --table-h is force-set to `auto` and no longer drives any layout — see the
|
||||
PAGE-SCROLL AXIS SWITCH note in useDashboardLayout.js. -->
|
||||
<div v-show="!isNarrow && !tableCollapsed" class="rd-rz rd-rz-table" data-resize="table"
|
||||
role="separator" aria-orientation="horizontal" tabindex="0"
|
||||
:title="pageScroll ? 'Drag or use arrow keys to resize the dashboard height' : 'Drag or use arrow keys to resize the table'"
|
||||
:aria-label="pageScroll ? 'Resize dashboard height' : 'Resize table height'"
|
||||
:title="pageScroll ? 'Drag or use arrow keys to resize the map/table split' : 'Drag or use arrow keys to resize the table'"
|
||||
:aria-label="pageScroll ? 'Resize map/table split' : 'Resize table height'"
|
||||
:aria-valuenow="pageScroll ? mapH : (parseFloat(tableH) || 34)"
|
||||
:aria-valuemin="pageScroll ? 220 : 12" :aria-valuemax="pageScroll ? 1400 : 70"
|
||||
@pointerdown="startTableResize" @keydown="onResizeKeydown('table', $event)"></div>
|
||||
<!-- ★ THE NEW HANDLE (BUG-SHELL-HANDLE-BOUND-TO-WRONG-DIVIDER) — a bar at the SHELL's own
|
||||
bottom edge, spanning all three columns, on the boundary between the whole
|
||||
research-dashboard element and whatever a host stacks below it (e.g. the people-groups
|
||||
grid in dashboard-plus-grid.vue). This is what actually answers "resize the height of the
|
||||
page": it is the ONLY handle that writes --rd-shell-h. Page-scroll only — grid-mode has no
|
||||
concept of the element's own height (the shell IS the viewport there, sized by its host
|
||||
band), so there is nothing for this handle to do outside page-scroll. -->
|
||||
<div v-show="!isNarrow && pageScroll" class="rd-rz rd-rz-shell" data-resize="shell"
|
||||
role="separator" aria-orientation="horizontal" tabindex="0"
|
||||
title="Drag or use arrow keys to resize the dashboard's height"
|
||||
aria-label="Resize dashboard height"
|
||||
:aria-valuenow="shellH" aria-valuemin="400" aria-valuemax="2600"
|
||||
@pointerdown="startShellResize" @keydown="onResizeKeydown('shell', $event)"></div>
|
||||
<div class="rd-seg" data-segmented @click="setView">
|
||||
<button v-for="s in SEG" :key="s.k" :class="{ on: mobileView === s.k }" data-view-btn :data-view="s.k">{{ s.l }}</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,25 @@
|
|||
* properties, PERSISTS the chosen layout to localStorage on every drag-end, RESTORES it on init,
|
||||
* and offers reset() to clear the key and snap back to the exact defaults.
|
||||
*
|
||||
* SHELL vs. TABLE axis (page-scroll mode only — BUG-SHELL-HANDLE-BOUND-TO-WRONG-DIVIDER):
|
||||
* Two DIFFERENT questions look similar and must stay answered by two DIFFERENT handles.
|
||||
* "table" axis = the MAP/TABLE SPLIT. In page-scroll mode it redistributes height BETWEEN
|
||||
* the map and the table; the sum of the two (the element's total height)
|
||||
* does NOT change. This is the EXISTING map/table divider
|
||||
* (.rd-rz-table in research-dashboard.vue), unchanged in grid-mode.
|
||||
* "shell" axis = the ELEMENT'S OWN HEIGHT (page-scroll mode only) — a bar at the shell's
|
||||
* BOTTOM edge, the boundary between the whole research-dashboard element and
|
||||
* whatever a host stacks below it (e.g. the people-groups grid in
|
||||
* dashboard-plus-grid.vue). Dragging it changes --rd-shell-h, the total map+
|
||||
* table budget; the map/table SPLIT within that budget is untouched.
|
||||
* --rd-shell-h is the new independent variable; --rd-map-h is now the map's share of it, and
|
||||
* the table's share is derived as shellH - mapH (see the CSS in research-dashboard.vue). Before
|
||||
* this, --rd-map-h alone was BOTH "the map/table split" AND "the element's total height" — one
|
||||
* knob answering two questions, which is why dragging the map/table divider used to resize the
|
||||
* whole element as a side effect. Driver: "you improperly bound that control to the separator
|
||||
* line between the map and the table... that's not the request I was making to resize the
|
||||
* height of the page."
|
||||
*
|
||||
* COMPOSITION with the collapse-table fix (do NOT regress):
|
||||
* The shell's DEFAULT rows are `52px auto 42px 1fr var(--table-h,34%)` while the collapsed rule
|
||||
* `.rd-shell.rd-table-collapsed{ grid-template-rows:52px auto 42px 1fr 0 }` comes LATER in source
|
||||
|
|
@ -39,17 +58,18 @@
|
|||
* @param {import('vue').Ref<boolean>|boolean=} options.pageScroll when true, the table axis drives
|
||||
* --rd-map-h (px) instead of --table-h — see the PAGE-SCROLL AXIS SWITCH note below.
|
||||
* @param {string=} options.storageKey localStorage key (default 'rd-dashboard-layout')
|
||||
* @param {{facetW?:number, railW?:number, tableH?:string, mapH?:number}=} options.defaults default track sizes
|
||||
* @param {{facetW?:[number,number], railW?:[number,number], tableH?:[number,number], mapH?:[number,number]}=} options.clamps
|
||||
* min/max — facetW/railW/mapH in px, tableH in percent-of-shell-height
|
||||
* @param {{facetW?:number, railW?:number, tableH?:string, mapH?:number, shellH?:number}=} options.defaults default track sizes
|
||||
* @param {{facetW?:[number,number], railW?:[number,number], tableH?:[number,number], mapH?:[number,number], shellH?:[number,number]}=} options.clamps
|
||||
* min/max — facetW/railW/mapH/shellH in px, tableH in percent-of-shell-height
|
||||
*
|
||||
* @returns {{
|
||||
* facetW: import('vue').Ref<number>, railW: import('vue').Ref<number>, tableH: import('vue').Ref<string>,
|
||||
* mapH: import('vue').Ref<number>,
|
||||
* mapH: import('vue').Ref<number>, shellH: import('vue').Ref<number>,
|
||||
* startFacetResize:(e:PointerEvent)=>void, startRailResize:(e:PointerEvent)=>void,
|
||||
* startTableResize:(e:PointerEvent)=>void, reset:()=>void, applyVars:()=>void, customized:import('vue').Ref<boolean>,
|
||||
* stepResize:(axis:'facet'|'rail'|'table', dir:-1|1, toExtreme?:boolean)=>void,
|
||||
* onResizeKeydown:(axis:'facet'|'rail'|'table', e:KeyboardEvent)=>void
|
||||
* startTableResize:(e:PointerEvent)=>void, startShellResize:(e:PointerEvent)=>void,
|
||||
* reset:()=>void, applyVars:()=>void, customized:import('vue').Ref<boolean>,
|
||||
* stepResize:(axis:'facet'|'rail'|'table'|'shell', dir:-1|1, toExtreme?:boolean)=>void,
|
||||
* onResizeKeydown:(axis:'facet'|'rail'|'table'|'shell', e:KeyboardEvent)=>void
|
||||
* }}
|
||||
*/
|
||||
import { ref, unref, onMounted, onBeforeUnmount } from 'vue'
|
||||
|
|
@ -62,8 +82,18 @@ import { ref, unref, onMounted, onBeforeUnmount } from 'vue'
|
|||
// ★ 58svh (522px) was the ORIGINAL default and left the page-scroll band taller than one desktop
|
||||
// viewport (BUG-DASHBOARD-BAND-GROWS-ENORMOUS: chrome 137px + map 522px + table 552px = 1211px
|
||||
// against 900px). 40svh is the value that makes chrome + map(X) + table(X + bar-height) fit <=900px.
|
||||
const DEFAULTS = { facetW: 296, railW: 264, tableH: '34%', mapH: 360 }
|
||||
const CLAMPS = { facetW: [160, 420], railW: [200, 520], tableH: [12, 70], mapH: [220, 1400] }
|
||||
// shellH default (720) is the SUM the old single-knob mapH(360) used to imply — map 360 + table
|
||||
// budget 360 (328 grid + 32 bar) — so first paint is pixel-identical to before this axis split;
|
||||
// only DRAGGING behaves differently now (see the SHELL vs. TABLE axis note above).
|
||||
const DEFAULTS = { facetW: 296, railW: 264, tableH: '34%', mapH: 360, shellH: 720 }
|
||||
const CLAMPS = { facetW: [160, 420], railW: [200, 520], tableH: [12, 70], mapH: [220, 1400], shellH: [400, 2600] }
|
||||
|
||||
// Mirrors the CSS fallback --rd-table-bar-h (research-dashboard.vue's .rd-table-bar budget calc) —
|
||||
// the table's header bar (count + Export CSV) sits ABOVE its scrollport and is not itself
|
||||
// resizable, so the map/table split must always leave it at least this much room, or the divider
|
||||
// could drag the table's usable grid to zero while its outer cell still looks non-empty.
|
||||
const TABLE_BAR_H = 32
|
||||
const TABLE_MIN_H = 120 // grid's own floor below the bar — a table smaller than this is unusable
|
||||
|
||||
const clamp = (v, [lo, hi]) => Math.min(hi, Math.max(lo, v))
|
||||
const pctNum = (v, fallback) => {
|
||||
|
|
@ -87,10 +117,14 @@ export function useDashboardLayout(options = {}) {
|
|||
const railW = ref(defaults.railW) // px
|
||||
const tableH = ref(defaults.tableH) // string, e.g. '34%'
|
||||
const mapH = ref(defaults.mapH) // px — only meaningful/applied in page-scroll mode
|
||||
const shellH = ref(defaults.shellH) // px — page-scroll ELEMENT total height (map+table budget)
|
||||
const customized = ref(false) // has the user (or a restored layout) diverged from CSS defaults?
|
||||
const mapHCustomized = ref(false) // page-scroll's own inline var is written independently of
|
||||
// customized (below), since an untouched grid-mode drag
|
||||
// must never suddenly pin --rd-map-h and vice versa.
|
||||
const shellHCustomized = ref(false) // same independence, one level up: the shell handle is a
|
||||
// separate drag from the map/table split and must not fire
|
||||
// just because the OTHER page-scroll var was touched.
|
||||
|
||||
const narrow = () => !!unref(isNarrow)
|
||||
const inPageScroll = () => !!unref(pageScroll)
|
||||
|
|
@ -111,6 +145,9 @@ export function useDashboardLayout(options = {}) {
|
|||
if (mapHCustomized.value) {
|
||||
el.style.setProperty('--rd-map-h', mapH.value + 'px')
|
||||
}
|
||||
if (shellHCustomized.value) {
|
||||
el.style.setProperty('--rd-shell-h', shellH.value + 'px')
|
||||
}
|
||||
}
|
||||
function clearVars() {
|
||||
const el = shellRef && shellRef.value
|
||||
|
|
@ -119,13 +156,14 @@ export function useDashboardLayout(options = {}) {
|
|||
el.style.removeProperty('--rail-w')
|
||||
el.style.removeProperty('--table-h')
|
||||
el.style.removeProperty('--rd-map-h')
|
||||
el.style.removeProperty('--rd-shell-h')
|
||||
}
|
||||
|
||||
// ── persistence ──
|
||||
function persist() {
|
||||
try {
|
||||
localStorage.setItem(storageKey, JSON.stringify({
|
||||
facetW: facetW.value, railW: railW.value, tableH: tableH.value, mapH: mapH.value,
|
||||
facetW: facetW.value, railW: railW.value, tableH: tableH.value, mapH: mapH.value, shellH: shellH.value,
|
||||
}))
|
||||
} catch (_) { /* storage blocked / SSR — layout still works this session */ }
|
||||
}
|
||||
|
|
@ -138,6 +176,9 @@ export function useDashboardLayout(options = {}) {
|
|||
if (Number.isFinite(s.railW)) railW.value = clamp(s.railW, clamps.railW)
|
||||
if (s.tableH != null) tableH.value = clamp(pctNum(s.tableH, pctNum(defaults.tableH, 34)), clamps.tableH) + '%'
|
||||
customized.value = true
|
||||
// shellH restores BEFORE mapH so the mapH clamp below (which bounds map to <= shellH, see
|
||||
// startResize's isTablePageScroll branch) has the right ceiling on first paint too.
|
||||
if (Number.isFinite(s.shellH)) { shellH.value = clamp(s.shellH, clamps.shellH); shellHCustomized.value = true }
|
||||
if (Number.isFinite(s.mapH)) { mapH.value = clamp(s.mapH, clamps.mapH); mapHCustomized.value = true }
|
||||
return true
|
||||
} catch (_) { return false }
|
||||
|
|
@ -150,8 +191,10 @@ export function useDashboardLayout(options = {}) {
|
|||
railW.value = defaults.railW
|
||||
tableH.value = defaults.tableH
|
||||
mapH.value = defaults.mapH
|
||||
shellH.value = defaults.shellH
|
||||
customized.value = false
|
||||
mapHCustomized.value = false
|
||||
shellHCustomized.value = false
|
||||
clearVars()
|
||||
}
|
||||
|
||||
|
|
@ -201,13 +244,23 @@ export function useDashboardLayout(options = {}) {
|
|||
// frame. The fix is to drive --rd-map-h directly, from the MAP ELEMENT's own top edge
|
||||
// (not the unbounded shell rect), so growing the map is a plain function of where the
|
||||
// pointer is relative to where the map starts.
|
||||
//
|
||||
// ★ THE map/table SPLIT vs. THE shell's OWN HEIGHT (BUG-SHELL-HANDLE-BOUND-TO-WRONG-DIVIDER)
|
||||
// — this handle must move height BETWEEN map and table, never grow the total. mapH is now
|
||||
// clamped to leave the table at least TABLE_BAR_H + TABLE_MIN_H of the FIXED shellH budget,
|
||||
// so dragging this divider can only redistribute the two, exactly like resizing an internal
|
||||
// pane split rather than the window itself.
|
||||
const isTablePageScroll = axis === 'table' && inPageScroll()
|
||||
const isShellResize = axis === 'shell' && inPageScroll()
|
||||
let startMapTop = 0
|
||||
let mapHCeiling = clamps.mapH[1]
|
||||
if (isTablePageScroll) {
|
||||
const mapEl = el.querySelector('[data-region="map"]')
|
||||
const mapRect = mapEl ? mapEl.getBoundingClientRect() : null
|
||||
startMapTop = mapRect ? mapRect.top : (e.clientY - mapH.value)
|
||||
mapHCeiling = Math.max(clamps.mapH[0], shellH.value - (TABLE_BAR_H + TABLE_MIN_H))
|
||||
}
|
||||
const startShellTop = isShellResize ? rect.top : 0
|
||||
try { handleEl && handleEl.setPointerCapture && handleEl.setPointerCapture(e.pointerId) } catch (_) {}
|
||||
|
||||
// ── A11Y / PHOTOSENSITIVITY (BUG-RESIZE-STROBE): mark the drag as LIVE ──
|
||||
|
|
@ -244,13 +297,27 @@ export function useDashboardLayout(options = {}) {
|
|||
// distance from the map's (fixed) top to the cursor — a direct 1:1 follow, exactly like
|
||||
// dragging the corner of a window. Re-derived fresh every frame from startMapTop (not
|
||||
// summed from a delta), so it can never drift from the cursor across a long drag.
|
||||
mapH.value = Math.round(clamp(ev.clientY - startMapTop, clamps.mapH))
|
||||
// Clamped to mapHCeiling (not the raw clamps.mapH max) — the ceiling reserves the table's
|
||||
// bar + a usable minimum out of the FIXED shellH budget, so this drag only ever moves
|
||||
// height between map and table and can never grow the element's total height. That is
|
||||
// the shell axis's job (see isShellResize below), not this one's.
|
||||
mapH.value = Math.round(clamp(ev.clientY - startMapTop, [clamps.mapH[0], mapHCeiling]))
|
||||
mapHCustomized.value = true
|
||||
} else if (axis === 'table') {
|
||||
// map/table divider → dragging up grows the table (height measured from shell bottom)
|
||||
const pct = ((rect.bottom - ev.clientY) / rect.height) * 100
|
||||
tableH.value = Math.round(clamp(pct, clamps.tableH)) + '%'
|
||||
customized.value = true
|
||||
} else if (isShellResize) {
|
||||
// The NEW shell handle — spans the whole element's bottom edge (research-dashboard.vue's
|
||||
// .rd-rz-shell), the boundary with whatever a host stacks below (e.g. the people-groups
|
||||
// grid in dashboard-plus-grid.vue). Same fixed-top / 1:1-follow pattern as the map handle
|
||||
// above, rooted at the SHELL's own top instead of the map's: the pointer's Y position IS
|
||||
// the shell's new bottom edge. This is the ONLY axis that changes --rd-shell-h, i.e. the
|
||||
// only handle that changes the element's total height — the map/table divider above must
|
||||
// never do this (see its own comment).
|
||||
shellH.value = Math.round(clamp(ev.clientY - startShellTop, clamps.shellH))
|
||||
shellHCustomized.value = true
|
||||
}
|
||||
applyVars()
|
||||
}
|
||||
|
|
@ -277,13 +344,15 @@ export function useDashboardLayout(options = {}) {
|
|||
const startFacetResize = (e) => startResize('facet', e)
|
||||
const startRailResize = (e) => startResize('rail', e)
|
||||
const startTableResize = (e) => startResize('table', e)
|
||||
const startShellResize = (e) => startResize('shell', e)
|
||||
|
||||
// ── keyboard step-resize (a11y: role="separator" needs an operable equivalent to the drag) ──
|
||||
// Reuses the SAME refs/clamps/persist/applyVars as the pointer path — no parallel math. One
|
||||
// discrete step per keypress (no rAF coalescing needed; a keydown can't fire faster than a frame).
|
||||
// `table` axis mirrors startResize's own page-scroll branch: in page-scroll mode it steps mapH
|
||||
// (the dashboard band height), otherwise it steps the grid-mode tableH percentage.
|
||||
const STEP = { facet: 16, rail: 16, table: 24, mapH: 24 } // px per arrow-key press (tableH in %)
|
||||
// (the map/table split within the FIXED shellH budget), otherwise it steps the grid-mode tableH
|
||||
// percentage. `shell` steps shellH directly — the only axis that changes the element's total height.
|
||||
const STEP = { facet: 16, rail: 16, table: 24, mapH: 24, shellH: 24 } // px per arrow-key press (tableH in %)
|
||||
// `toExtreme` bypasses the per-step delta entirely and clamps straight to lo/hi — Home/End jump,
|
||||
// not a long walk of individual steps (which would also mean 200 localStorage writes per keypress).
|
||||
function stepResize(axis, dir, toExtreme) {
|
||||
|
|
@ -299,25 +368,33 @@ export function useDashboardLayout(options = {}) {
|
|||
: Math.round(clamp(railW.value - dir * STEP.rail, clamps.railW))
|
||||
customized.value = true
|
||||
} else if (axis === 'table' && inPageScroll()) {
|
||||
mapH.value = toExtreme ? (dir < 0 ? clamps.mapH[0] : clamps.mapH[1])
|
||||
: Math.round(clamp(mapH.value + dir * STEP.mapH, clamps.mapH))
|
||||
// Same ceiling as the pointer path (startResize's mapHCeiling): reserve the table's bar +
|
||||
// usable minimum out of the CURRENT shellH so a keyboard drag can redistribute but never
|
||||
// grow the total either.
|
||||
const ceiling = Math.max(clamps.mapH[0], shellH.value - (TABLE_BAR_H + TABLE_MIN_H))
|
||||
mapH.value = toExtreme ? (dir < 0 ? clamps.mapH[0] : ceiling)
|
||||
: Math.round(clamp(mapH.value + dir * STEP.mapH, [clamps.mapH[0], ceiling]))
|
||||
mapHCustomized.value = true
|
||||
} else if (axis === 'table') {
|
||||
const pct = toExtreme ? (dir < 0 ? clamps.tableH[0] : clamps.tableH[1])
|
||||
: clamp(pctNum(tableH.value, pctNum(defaults.tableH, 34)) - dir * STEP.table / 4, clamps.tableH)
|
||||
tableH.value = Math.round(pct) + '%'
|
||||
customized.value = true
|
||||
} else if (axis === 'shell' && inPageScroll()) {
|
||||
shellH.value = toExtreme ? (dir < 0 ? clamps.shellH[0] : clamps.shellH[1])
|
||||
: Math.round(clamp(shellH.value + dir * STEP.shellH, clamps.shellH))
|
||||
shellHCustomized.value = true
|
||||
}
|
||||
applyVars()
|
||||
persist()
|
||||
}
|
||||
// ArrowUp/Left = grow toward the start edge, ArrowDown/Right = grow toward the end edge, matching
|
||||
// each handle's drag SENSE (facet/rail are col-resize, table/map is row-resize). Home/End jump to
|
||||
// the axis's clamp extremes in one write (not a walk of individual steps).
|
||||
// each handle's drag SENSE (facet/rail are col-resize, table/map/shell are row-resize). Home/End
|
||||
// jump to the axis's clamp extremes in one write (not a walk of individual steps).
|
||||
function onResizeKeydown(axis, e) {
|
||||
const K = e.key
|
||||
let dir = 0
|
||||
if (axis === 'table') { if (K === 'ArrowUp') dir = -1; else if (K === 'ArrowDown') dir = 1 }
|
||||
if (axis === 'table' || axis === 'shell') { if (K === 'ArrowUp') dir = -1; else if (K === 'ArrowDown') dir = 1 }
|
||||
else { if (K === 'ArrowLeft') dir = -1; else if (K === 'ArrowRight') dir = 1 }
|
||||
if (dir) { e.preventDefault(); stepResize(axis, dir); return }
|
||||
if (K === 'Home' || K === 'End') {
|
||||
|
|
@ -340,8 +417,8 @@ export function useDashboardLayout(options = {}) {
|
|||
})
|
||||
|
||||
return {
|
||||
facetW, railW, tableH, mapH, customized, resizing,
|
||||
startFacetResize, startRailResize, startTableResize,
|
||||
facetW, railW, tableH, mapH, shellH, customized, resizing,
|
||||
startFacetResize, startRailResize, startTableResize, startShellResize,
|
||||
startResize, reset, applyVars, stepResize, onResizeKeydown,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue