@spaethtech/svelte-ui 0.7.1-dev.50.4b4666d → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -471,16 +471,23 @@
|
|
|
471
471
|
// `inline`) so its SVG stays centered; otherwise the same logic.
|
|
472
472
|
const labelClass = $derived(labelsVisible ? "inline" : "hidden");
|
|
473
473
|
const chevronVisibility = $derived(labelsVisible ? "inline-flex" : "hidden");
|
|
474
|
-
//
|
|
475
|
-
//
|
|
476
|
-
// the
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
// has no reason to hover for it anyway.
|
|
474
|
+
// Row tooltips exist ONLY to surface a label the rail can't show. That's a single case: a collapsed
|
|
475
|
+
// `icon` rail that does NOT hover-expand. When labels are already visible (`expanded`/`drawer`/
|
|
476
|
+
// `stepped`), or the rail hover-expands to reveal them (`iconHoverExpand`), the tooltip is redundant
|
|
477
|
+
// noise — suppress it. (Per-row `disabled` also drops the tooltip while that parent's popout is open.)
|
|
478
|
+
const tooltipSuppressed = $derived(labelsVisible || hoverExpandActive);
|
|
480
479
|
|
|
481
480
|
// ── Active state ────────────────────────────────────────────────
|
|
481
|
+
// Match on a SEGMENT boundary, not a raw prefix: a link is active when the current path equals it
|
|
482
|
+
// or is nested under it (`/x` matches `/x/y` but not `/xyz`). `/` (a Home/root link) matches ONLY
|
|
483
|
+
// the exact root — otherwise it would light up on every page (every path starts with "/").
|
|
484
|
+
function pathMatches(link: string, path: string): boolean {
|
|
485
|
+
if (link === path) return true;
|
|
486
|
+
if (link === "/") return false;
|
|
487
|
+
return path.startsWith(link.endsWith("/") ? link : link + "/");
|
|
488
|
+
}
|
|
482
489
|
function isItemActive(item: SideBarMenuItem): boolean {
|
|
483
|
-
if (item.link &&
|
|
490
|
+
if (item.link && pathMatches(item.link, currentPath)) return true;
|
|
484
491
|
if (item.children?.length) return item.children.some(isItemActive);
|
|
485
492
|
return false;
|
|
486
493
|
}
|
|
@@ -654,7 +661,7 @@
|
|
|
654
661
|
<!-- Single row template — used by items / bottomItems and the popout. -->
|
|
655
662
|
{#snippet row(item: SideBarMenuItem, opts: { id?: ItemId; isPopoutChild?: boolean })}
|
|
656
663
|
{@const active = isItemActive(item)}
|
|
657
|
-
{@const ownActive = !!item.link &&
|
|
664
|
+
{@const ownActive = !!item.link && pathMatches(item.link, currentPath)}
|
|
658
665
|
{@const hasChildren = (item.children?.length ?? 0) > 0}
|
|
659
666
|
{@const isParent = (hasChildren || !!item.popout) && !opts.isPopoutChild}
|
|
660
667
|
{@const hasLink = !!item.link}
|
|
@@ -673,8 +680,8 @@
|
|
|
673
680
|
aria-controls={isParent && isOpen ? popoutId : undefined}
|
|
674
681
|
use:tooltip={{
|
|
675
682
|
text: item.label,
|
|
676
|
-
side:
|
|
677
|
-
disabled: isParent && opts.id ? openId === opts.id : false,
|
|
683
|
+
side: popoutSide,
|
|
684
|
+
disabled: tooltipSuppressed || (isParent && opts.id ? openId === opts.id : false),
|
|
678
685
|
}}
|
|
679
686
|
onmouseenter={isParent && opts.id && !isStepped ? () => openOnHover(opts.id!) : undefined}
|
|
680
687
|
onmouseleave={isParent && opts.id && !isStepped ? scheduleClose : undefined}
|
|
@@ -710,7 +717,7 @@
|
|
|
710
717
|
aria-haspopup="menu"
|
|
711
718
|
aria-expanded={openId === opts.id}
|
|
712
719
|
aria-controls={isOpen ? popoutId : undefined}
|
|
713
|
-
use:tooltip={{ text: item.label, side:
|
|
720
|
+
use:tooltip={{ text: item.label, side: popoutSide, disabled: tooltipSuppressed || openId === opts.id }}
|
|
714
721
|
onmouseenter={!isStepped ? () => openOnHover(opts.id!) : undefined}
|
|
715
722
|
onmouseleave={!isStepped ? scheduleClose : undefined}
|
|
716
723
|
onclick={() => (isStepped ? stepIntoChildren(item) : toggleOnClick(opts.id!))}
|
|
@@ -724,7 +731,7 @@
|
|
|
724
731
|
type="button"
|
|
725
732
|
use:refIntoMap={opts.id}
|
|
726
733
|
class="{rowClass} text-left"
|
|
727
|
-
use:tooltip={{ text: item.label, side:
|
|
734
|
+
use:tooltip={{ text: item.label, side: popoutSide, disabled: tooltipSuppressed }}
|
|
728
735
|
onclick={(e) => {
|
|
729
736
|
item.onclick!(e);
|
|
730
737
|
closeAfterNav();
|
|
@@ -108,15 +108,34 @@ export function anchored(node, params) {
|
|
|
108
108
|
const onScrollResize = () => place();
|
|
109
109
|
window.addEventListener("scroll", onScrollResize, { passive: true, capture: true });
|
|
110
110
|
window.addEventListener("resize", onScrollResize, { passive: true });
|
|
111
|
+
// Re-place when the ANCHOR (or the floating box) changes size — not just on window resize. A CSS
|
|
112
|
+
// transition on the anchor (e.g. a sidebar rail animating its width open) moves the edge we're
|
|
113
|
+
// glued to; without this the box stays pinned to the anchor's size at open-time. Observing `node`
|
|
114
|
+
// too keeps us placed if the content reflows.
|
|
115
|
+
let ro;
|
|
116
|
+
function observe(anchor) {
|
|
117
|
+
ro?.disconnect();
|
|
118
|
+
if (typeof ResizeObserver === "undefined")
|
|
119
|
+
return;
|
|
120
|
+
ro ??= new ResizeObserver(() => place());
|
|
121
|
+
if (anchor)
|
|
122
|
+
ro.observe(anchor);
|
|
123
|
+
ro.observe(node);
|
|
124
|
+
}
|
|
125
|
+
observe(params.anchor);
|
|
111
126
|
place();
|
|
112
127
|
return {
|
|
113
128
|
update(next) {
|
|
129
|
+
const anchorChanged = next.anchor !== current.anchor;
|
|
114
130
|
current = next;
|
|
131
|
+
if (anchorChanged)
|
|
132
|
+
observe(next.anchor);
|
|
115
133
|
place();
|
|
116
134
|
},
|
|
117
135
|
destroy() {
|
|
118
136
|
window.removeEventListener("scroll", onScrollResize, { capture: true });
|
|
119
137
|
window.removeEventListener("resize", onScrollResize);
|
|
138
|
+
ro?.disconnect();
|
|
120
139
|
},
|
|
121
140
|
};
|
|
122
141
|
}
|