meow
This commit is contained in:
+17
-4
@@ -3934,6 +3934,8 @@ function BracketViewport({ children }) {
|
|||||||
start.moved = true
|
start.moved = true
|
||||||
bracketPan.dragged = true
|
bracketPan.dragged = true
|
||||||
el.classList.add('is-grabbing')
|
el.classList.add('is-grabbing')
|
||||||
|
// Drop any text selection the initial press may have started.
|
||||||
|
window.getSelection()?.removeAllRanges()
|
||||||
}
|
}
|
||||||
if (start.moved) {
|
if (start.moved) {
|
||||||
el.scrollLeft = start.sl - dx
|
el.scrollLeft = start.sl - dx
|
||||||
@@ -3979,6 +3981,7 @@ function TournamentBracketSide({ side, navigate, highlight, onHover }) {
|
|||||||
// `tops` places each match; once placed we read back the DOM to draw connectors.
|
// `tops` places each match; once placed we read back the DOM to draw connectors.
|
||||||
const [layout, setLayout] = useState({ tops: new Map(), height: 0 })
|
const [layout, setLayout] = useState({ tops: new Map(), height: 0 })
|
||||||
const [connectors, setConnectors] = useState({ width: 0, height: 0, lines: [], byes: [] })
|
const [connectors, setConnectors] = useState({ width: 0, height: 0, lines: [], byes: [] })
|
||||||
|
const [collapsed, setCollapsed] = useState(false)
|
||||||
|
|
||||||
// Pass 1: measure card heights, then position every match centred on its feeders.
|
// Pass 1: measure card heights, then position every match centred on its feeders.
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@@ -3994,7 +3997,7 @@ function TournamentBracketSide({ side, navigate, highlight, onHover }) {
|
|||||||
const observer = new ResizeObserver(relayout)
|
const observer = new ResizeObserver(relayout)
|
||||||
observer.observe(grid)
|
observer.observe(grid)
|
||||||
return () => observer.disconnect()
|
return () => observer.disconnect()
|
||||||
}, [side])
|
}, [side, collapsed])
|
||||||
|
|
||||||
// Pass 2: with matches positioned, read real edges and draw elbow connectors.
|
// Pass 2: with matches positioned, read real edges and draw elbow connectors.
|
||||||
// Each connector carries the teams on either end so a hovered team's whole run
|
// Each connector carries the teams on either end so a hovered team's whole run
|
||||||
@@ -4040,13 +4043,22 @@ function TournamentBracketSide({ side, navigate, highlight, onHover }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setConnectors({ width: grid.scrollWidth, height: grid.scrollHeight, lines, byes })
|
setConnectors({ width: grid.scrollWidth, height: grid.scrollHeight, lines, byes })
|
||||||
}, [side, layout])
|
}, [side, layout, collapsed])
|
||||||
|
|
||||||
const active = Boolean(highlight)
|
const active = Boolean(highlight)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="bracket-side">
|
||||||
<h3 className="mb-3 text-sm font-semibold uppercase tracking-wide text-fury-violet">{side.label}</h3>
|
<button
|
||||||
|
aria-expanded={!collapsed}
|
||||||
|
className="mb-3 flex items-center gap-2 text-sm font-semibold uppercase tracking-wide text-fury-violet transition hover:text-text"
|
||||||
|
onClick={() => setCollapsed((value) => !value)}
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true" className={`bracket-caret${collapsed ? ' is-collapsed' : ''}`}>▾</span>
|
||||||
|
{side.label}
|
||||||
|
</button>
|
||||||
|
{collapsed ? null : (
|
||||||
<BracketViewport>
|
<BracketViewport>
|
||||||
<div className={`tournament-bracket-grid${active ? ' has-trace' : ''}`} ref={gridRef}>
|
<div className={`tournament-bracket-grid${active ? ' has-trace' : ''}`} ref={gridRef}>
|
||||||
<svg
|
<svg
|
||||||
@@ -4101,6 +4113,7 @@ function TournamentBracketSide({ side, navigate, highlight, onHover }) {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</BracketViewport>
|
</BracketViewport>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+24
-2
@@ -834,10 +834,26 @@ h3 {
|
|||||||
/* The bracket reads as a campaign map you drag around. The viewport breaks the
|
/* The bracket reads as a campaign map you drag around. The viewport breaks the
|
||||||
page's max-width so there's room to manoeuvre; the surface carries a faint
|
page's max-width so there's room to manoeuvre; the surface carries a faint
|
||||||
tactical grid that pans with the bracket (it lives on the scrolling content). */
|
tactical grid that pans with the bracket (it lives on the scrolling content). */
|
||||||
.bracket-viewport {
|
/* The whole side (heading + canvas) breaks out to 85vw and centres on the
|
||||||
position: relative;
|
viewport, so the WINNER/LOSER heading aligns with the canvas's left edge. */
|
||||||
|
.bracket-side {
|
||||||
width: 85vw;
|
width: 85vw;
|
||||||
margin-left: calc(50% - 42.5vw);
|
margin-left: calc(50% - 42.5vw);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bracket-caret {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 0.7em;
|
||||||
|
transition: transform 0.18s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bracket-caret.is-collapsed {
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bracket-viewport {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
max-height: 78vh;
|
max-height: 78vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
@@ -849,6 +865,12 @@ h3 {
|
|||||||
|
|
||||||
.bracket-viewport.is-grabbing {
|
.bracket-viewport.is-grabbing {
|
||||||
cursor: grabbing;
|
cursor: grabbing;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* While panning, don't let the drag turn into a text selection. */
|
||||||
|
.bracket-viewport.is-grabbing * {
|
||||||
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tournament-bracket-grid {
|
.tournament-bracket-grid {
|
||||||
|
|||||||
Reference in New Issue
Block a user