mirror of
https://github.com/BetterSEQTA/BetterSEQTA-Plus.git
synced 2026-08-28 17:21:05 +00:00
feat: minor fixes before update
This commit is contained in:
@@ -530,7 +530,30 @@ function setupSidebarAccessibility() {
|
||||
if (!menu) return;
|
||||
|
||||
sidebarAccessibilityObserver?.disconnect();
|
||||
sidebarAccessibilityObserver = new MutationObserver(() => {
|
||||
sidebarAccessibilityObserver = new MutationObserver((mutations) => {
|
||||
// Custom Svelte sidebar owns drill a11y — ignore its DOM (opening Goals/Folios
|
||||
// mutates a lot; re-running here used to help freeze the tab).
|
||||
if (menu.classList.contains("bsplus-custom-sidebar")) {
|
||||
const root = document.getElementById("bsplus-sidebar-root");
|
||||
if (
|
||||
root &&
|
||||
mutations.every(
|
||||
(m) => root === m.target || root.contains(m.target as Node),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// Still ignore class/style-only native noise while custom sidebar is on.
|
||||
if (
|
||||
mutations.every(
|
||||
(m) =>
|
||||
m.type === "attributes" &&
|
||||
(m.attributeName === "class" || m.attributeName === "style"),
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
scheduleSidebarAccessibilityUpdate();
|
||||
});
|
||||
sidebarAccessibilityObserver.observe(menu, {
|
||||
|
||||
@@ -97,6 +97,20 @@
|
||||
void sidebarState.isDrilling;
|
||||
restoreCustomMenuActive();
|
||||
});
|
||||
|
||||
// Drill `.sub` is position:absolute inside this scrollport — if the list was
|
||||
// scrolled down (e.g. Folios/Goals near the bottom), the panel sits under the
|
||||
// logo until we reset. Also reset when going back up the stack.
|
||||
$effect(() => {
|
||||
void sidebarState.drillStack.length;
|
||||
void sidebarState.enterFrameKey;
|
||||
const root = document.getElementById("bsplus-sidebar-root");
|
||||
if (!root) return;
|
||||
root.scrollTop = 0;
|
||||
requestAnimationFrame(() => {
|
||||
root.scrollTop = 0;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<!--
|
||||
@@ -212,10 +226,15 @@
|
||||
role="button"
|
||||
tabindex="0"
|
||||
aria-label="Back"
|
||||
onclick={() => sidebarState.goBack()}
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
sidebarState.goBack();
|
||||
}}
|
||||
onkeydown={(e) => {
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
sidebarState.goBack();
|
||||
}
|
||||
}}
|
||||
@@ -303,22 +322,27 @@
|
||||
.bsplus-sidebar-edit-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
width: 85%;
|
||||
margin: 12px auto 16px;
|
||||
width: calc(100% - 12px);
|
||||
margin: 12px 6px 16px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
cursor: default;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.edit-btn {
|
||||
flex: 1;
|
||||
padding: 10px 12px;
|
||||
flex: 1 1 0;
|
||||
min-width: 0;
|
||||
padding: 10px 8px;
|
||||
border: none;
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.2s ease,
|
||||
|
||||
@@ -56,13 +56,18 @@
|
||||
ondragstart={() => onDragStart?.(item.key)}
|
||||
ondragover={(e) => e.preventDefault()}
|
||||
ondrop={() => onDrop?.(item.key)}
|
||||
onclick={() => {
|
||||
onclick={(e) => {
|
||||
// Keep SEQTA's #menu handlers from seeing custom-list clicks — that fights
|
||||
// our drill UI and can freeze the tab (Goals / Folios / etc.).
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (!editMode) onActivate(item);
|
||||
}}
|
||||
onkeydown={(e) => {
|
||||
if (editMode) return;
|
||||
if (e.key === "Enter" || e.key === " ") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
onActivate(item);
|
||||
}
|
||||
}}
|
||||
@@ -118,10 +123,11 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.bsplus-sidebar-item > label {
|
||||
.bsplus-sidebar-item > label:not(.toggle) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
@@ -129,7 +135,7 @@
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.bsplus-sidebar-item:hover {
|
||||
.bsplus-sidebar-item:hover:not(.active) {
|
||||
background: rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@@ -138,7 +144,8 @@
|
||||
box-shadow: 0 0 0 2px var(--theme-primary, #fff);
|
||||
}
|
||||
|
||||
.bsplus-sidebar-item.active:not(.hasChildren) {
|
||||
.bsplus-sidebar-item.active:not(.hasChildren),
|
||||
.bsplus-sidebar-item.active:not(.hasChildren):hover {
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
color: #fff;
|
||||
}
|
||||
@@ -147,7 +154,7 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bsplus-sidebar-item.compact > label {
|
||||
.bsplus-sidebar-item.compact > label:not(.toggle) {
|
||||
padding: 8px;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -158,6 +165,18 @@
|
||||
|
||||
.bsplus-sidebar-item.edit-mode {
|
||||
cursor: grab;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
gap: 4px;
|
||||
padding-right: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.bsplus-sidebar-item.edit-mode > label:not(.toggle) {
|
||||
flex: 1 1 0;
|
||||
width: 0;
|
||||
min-width: 0;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.bsplus-sidebar-item :global(label > svg) {
|
||||
@@ -171,8 +190,9 @@
|
||||
.label {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
@@ -194,17 +214,23 @@
|
||||
}
|
||||
|
||||
.toggle {
|
||||
margin-right: 12px;
|
||||
margin: 0 10px 0 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
flex-shrink: 0;
|
||||
flex: 0 0 auto;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.toggle input {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
accent-color: var(--theme-primary, #fff);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ let menuEl: HTMLElement | null = null;
|
||||
let menuObserver: MutationObserver | null = null;
|
||||
let syncTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
let hashListenerAttached = false;
|
||||
let sidebarCaptureAttached = false;
|
||||
let earlyPrepareStarted = false;
|
||||
let catchupTimer: ReturnType<typeof setInterval> | null = null;
|
||||
let nativeMenuListenerAttached = false;
|
||||
@@ -56,6 +57,44 @@ function scheduleSync() {
|
||||
}, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture-phase: own all clicks inside the custom list so SEQTA's #menu handlers
|
||||
* never see them. Opening Goals/Folios via SEQTA + our drill UI freezes the tab.
|
||||
*/
|
||||
function onCustomSidebarCaptureClick(event: MouseEvent) {
|
||||
if (!menuEl || sidebarState.editMode) return;
|
||||
|
||||
const root = document.getElementById(ROOT_ID);
|
||||
const target = event.target;
|
||||
if (!(target instanceof Element) || !root?.contains(target)) return;
|
||||
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
const back = target.closest(".back");
|
||||
if (back instanceof HTMLElement && root.contains(back)) {
|
||||
sidebarState.goBack();
|
||||
return;
|
||||
}
|
||||
|
||||
const li = target.closest("li.item[data-key]");
|
||||
if (!(li instanceof HTMLElement) || !root.contains(li)) return;
|
||||
|
||||
// Already-open folder chrome (renders its own `.sub`) — ignore; use Back.
|
||||
if (
|
||||
li.classList.contains("hasChildren") &&
|
||||
li.querySelector(":scope > .sub")
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const key = li.dataset.key;
|
||||
if (!key) return;
|
||||
const item = sidebarState.findByKey(key);
|
||||
if (item) sidebarState.activateItem(item, menuEl);
|
||||
}
|
||||
|
||||
function onHashChange() {
|
||||
sidebarState.syncActiveFromLocation();
|
||||
if (menuEl) clearNativeDrillActive(menuEl);
|
||||
@@ -189,11 +228,14 @@ export async function mountCustomSidebar(): Promise<boolean> {
|
||||
menuObserver?.disconnect();
|
||||
menuObserver = new MutationObserver((mutations) => {
|
||||
const ours = document.getElementById(ROOT_ID);
|
||||
// Ignore our list entirely. Also ignore native `class` toggles — SEQTA
|
||||
// re-adds drill `.active` after we clear it; syncing on that freezes the tab.
|
||||
// Ignore our list entirely. Ignore native class/style churn — SEQTA and
|
||||
// theme transitions rewrite those constantly; syncing on them freezes the tab.
|
||||
const relevant = mutations.some((m) => {
|
||||
if (ours?.contains(m.target as Node)) return false;
|
||||
if (m.type === "attributes" && m.attributeName === "class") return false;
|
||||
if (m.type === "attributes") {
|
||||
const attr = m.attributeName;
|
||||
if (attr === "class" || attr === "style") return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!relevant) return;
|
||||
@@ -203,9 +245,14 @@ export async function mountCustomSidebar(): Promise<boolean> {
|
||||
subtree: true,
|
||||
childList: true,
|
||||
attributes: true,
|
||||
attributeFilter: ["style", "data-key", "data-path", "data-colour"],
|
||||
attributeFilter: ["data-key", "data-path", "data-colour"],
|
||||
});
|
||||
|
||||
if (!sidebarCaptureAttached) {
|
||||
document.addEventListener("click", onCustomSidebarCaptureClick, true);
|
||||
sidebarCaptureAttached = true;
|
||||
}
|
||||
|
||||
if (!hashListenerAttached) {
|
||||
window.addEventListener("hashchange", onHashChange);
|
||||
hashListenerAttached = true;
|
||||
@@ -242,6 +289,11 @@ export function unmountCustomSidebar() {
|
||||
hashListenerAttached = false;
|
||||
}
|
||||
|
||||
if (sidebarCaptureAttached) {
|
||||
document.removeEventListener("click", onCustomSidebarCaptureClick, true);
|
||||
sidebarCaptureAttached = false;
|
||||
}
|
||||
|
||||
if (nativeMenuListenerAttached) {
|
||||
window.removeEventListener("bsplus-native-menu-updated", syncFromMenu);
|
||||
nativeMenuListenerAttached = false;
|
||||
|
||||
@@ -43,6 +43,15 @@ function ensureActive(el: Element | null | undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
function resetSidebarScroll() {
|
||||
const root = document.getElementById("bsplus-sidebar-root");
|
||||
if (!(root instanceof HTMLElement)) return;
|
||||
root.scrollTop = 0;
|
||||
requestAnimationFrame(() => {
|
||||
root.scrollTop = 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SEQTA (and some themes) strip `.active` from `#menu li` after navigation.
|
||||
* Theme decorations and drill `.sub` chrome depend on that class on our list.
|
||||
@@ -247,17 +256,29 @@ class SidebarState {
|
||||
}
|
||||
}
|
||||
|
||||
openFolder(item: SidebarItem) {
|
||||
openFolder(item: SidebarItem, menu?: HTMLElement) {
|
||||
if (!item.hasChildren) return;
|
||||
// Ignore duplicate opens (double-firing click / label + li).
|
||||
if (this.drillStack.at(-1)?.key === item.key) return;
|
||||
|
||||
const frame: SidebarDrillFrame = {
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
items: filterVisible(item.children),
|
||||
};
|
||||
const isRoot = this.visibleRootItems.some((entry) => entry.key === item.key);
|
||||
|
||||
this.enterFrameKey = item.key;
|
||||
this.drillStack = [
|
||||
...this.drillStack,
|
||||
{
|
||||
key: item.key,
|
||||
label: item.label,
|
||||
items: filterVisible(item.children),
|
||||
},
|
||||
];
|
||||
// Root folders replace the stack; nested folders append.
|
||||
this.drillStack = isRoot ? [frame] : [...this.drillStack, frame];
|
||||
|
||||
// Keep native drill closed so SEQTA CSS :has(> ul > li.hasChildren.active)
|
||||
// does not lock pointer-events on the custom list.
|
||||
if (menu) clearNativeDrillActive(menu);
|
||||
|
||||
// Absolute `.sub` panels live inside the scrollport — jump to top so the
|
||||
// drilled page isn't left under the logo when the list was scrolled down.
|
||||
resetSidebarScroll();
|
||||
}
|
||||
|
||||
clearEnterFrame(key?: string) {
|
||||
@@ -270,11 +291,13 @@ class SidebarState {
|
||||
if (!this.drillStack.length) return;
|
||||
this.enterFrameKey = null;
|
||||
this.drillStack = this.drillStack.slice(0, -1);
|
||||
resetSidebarScroll();
|
||||
}
|
||||
|
||||
resetDrill() {
|
||||
this.enterFrameKey = null;
|
||||
this.drillStack = [];
|
||||
resetSidebarScroll();
|
||||
}
|
||||
|
||||
setEditMode(enabled: boolean) {
|
||||
@@ -320,7 +343,7 @@ class SidebarState {
|
||||
if (this.editMode) return;
|
||||
|
||||
if (item.hasChildren) {
|
||||
this.openFolder(item);
|
||||
this.openFolder(item, menu);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user