fix: fix sidebar animation weirdness in sidebar folders

This commit is contained in:
2026-08-24 14:04:08 +09:30
parent a411d646ae
commit dde5096d1c
5 changed files with 155 additions and 78 deletions
+31 -15
View File
@@ -722,6 +722,13 @@ html.bsplus-custom-title-pending #title > :not(#bsplus-title-root),
pointer-events: none !important;
}
/* Same locks keyed off `.drilling` so nested leaf clicks do not flicker when
SEQTA briefly strips `.active` from folder rows. */
> #bsplus-sidebar-root.drilling > li:not(.hasChildren),
> #bsplus-sidebar-root.drilling > li:not(.hasChildren) * {
pointer-events: none !important;
}
> #bsplus-sidebar-root .sub {
pointer-events: none;
}
@@ -742,13 +749,24 @@ html.bsplus-custom-title-pending #title > :not(#bsplus-title-root),
background-image: none !important;
}
/* One-shot enter — not tied to `.active` (SEQTA strips/restores that on every
nested click, which would restart the animation). */
> #bsplus-sidebar-root .sub.bsplus-sub-enter {
animation: bsplus-submenu-enter 0.32s cubic-bezier(0.22, 1, 0.36, 1);
> #bsplus-sidebar-root.drilling li.hasChildren > .sub:not(:has(.sub)) {
pointer-events: auto !important;
background: transparent !important;
background-image: none !important;
}
> #bsplus-sidebar-root > li.hasChildren.active > label {
> #bsplus-sidebar-root.drilling li.hasChildren > .sub:has(.sub) {
pointer-events: none !important;
}
> #bsplus-sidebar-root.drilling li.hasChildren > .sub .sub {
pointer-events: auto !important;
background: transparent !important;
background-image: none !important;
}
> #bsplus-sidebar-root > li.hasChildren.active > label,
> #bsplus-sidebar-root.drilling > li.hasChildren > label {
pointer-events: none !important;
}
@@ -764,6 +782,14 @@ html.bsplus-custom-title-pending #title > :not(#bsplus-title-root),
opacity: 0 !important;
}
> #bsplus-sidebar-root.drilling > li:not(.hasChildren)::before,
> #bsplus-sidebar-root.drilling > li:not(.hasChildren)::after {
content: none !important;
display: none !important;
animation: none !important;
opacity: 0 !important;
}
#bsplus-sidebar-root .sub .back {
display: flex;
align-items: center;
@@ -790,16 +816,6 @@ html.bsplus-custom-title-pending #title > :not(#bsplus-title-root),
}
}
@keyframes bsplus-submenu-enter {
from {
opacity: 0.35;
transform: translateX(28px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
#menu li > label > svg,
#menu section > label > svg {
margin: 0 10px 0 4px;
+96 -56
View File
@@ -15,44 +15,25 @@
#menu > ul:has(li.hasChildren.active) > li > svg,
#menu > ul ul:has(li.hasChildren.active) > li > label,
#menu > ul ul:has(li.hasChildren.active) > li > svg {
/* !important beats theme animations (e.g. Beach palm-slide forwards). */
transform: translateX(-320px) !important;
animation: none !important;
}
/*
* Custom sidebar: park sibling chrome while `.drilling` is set.
* SEQTA strips/restores `.active` on every nested leaf click; keying only off
* `:has(.active)` briefly undoes the translate and the 0.3s transform
* transition makes that look like the open animation flashing again.
*
* Scope carefully only root siblings (and nested-folder siblings via
* `.sub:has(.sub)`), never the visible leaf rows in the current panel.
*/
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling > li::before,
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling > li::after,
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling > li > label,
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling > li > svg {
transform: translateX(-320px) !important;
animation: none !important;
transition: none !important;
#menu ul > li > label,
#menu ul > li > svg {
transition: transform 0.3s ease;
}
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling .sub:has(.sub) > ul > li::before,
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling .sub:has(.sub) > ul > li::after,
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling .sub:has(.sub) > ul > li > label,
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling .sub:has(.sub) > ul > li > svg {
transform: translateX(-320px) !important;
animation: none !important;
transition: none !important;
#menu .sub {
transition: transform 0.3s ease, left 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling {
overflow: hidden !important;
#menu > ul:has(li.hasChildren.active) > li.active {
background: transparent !important;
}
#menu li.hasChildren:not(.active) li .sub {
display: none; // improves performance
display: none;
}
.sub .sub {
@@ -64,27 +45,6 @@
display: none !important;
}
#menu ul > li > label,
#menu ul > li > svg {
transition: transform 0.3s ease;
}
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling > li > label,
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling > li > svg,
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling .sub:has(.sub) > ul > li > label,
#menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling .sub:has(.sub) > ul > li > svg {
transition: none !important;
}
#menu .sub {
transition: transform 0.3s ease, left 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
#menu > ul:has(li.hasChildren.active) > li.active {
background: transparent !important;
}
/* Icon-only collapsed: submenu slides over narrow icons */
body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul:has(li.hasChildren.active) > li::before,
body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul ul:has(li.hasChildren.active) > li::before,
body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul:has(li.hasChildren.active) > li > label,
@@ -94,12 +54,92 @@ body.icon-only-sidebar:not(:has(#menu li.hasChildren.active)) #menu > ul ul:has(
transform: translateX(-70px);
}
body.icon-only-sidebar #menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling > li::before,
body.icon-only-sidebar #menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling > li > label,
body.icon-only-sidebar #menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling > li > svg,
body.icon-only-sidebar #menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling .sub:has(.sub) > ul > li::before,
body.icon-only-sidebar #menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling .sub:has(.sub) > ul > li > label,
body.icon-only-sidebar #menu.bsplus-custom-sidebar #bsplus-sidebar-root.drilling .sub:has(.sub) > ul > li > svg {
transform: translateX(-70px) !important;
transition: none !important;
/*
* Custom sidebar drill slides use `.drilling` / `.drill-entering` / `.drill-returning`,
* not `:has(.active)` (SEQTA strips `.active` on nested clicks).
*/
#menu.bsplus-custom-sidebar #bsplus-sidebar-root {
--bsplus-slide-off: -320px;
.sub {
transition: none !important;
}
&.drilling {
overflow: hidden !important;
&:not(.drill-returning) > li::before,
&:not(.drill-returning) > li::after,
&:not(.drill-returning) > li > label,
&:not(.drill-returning) > li > svg,
&:not(.drill-returning) .sub:has(.sub) > ul > li::before,
&:not(.drill-returning) .sub:has(.sub) > ul > li::after,
&:not(.drill-returning) .sub:has(.sub) > ul > li > label,
&:not(.drill-returning) .sub:has(.sub) > ul > li > svg {
transform: translateX(var(--bsplus-slide-off)) !important;
animation: none !important;
}
&:not(.drill-entering):not(.drill-returning) {
> li::before,
> li::after,
> li > label,
> li > svg,
.sub:has(.sub) > ul > li::before,
.sub:has(.sub) > ul > li::after,
.sub:has(.sub) > ul > li > label,
.sub:has(.sub) > ul > li > svg {
transition: none !important;
}
.sub {
transform: translateX(0) !important;
opacity: 1 !important;
}
}
.sub:has(.sub) > .nav > .back {
display: none !important;
}
}
&.drill-entering .sub.bsplus-sub-enter {
animation: bsplus-submenu-enter 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}
&.drill-returning:not(.drilling) > li::before,
&.drill-returning:not(.drilling) > li::after,
&.drill-returning:not(.drilling) > li > label,
&.drill-returning:not(.drilling) > li > svg,
&.drilling.drill-returning > li.hasChildren > .sub > ul > li::before,
&.drilling.drill-returning > li.hasChildren > .sub > ul > li::after,
&.drilling.drill-returning > li.hasChildren > .sub > ul > li > label,
&.drilling.drill-returning > li.hasChildren > .sub > ul > li > svg {
transition: none !important;
animation: bsplus-sidebar-return 0.32s cubic-bezier(0.22, 1, 0.36, 1) both !important;
}
}
body.icon-only-sidebar #menu.bsplus-custom-sidebar #bsplus-sidebar-root {
--bsplus-slide-off: -70px;
}
@keyframes bsplus-sidebar-return {
from {
transform: translateX(var(--bsplus-slide-off, -320px));
}
to {
transform: translateX(0);
}
}
@keyframes bsplus-submenu-enter {
from {
opacity: 0.35;
transform: translateX(28px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
+4 -1
View File
@@ -195,6 +195,8 @@
class="logo-link bsplus-sidebar-list"
class:noscroll={sidebarState.isDrilling}
class:drilling={sidebarState.isDrilling}
class:drill-entering={sidebarState.enterFrameKey != null}
class:drill-returning={sidebarState.drillReturning}
class:compact={sidebarState.compact}
class:edit-mode={sidebarState.editMode}
class:is-sorting={dragging}
@@ -283,7 +285,8 @@
class="sub"
class:bsplus-sub-enter={sidebarState.enterFrameKey === current.frame.key}
onanimationend={(e) => {
if (e.target === e.currentTarget) {
if (e.target !== e.currentTarget) return;
if (sidebarState.enterFrameKey === current.frame.key) {
sidebarState.clearEnterFrame(current.frame.key);
}
}}
+1 -1
View File
@@ -101,7 +101,7 @@ function scheduleSync() {
* never see them. Opening Goals/Folios via SEQTA + our drill UI freezes the tab.
*/
function onCustomSidebarCaptureClick(event: MouseEvent) {
if (!menuEl || sidebarState.editMode) return;
if (!menuEl || sidebarState.editMode || sidebarState.drillReturning) return;
const root = document.getElementById(ROOT_ID);
const target = event.target;
+23 -5
View File
@@ -184,8 +184,9 @@ class SidebarState {
activePath = $state("");
editMode = $state(false);
iconOnly = $state(false);
/** Frame key whose `.sub` should play the one-shot enter animation. */
/** Folder key playing the one-shot panel enter animation. */
enterFrameKey = $state<string | null>(null);
drillReturning = $state(false);
visibleRootItems = $derived(
filterVisible(orderItems(this.items, settingsState.menuorder ?? [])),
@@ -262,10 +263,11 @@ class SidebarState {
}
openFolder(item: SidebarItem, menu?: HTMLElement) {
if (!item.hasChildren) return;
// Ignore duplicate opens (double-firing click / label + li).
if (!item.hasChildren || this.drillReturning) return;
if (this.drillStack.at(-1)?.key === item.key) return;
this.drillReturning = false;
const frame: SidebarDrillFrame = {
key: item.key,
label: item.label,
@@ -293,14 +295,23 @@ class SidebarState {
}
goBack() {
if (!this.drillStack.length) return;
if (!this.drillStack.length || this.drillReturning) return;
this.enterFrameKey = null;
this.drillStack = this.drillStack.slice(0, -1);
resetSidebarScroll();
if (settingsState.animations !== true) return;
this.drillReturning = true;
window.setTimeout(() => {
this.drillReturning = false;
}, 360);
}
resetDrill() {
this.enterFrameKey = null;
this.drillReturning = false;
this.drillStack = [];
resetSidebarScroll();
}
@@ -350,7 +361,7 @@ class SidebarState {
}
activateItem(item: SidebarItem, menu: HTMLElement) {
if (this.editMode) return;
if (this.editMode || this.drillReturning) return;
if (item.hasChildren) {
this.openFolder(item, menu);
@@ -360,6 +371,13 @@ class SidebarState {
this.activeKey = item.key;
if (item.path) this.activePath = item.path;
// Already inside this folder — never replay the one-shot enter animation when
// SEQTA rewrites `.active` on the route change.
if (this.isDrilling) {
this.enterFrameKey = null;
restoreCustomMenuActive();
}
const native = findNativeMenuEntry(menu, item);
if (native) {
// Clear native drill once only — repeating clearNativeDrillActive fights