feat(theseus/toolbar): drag-handles + pin right-side items on URL/search resize
Two fixes bundled: 1) Right-side items (download, extension dock, Theseus button) no longer slide leftward when the URL bar is capped. Added margin-right: auto to .urlwrap under every capped state (data-urlsize=medium/compact and the new data-urlwidth override) so the leftover flex space sits AFTER the URL bar, keeping the right group pinned to the right edge. Verified: logo's gap from bar's right edge stays at 10px across default / urlBarSize=compact / urlBarWidthPx=500 / reset. 2) Drag handles for live resize. A 6-px col-resize strip sits on the trailing edge of .urlwrap and the leading edge of .searchbox; a pointerdown/move/up dance updates the width live via a CSS custom property and persists to settings on release. Two new keys: - urlBarWidthPx (0-1800, 0 = follow size preset) - searchBoxWidthPx (0-800, 0 = follow size preset) When either is non-zero, the corresponding CSS override wins over the discrete size preset. Preload gets setSetting so the drag can push the persistent value from chrome. Visual affordance: handles are transparent by default, gain a faint acid tint on hover and while dragging.
This commit is contained in:
parent
c55570e4bf
commit
909b93ee23
3 changed files with 76 additions and 3 deletions
74
chrome.html
74
chrome.html
|
|
@ -147,13 +147,28 @@
|
||||||
/* address bar. flex:1 by default consumes remaining space; user can cap
|
/* address bar. flex:1 by default consumes remaining space; user can cap
|
||||||
it via Settings > Appearance > Address bar size so the extension dock
|
it via Settings > Appearance > Address bar size so the extension dock
|
||||||
gets more room. min-width guards against squeezing the URL invisible. */
|
gets more room. min-width guards against squeezing the URL invisible. */
|
||||||
.urlwrap { flex: 1 1 auto; min-width: 200px; display: flex; align-items: center; gap: 4px; background: var(--surface); border: 1px solid var(--line);
|
.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);
|
||||||
border-radius: 999px; padding: 0 6px 0 4px; }
|
border-radius: 999px; padding: 0 6px 0 4px; }
|
||||||
.bar[data-urlsize="medium"] .urlwrap { flex: 0 1 640px; }
|
/* Capped URL bar: pin the trailing items (download / extensions / logo /
|
||||||
.bar[data-urlsize="compact"] .urlwrap { flex: 0 1 400px; }
|
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="hidden"] .searchbox { display: none; }
|
||||||
.bar[data-searchsize="compact"] .searchbox { width: 180px; }
|
.bar[data-searchsize="compact"] .searchbox { width: 180px; }
|
||||||
.bar[data-searchsize="wide"] .searchbox { width: 400px; }
|
.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: rgba(214,255,61,.35); }
|
||||||
/* Adaptive toolbar. A ResizeObserver on .bar sets data-responsive:
|
/* Adaptive toolbar. A ResizeObserver on .bar sets data-responsive:
|
||||||
"0" wide — everything visible (default)
|
"0" wide — everything visible (default)
|
||||||
"1" tight — auto-hide the search box, min-width for URL bar drops
|
"1" tight — auto-hide the search box, min-width for URL bar drops
|
||||||
|
|
@ -339,8 +354,10 @@
|
||||||
<span class="reg" id="reg" hidden></span>
|
<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="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>
|
<button class="star" id="star" title="Save this page">☆</button>
|
||||||
|
<div class="urldrag" id="urldrag" title="Drag to resize"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="searchbox">
|
<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>
|
<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">
|
<input id="search" placeholder="Search" spellcheck="false">
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1197,15 +1214,66 @@
|
||||||
syncHeight();
|
syncHeight();
|
||||||
// Toolbar sizing: URL bar + search box widths follow user preference.
|
// Toolbar sizing: URL bar + search box widths follow user preference.
|
||||||
// Applied as data-attrs on .bar so CSS handles the layout swap.
|
// 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.
|
||||||
function applyBarSizes(s) {
|
function applyBarSizes(s) {
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
const bar = $("bar");
|
const bar = $("bar");
|
||||||
if (s.urlBarSize) bar.dataset.urlsize = s.urlBarSize;
|
if (s.urlBarSize) bar.dataset.urlsize = s.urlBarSize;
|
||||||
if (s.searchBoxSize) bar.dataset.searchsize = s.searchBoxSize;
|
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"); }
|
||||||
syncHeight();
|
syncHeight();
|
||||||
}
|
}
|
||||||
T.getSettings && T.getSettings().then(applyBarSizes);
|
T.getSettings && T.getSettings().then(applyBarSizes);
|
||||||
T.onSettingsUpdate && T.onSettingsUpdate(applyBarSizes);
|
T.onSettingsUpdate && T.onSettingsUpdate(applyBarSizes);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
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"); },
|
||||||
|
});
|
||||||
|
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"); },
|
||||||
|
});
|
||||||
// Self-heal: some menu-close paths (drag interruptions, focus flips) can
|
// Self-heal: some menu-close paths (drag interruptions, focus flips) can
|
||||||
// leave the chrome view taller than the natural body-scrollHeight. Every
|
// 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
|
// 750 ms with no menu open, re-sync — main.js only re-layouts when the
|
||||||
|
|
|
||||||
4
main.js
4
main.js
|
|
@ -230,6 +230,10 @@ const SETTINGS_DEFAULTS = {
|
||||||
// room to grow. The search box is fixed-width; hidden removes it.
|
// room to grow. The search box is fixed-width; hidden removes it.
|
||||||
urlBarSize: "wide", // compact | medium | wide (default)
|
urlBarSize: "wide", // compact | medium | wide (default)
|
||||||
searchBoxSize: "normal", // hidden | compact | normal (default) | wide
|
searchBoxSize: "normal", // hidden | compact | normal (default) | wide
|
||||||
|
// Drag-set widths in pixels. Non-zero → override the corresponding size
|
||||||
|
// preset; zero/null → follow the preset. Set by the toolbar drag handles.
|
||||||
|
urlBarWidthPx: 0,
|
||||||
|
searchBoxWidthPx: 0,
|
||||||
};
|
};
|
||||||
// Applying the theme via nativeTheme.themeSource makes prefers-color-scheme update
|
// Applying the theme via nativeTheme.themeSource makes prefers-color-scheme update
|
||||||
// in every renderer (chrome, settings, popover, page views) with no per-view IPC.
|
// in every renderer (chrome, settings, popover, page views) with no per-view IPC.
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ contextBridge.exposeInMainWorld("theseus", {
|
||||||
toggleTor: () => ipcRenderer.invoke("toggle-tor"),
|
toggleTor: () => ipcRenderer.invoke("toggle-tor"),
|
||||||
openSettings: () => ipcRenderer.invoke("open-settings"),
|
openSettings: () => ipcRenderer.invoke("open-settings"),
|
||||||
getSettings: () => ipcRenderer.invoke("settings-get"),
|
getSettings: () => ipcRenderer.invoke("settings-get"),
|
||||||
|
setSetting: (key, val) => ipcRenderer.invoke("settings-set", key, val),
|
||||||
onSettingsUpdate: (cb) => ipcRenderer.on("settings-update", (_e, d) => cb(d)),
|
onSettingsUpdate: (cb) => ipcRenderer.on("settings-update", (_e, d) => cb(d)),
|
||||||
toggleSiteInfo: (rect) => ipcRenderer.invoke("toggle-site-info", rect),
|
toggleSiteInfo: (rect) => ipcRenderer.invoke("toggle-site-info", rect),
|
||||||
switchToBcnr: () => ipcRenderer.invoke("switch-to-bcnr"),
|
switchToBcnr: () => ipcRenderer.invoke("switch-to-bcnr"),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue