@spaethtech/svelte-ui 0.7.1-dev.51.cb076c4 → 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
- // The corresponding "label is currently visible" check used to disable
475
- // tooltips at the expanded breakpoint. We don't have direct access to
476
- // the media query here without an effect, so we always pass the tooltip
477
- // the label text and let the tooltip's own `disabled` option be flipped
478
- // off by the popout-open state; when the label is on-screen the user
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 && currentPath.startsWith(item.link)) return true;
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 && currentPath.startsWith(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: "right",
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: "right", disabled: openId === opts.id }}
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: "right" }}
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spaethtech/svelte-ui",
3
- "version": "0.7.1-dev.51.cb076c4",
3
+ "version": "0.8.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/spaethtech/svelte-ui.git"