feat(theseus/toolbar): Firefox-style single drag handle, zero-sum URL/search ratio
The two independent drag handles (right edge of URL bar, left edge of search bar) are replaced with ONE handle sitting between them, wrapped in a new .urlsearch flex container that owns the URL-bar-plus-search-bar budget between the nav buttons on the left and the trailing dock on the right. Zero-sum semantics: dragging the handle right grows the URL bar and shrinks the search bar; dragging left inverts. Nothing else in the toolbar moves — nav buttons, Downloads, extension dock and the ⛓ Theseus logo all sit outside .urlsearch's flex parent. When the window shrinks below the responsive breakpoint the search bar (and the drag handle) hide as before, and the URL bar grows to fill the .urlsearch budget on its own (added flex: 1 1 auto in the data-responsive rules for .urlwrap). Verified static rendering: url=700px → search=157px, url=300px → search=539px, url=default → 60/40 split, narrow window → search hidden and url fills. Old settings (urlBarSize / searchBoxSize / searchBoxWidthPx) are ignored by chrome.html but kept in main's SETTINGS_DEFAULTS so an older chrome.html could still read them on a downgrade. Only urlBarWidthPx is used going forward.
This commit is contained in:
parent
71bd3004e0
commit
e36d2b361c
2 changed files with 96 additions and 102 deletions
196
chrome.html
196
chrome.html
|
|
@ -159,28 +159,22 @@
|
|||
/* address bar. flex:1 by default consumes remaining space; user can cap
|
||||
it via Settings > Appearance > Address bar size so the extension dock
|
||||
gets more room. min-width guards against squeezing the URL invisible. */
|
||||
.urlwrap { position: relative; flex: 1 1 auto; min-width: 200px; display: flex; align-items: center; gap: 4px; background: var(--surface); border: 1px solid var(--line);
|
||||
/* The address bar and search bar share ONE flex budget between the nav
|
||||
buttons and the trailing button dock — the whole .urlsearch unit takes
|
||||
whatever space is left after the fixed items. A single drag handle
|
||||
between them shifts the ratio (bigger URL bar = smaller search bar,
|
||||
and vice versa) without moving anything else in the toolbar. When the
|
||||
window is too narrow for both, the search bar hides (see the
|
||||
data-responsive rules below); the URL bar keeps working alone. */
|
||||
.urlsearch { flex: 1 1 auto; min-width: 0; display: flex; align-items: center; gap: 6px; }
|
||||
.urlwrap { position: relative; flex: 0 1 var(--urlbar-w, 60%); min-width: 220px; display: flex; align-items: center; gap: 4px; background: var(--surface); border: 1px solid var(--line);
|
||||
border-radius: 999px; padding: 0 6px 0 4px; }
|
||||
/* Capped URL bar: pin the trailing items (download / extensions / logo /
|
||||
Theseus) to the RIGHT edge instead of packing them next to the URL bar
|
||||
— a smaller address bar shouldn't slide everything leftward. margin-
|
||||
right: auto on the URL bar consumes the slack. */
|
||||
.bar[data-urlsize="medium"] .urlwrap { flex: 0 1 640px; margin-right: auto; }
|
||||
.bar[data-urlsize="compact"] .urlwrap { flex: 0 1 400px; margin-right: auto; }
|
||||
.bar[data-urlwidth] .urlwrap { flex: 0 0 var(--urlbar-w, auto); margin-right: auto; }
|
||||
.searchbox { position: relative; }
|
||||
.bar[data-searchsize="hidden"] .searchbox { display: none; }
|
||||
.bar[data-searchsize="compact"] .searchbox { width: 180px; }
|
||||
.bar[data-searchsize="wide"] .searchbox { width: 400px; }
|
||||
.bar[data-searchwidth] .searchbox { width: var(--searchbox-w, 300px); }
|
||||
/* Drag-to-resize handles: a thin invisible strip on the trailing edge of
|
||||
.urlwrap and the leading edge of .searchbox. col-resize cursor makes
|
||||
the affordance visible; a faint acid tint on hover confirms it's live. */
|
||||
.urldrag, .searchdrag { position: absolute; top: 0; bottom: 0; width: 6px; cursor: col-resize; z-index: 10; background: transparent; }
|
||||
.urldrag { right: -3px; }
|
||||
.searchdrag { left: -3px; }
|
||||
.urldrag:hover, .searchdrag:hover,
|
||||
.urldrag.dragging, .searchdrag.dragging { background: rgb(from var(--acid) r g b / .35); }
|
||||
.searchbox { flex: 1 1 0; min-width: 140px; }
|
||||
/* Single drag handle sitting between the two — a thin 6px column with a
|
||||
col-resize cursor. Zero-sum semantics: dragging right grows the URL
|
||||
bar and shrinks the search bar; dragging left does the inverse. */
|
||||
.bardrag { flex: 0 0 6px; align-self: stretch; cursor: col-resize; background: transparent; border-radius: 3px; }
|
||||
.bardrag:hover, .bardrag.dragging { background: rgb(from var(--acid) r g b / .35); }
|
||||
/* Adaptive toolbar. A ResizeObserver on .bar sets data-responsive:
|
||||
"0" wide — everything visible (default)
|
||||
"1" tight — auto-hide the search box, min-width for URL bar drops
|
||||
|
|
@ -192,14 +186,17 @@
|
|||
narrow widths yields to the responsive collapse. */
|
||||
.bar[data-responsive="1"] .searchbox,
|
||||
.bar[data-responsive="2"] .searchbox,
|
||||
.bar[data-responsive="3"] .searchbox { display: none; }
|
||||
.bar[data-responsive="3"] .searchbox,
|
||||
.bar[data-responsive="1"] .bardrag,
|
||||
.bar[data-responsive="2"] .bardrag,
|
||||
.bar[data-responsive="3"] .bardrag { display: none; }
|
||||
.bar[data-responsive="2"] .logo span:not(.gear),
|
||||
.bar[data-responsive="3"] .logo span:not(.gear) { display: none; }
|
||||
.bar[data-responsive="2"] .logo,
|
||||
.bar[data-responsive="3"] .logo { padding: 4px 8px; }
|
||||
.bar[data-responsive="1"] .urlwrap,
|
||||
.bar[data-responsive="2"] .urlwrap,
|
||||
.bar[data-responsive="3"] .urlwrap { min-width: 120px; }
|
||||
.bar[data-responsive="3"] .urlwrap { min-width: 120px; flex: 1 1 auto; }
|
||||
/* Level 3: hide the per-extension button row, show the collapsed puzzle
|
||||
button. Dropdown is a floating .extpop the JS builds on click. */
|
||||
/* Two paths to the collapsed dock: the coarse width-based level 3
|
||||
|
|
@ -279,8 +276,10 @@
|
|||
.pwchip { display: inline-flex; align-items: center; gap: 3px; border: none; background: transparent; color: #3fb950; cursor: pointer; padding: 2px 5px; border-radius: 6px; font-size: 11px; font-weight: 700; }
|
||||
.pwchip:hover { background: rgba(63,185,80,.14); }
|
||||
.pwchip svg { width: 14px; height: 14px; }
|
||||
/* search box: engine icon (click = pick engine) + a wide typing area */
|
||||
.searchbox { display: flex; align-items: center; width: 300px; background: var(--surface); border: 1px solid var(--line);
|
||||
/* search box: engine icon (click = pick engine) + a wide typing area.
|
||||
Width comes from the parent .urlsearch flex budget (flex: 1 1 0) —
|
||||
the earlier top rule at line 172 sets that; here we only style. */
|
||||
.searchbox { display: flex; align-items: center; background: var(--surface); border: 1px solid var(--line);
|
||||
border-radius: 999px; padding: 0 12px 0 3px; }
|
||||
.searchbox:focus-within { border-color: #4b7bec; }
|
||||
/* dropdown button shows the SELECTED engine's favicon (emoji fallback) + a caret */
|
||||
|
|
@ -391,25 +390,26 @@
|
|||
<button class="ic" id="reload" title="Reload"><svg viewBox="0 0 16 16"><path d="M13 8 a5 5 0 1 1 -1.5 -3.6"/><path d="M13 2.5 L13 5 L10.5 5"/></svg></button>
|
||||
<button class="ic" id="home" title="Home"><svg viewBox="0 0 16 16"><path d="M2.5 7.5 L8 3 L13.5 7.5"/><path d="M4 6.7 L4 13 L12 13 L12 6.7"/></svg></button>
|
||||
</div>
|
||||
<div class="urlwrap">
|
||||
<button class="secbadge" id="secbadge" title="Site information">
|
||||
<!-- Heraldic shield outline matching the reference image: flared top
|
||||
shoulders, concave sides tapering to a sharp bottom point. Single
|
||||
filled path, `currentColor` so state class toggles the tint. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true">
|
||||
<path d="M8 1.2 C6 1.2 3.5 1.7 2.5 2.3 Q2 2.6 2 3.5 L2 7 C2 11.2 4.2 13.7 8 15 C11.8 13.7 14 11.2 14 7 L14 3.5 Q14 2.6 13.5 2.3 C12.5 1.7 10 1.2 8 1.2 Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<input id="url" placeholder="Ask a search engine or enter web address" spellcheck="false">
|
||||
<span class="reg" id="reg" hidden></span>
|
||||
<button class="pwchip" id="pwchip" title="Fill password for this site" hidden><span id="pwchipCount"></span><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="6" cy="9" r="3.2" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M8.8 8.6 L14 8.6 M13.2 8.6 L13.2 11 M11.5 8.6 L11.5 10.4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
|
||||
<button class="star" id="star" title="Save this page">☆</button>
|
||||
<div class="urldrag" id="urldrag" title="Drag to resize"></div>
|
||||
</div>
|
||||
<div class="searchbox">
|
||||
<div class="searchdrag" id="searchdrag" title="Drag to resize"></div>
|
||||
<button id="engineBtn" class="engine-btn" title="Choose search engine"><span id="engineIcon"></span><span class="cv">▾</span></button>
|
||||
<input id="search" placeholder="Search" spellcheck="false">
|
||||
<div class="urlsearch" id="urlsearch">
|
||||
<div class="urlwrap">
|
||||
<button class="secbadge" id="secbadge" title="Site information">
|
||||
<!-- Heraldic shield outline matching the reference image: flared top
|
||||
shoulders, concave sides tapering to a sharp bottom point. Single
|
||||
filled path, `currentColor` so state class toggles the tint. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" aria-hidden="true">
|
||||
<path d="M8 1.2 C6 1.2 3.5 1.7 2.5 2.3 Q2 2.6 2 3.5 L2 7 C2 11.2 4.2 13.7 8 15 C11.8 13.7 14 11.2 14 7 L14 3.5 Q14 2.6 13.5 2.3 C12.5 1.7 10 1.2 8 1.2 Z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<input id="url" placeholder="Ask a search engine or enter web address" spellcheck="false">
|
||||
<span class="reg" id="reg" hidden></span>
|
||||
<button class="pwchip" id="pwchip" title="Fill password for this site" hidden><span id="pwchipCount"></span><svg viewBox="0 0 16 16" aria-hidden="true"><circle cx="6" cy="9" r="3.2" fill="none" stroke="currentColor" stroke-width="1.5"/><path d="M8.8 8.6 L14 8.6 M13.2 8.6 L13.2 11 M11.5 8.6 L11.5 10.4" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg></button>
|
||||
<button class="star" id="star" title="Save this page">☆</button>
|
||||
</div>
|
||||
<div class="bardrag" id="bardrag" title="Drag to shift the address / search bar ratio"></div>
|
||||
<div class="searchbox">
|
||||
<button id="engineBtn" class="engine-btn" title="Choose search engine"><span id="engineIcon"></span><span class="cv">▾</span></button>
|
||||
<input id="search" placeholder="Search" spellcheck="false">
|
||||
</div>
|
||||
</div>
|
||||
<button class="ic dlbtn" id="downloads" title="Downloads">
|
||||
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 2 V10 M4.5 6.5 L8 10 L11.5 6.5"/><path d="M3 12.5 L3 13.5 L13 13.5 L13 12.5"/></svg>
|
||||
|
|
@ -1352,27 +1352,22 @@
|
|||
});
|
||||
|
||||
syncHeight();
|
||||
// Toolbar sizing: URL bar + search box widths follow user preference.
|
||||
// Applied as data-attrs on .bar so CSS handles the layout swap.
|
||||
// urlBarWidthPx / searchBoxWidthPx (integers > 0) come from the drag
|
||||
// handles; when present they OVERRIDE the discrete size preset. Set to
|
||||
// null (or 0) to restore preset behavior.
|
||||
// Toolbar sizing: only urlBarWidthPx now matters. The URL bar and the
|
||||
// search bar share one flex budget in .urlsearch — bigger URL bar means
|
||||
// smaller search bar and vice versa, without moving any of the fixed
|
||||
// items on either side. Old urlBarSize / searchBoxSize / searchBoxWidthPx
|
||||
// settings are ignored (kept in the settings file for backward compat
|
||||
// so an older version's chrome.html can still pick them up).
|
||||
function applyBarSizes(s) {
|
||||
if (!s) return;
|
||||
const bar = $("bar");
|
||||
if (s.urlBarSize) bar.dataset.urlsize = s.urlBarSize;
|
||||
if (s.searchBoxSize) bar.dataset.searchsize = s.searchBoxSize;
|
||||
const uw = Number(s.urlBarWidthPx) || 0;
|
||||
if (uw > 0) { bar.dataset.urlwidth = "1"; bar.style.setProperty("--urlbar-w", uw + "px"); }
|
||||
else { delete bar.dataset.urlwidth; bar.style.removeProperty("--urlbar-w"); }
|
||||
const sw = Number(s.searchBoxWidthPx) || 0;
|
||||
if (sw > 0) { bar.dataset.searchwidth = "1"; bar.style.setProperty("--searchbox-w", sw + "px"); }
|
||||
else { delete bar.dataset.searchwidth; bar.style.removeProperty("--searchbox-w"); }
|
||||
// The URL/search width change squeezes the extension dock; the RO on
|
||||
// #extbuttons doesn't always fire (overflow:hidden parent), so re-check
|
||||
// overflow explicitly after the next layout pass. requestAnimationFrame
|
||||
// isn't always enough (the CSS var propagation + reflow can straddle
|
||||
// frames), hence the belt-and-braces setTimeout too.
|
||||
if (uw > 0) bar.style.setProperty("--urlbar-w", uw + "px");
|
||||
else bar.style.removeProperty("--urlbar-w");
|
||||
// The URL/search ratio change doesn't squeeze the extension dock any
|
||||
// more (they live in different flex parents), but the responsive-hide
|
||||
// rules for narrow bars still depend on updateExtCollapse's math, so
|
||||
// re-check overflow after the layout settles.
|
||||
if (typeof updateExtCollapse === "function") {
|
||||
requestAnimationFrame(updateExtCollapse);
|
||||
setTimeout(updateExtCollapse, 30);
|
||||
|
|
@ -1437,49 +1432,48 @@
|
|||
$("findNext").onclick = () => stepFind(true);
|
||||
$("findClose").onclick = closeFind;
|
||||
|
||||
// Drag handles: right edge of .urlwrap resizes the URL bar; left edge of
|
||||
// .searchbox resizes the search box. Persists to settings.urlBarWidthPx /
|
||||
// searchBoxWidthPx on release, which then wins over the size preset.
|
||||
function makeDragHandle(handleEl, targetEl, opts) {
|
||||
let startX = 0, startW = 0, dragging = false, lastW = 0;
|
||||
handleEl.addEventListener("pointerdown", (e) => {
|
||||
if (e.button !== 0) return;
|
||||
dragging = true;
|
||||
startX = e.clientX;
|
||||
startW = targetEl.getBoundingClientRect().width;
|
||||
handleEl.setPointerCapture(e.pointerId);
|
||||
handleEl.classList.add("dragging");
|
||||
e.preventDefault();
|
||||
});
|
||||
handleEl.addEventListener("pointermove", (e) => {
|
||||
if (!dragging) return;
|
||||
const dx = e.clientX - startX;
|
||||
// Left-edge handles (search) grow when dragged LEFT (dx < 0).
|
||||
const w = Math.round(Math.max(opts.min, Math.min(opts.max, startW + (opts.reverseX ? -dx : dx))));
|
||||
lastW = w;
|
||||
opts.applyLive(w);
|
||||
});
|
||||
const stop = (e) => {
|
||||
if (!dragging) return;
|
||||
dragging = false;
|
||||
handleEl.classList.remove("dragging");
|
||||
try { handleEl.releasePointerCapture(e.pointerId); } catch {}
|
||||
if (lastW > 0 && T.setSetting) T.setSetting(opts.settingKey, lastW);
|
||||
};
|
||||
handleEl.addEventListener("pointerup", stop);
|
||||
handleEl.addEventListener("pointercancel", stop);
|
||||
}
|
||||
// Single drag handle between the address bar and the search bar (Firefox
|
||||
// model): dragging right grows the URL bar and shrinks the search bar,
|
||||
// dragging left inverts. Zero-sum — nothing else in the toolbar moves.
|
||||
// The URL bar's width is what we control directly; the search bar just
|
||||
// fills whatever's left of the .urlsearch flex budget. Persisted as
|
||||
// urlBarWidthPx on release. Bounds:
|
||||
// min = URL bar's own min-width (matches the 220px CSS floor)
|
||||
// max = urlsearch total width − handle − gap − minSearch(140)
|
||||
// so the drag can never squeeze the search bar past its CSS floor either.
|
||||
const barEl2 = $("bar");
|
||||
makeDragHandle($("urldrag"), $("bar").querySelector(".urlwrap"), {
|
||||
min: 200, max: 1800, settingKey: "urlBarWidthPx", reverseX: false,
|
||||
applyLive: (w) => { barEl2.dataset.urlwidth = "1"; barEl2.style.setProperty("--urlbar-w", w + "px");
|
||||
if (typeof updateExtCollapse === "function") updateExtCollapse(); },
|
||||
const handleEl = $("bardrag");
|
||||
const urlWrapEl = barEl2.querySelector(".urlwrap");
|
||||
const urlsearchEl = $("urlsearch");
|
||||
const MIN_URL = 220, MIN_SEARCH = 140, HANDLE_W = 6, GAP = 6;
|
||||
let dragging = false, startX = 0, startW = 0, lastW = 0;
|
||||
handleEl.addEventListener("pointerdown", (e) => {
|
||||
if (e.button !== 0) return;
|
||||
dragging = true;
|
||||
startX = e.clientX;
|
||||
startW = urlWrapEl.getBoundingClientRect().width;
|
||||
handleEl.setPointerCapture(e.pointerId);
|
||||
handleEl.classList.add("dragging");
|
||||
e.preventDefault();
|
||||
});
|
||||
makeDragHandle($("searchdrag"), $("bar").querySelector(".searchbox"), {
|
||||
min: 120, max: 800, settingKey: "searchBoxWidthPx", reverseX: true,
|
||||
applyLive: (w) => { barEl2.dataset.searchwidth = "1"; barEl2.style.setProperty("--searchbox-w", w + "px");
|
||||
if (typeof updateExtCollapse === "function") updateExtCollapse(); },
|
||||
handleEl.addEventListener("pointermove", (e) => {
|
||||
if (!dragging) return;
|
||||
const total = urlsearchEl.getBoundingClientRect().width;
|
||||
const maxUrl = Math.max(MIN_URL, total - HANDLE_W - GAP * 2 - MIN_SEARCH);
|
||||
const w = Math.round(Math.max(MIN_URL, Math.min(maxUrl, startW + (e.clientX - startX))));
|
||||
lastW = w;
|
||||
barEl2.style.setProperty("--urlbar-w", w + "px");
|
||||
if (typeof updateExtCollapse === "function") updateExtCollapse();
|
||||
});
|
||||
const stopDrag = (e) => {
|
||||
if (!dragging) return;
|
||||
dragging = false;
|
||||
handleEl.classList.remove("dragging");
|
||||
try { handleEl.releasePointerCapture(e.pointerId); } catch {}
|
||||
if (lastW > 0 && T.setSetting) T.setSetting("urlBarWidthPx", lastW);
|
||||
};
|
||||
handleEl.addEventListener("pointerup", stopDrag);
|
||||
handleEl.addEventListener("pointercancel", stopDrag);
|
||||
// Self-heal: some menu-close paths (drag interruptions, focus flips) can
|
||||
// leave the chrome view taller than the natural body-scrollHeight. Every
|
||||
// 750 ms with no menu open, re-sync — main.js only re-layouts when the
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "theseus-navigator",
|
||||
"version": "0.3.36",
|
||||
"version": "0.3.37",
|
||||
"description": "Theseus Navigator — a browser that follows the thread. By Silent Mode, a Deviant project.",
|
||||
"author": "Silent Mode",
|
||||
"main": "main.js",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue