@tangle-network/ui 11.40.1 → 11.40.2

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @tangle-network/ui
2
2
 
3
+ ## 11.40.2
4
+
5
+ ### Patch Changes
6
+
7
+ - c22e1b3: PageShell `measure="settings"` re-centres the phone section strip only when the current link changes. Scrolling or tapping an arrow to the first or last section now sticks instead of snapping back to the current link.
8
+
3
9
  ## 11.40.1
4
10
 
5
11
  ### Patch Changes
@@ -6,27 +6,44 @@ import { jsx, jsxs } from "react/jsx-runtime";
6
6
  /**
7
7
  * Centre the current link of a horizontally scrolling nav, so the links on
8
8
  * both sides show that the strip scrolls. It moves the strip, never the page.
9
+ * It centres when the current link changes (or first becomes reachable), never
10
+ * on scroll, arrow taps or re-renders that keep the same current link, so a
11
+ * user who scrolls to the first or last section stays there. Resize and font
12
+ * swaps re-centre only until the user first scrolls the strip.
9
13
  */
10
14
  function useCurrentLinkInView(ref, enabled) {
11
15
  React.useEffect(() => {
12
16
  const nav = ref.current;
13
17
  if (!enabled || !nav) return;
14
- const reveal = () => {
18
+ let centredKey = null;
19
+ let userScrolled = false;
20
+ const reveal = (layoutOnly) => {
15
21
  if (nav.scrollWidth <= nav.clientWidth) return;
16
22
  const current = nav.querySelector("[aria-current=\"page\"]");
17
23
  if (!current) return;
24
+ const key = current.getAttribute("href") ?? current.textContent ?? "";
25
+ if (key !== centredKey) userScrolled = false;
26
+ else if (!layoutOnly || userScrolled) return;
27
+ centredKey = key;
18
28
  const strip = nav.getBoundingClientRect();
19
29
  const link = current.getBoundingClientRect();
20
30
  nav.scrollLeft += link.left + link.width / 2 - (strip.left + strip.width / 2);
21
31
  };
32
+ const onUser = () => {
33
+ userScrolled = true;
34
+ };
35
+ nav.addEventListener("pointerdown", onUser, { passive: true });
36
+ nav.addEventListener("touchstart", onUser, { passive: true });
37
+ nav.addEventListener("wheel", onUser, { passive: true });
38
+ nav.addEventListener("keydown", onUser);
22
39
  let live = true;
23
- reveal();
40
+ reveal(false);
24
41
  document.fonts?.ready.then(() => {
25
- if (live) reveal();
42
+ if (live) reveal(true);
26
43
  });
27
- const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(reveal);
44
+ const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => reveal(true));
28
45
  resize?.observe(nav);
29
- const links = new MutationObserver(reveal);
46
+ const links = new MutationObserver(() => reveal(false));
30
47
  links.observe(nav, {
31
48
  subtree: true,
32
49
  childList: true,
@@ -38,6 +55,10 @@ function useCurrentLinkInView(ref, enabled) {
38
55
  live = false;
39
56
  resize?.disconnect();
40
57
  links.disconnect();
58
+ nav.removeEventListener("pointerdown", onUser);
59
+ nav.removeEventListener("touchstart", onUser);
60
+ nav.removeEventListener("wheel", onUser);
61
+ nav.removeEventListener("keydown", onUser);
41
62
  };
42
63
  }, [ref, enabled]);
43
64
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tangle-network/ui",
3
- "version": "11.40.1",
3
+ "version": "11.40.2",
4
4
  "description": "Generic React UI components for Tangle products — primitives, chat, run, files, editor, markdown.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -165,7 +165,7 @@
165
165
  "peerDependencies": {
166
166
  "@hocuspocus/provider": ">=2.15.0 <5.0.0",
167
167
  "@nanostores/react": ">=0.8.4 <3.0.0",
168
- "@tangle-network/brand": "^1.26.0",
168
+ "@tangle-network/brand": "^1.26.1",
169
169
  "@tiptap/core": ">=3.20.4 <4.0.0",
170
170
  "@tiptap/extension-collaboration": ">=3.20.4 <4.0.0",
171
171
  "@tiptap/extension-collaboration-caret": ">=3.7.0 <4.0.0",
@@ -30,34 +30,53 @@ export interface PageShellProps {
30
30
  /**
31
31
  * Centre the current link of a horizontally scrolling nav, so the links on
32
32
  * both sides show that the strip scrolls. It moves the strip, never the page.
33
+ * It centres when the current link changes (or first becomes reachable), never
34
+ * on scroll, arrow taps or re-renders that keep the same current link, so a
35
+ * user who scrolls to the first or last section stays there. Resize and font
36
+ * swaps re-centre only until the user first scrolls the strip.
33
37
  */
34
38
  function useCurrentLinkInView(ref: React.RefObject<HTMLElement | null>, enabled: boolean) {
35
39
  React.useEffect(() => {
36
40
  const nav = ref.current;
37
41
  if (!enabled || !nav) return;
38
- const reveal = () => {
42
+ let centredKey: string | null = null;
43
+ let userScrolled = false;
44
+ const reveal = (layoutOnly: boolean) => {
39
45
  if (nav.scrollWidth <= nav.clientWidth) return;
40
46
  const current = nav.querySelector<HTMLElement>('[aria-current="page"]');
41
47
  if (!current) return;
48
+ const key = current.getAttribute("href") ?? current.textContent ?? "";
49
+ if (key !== centredKey) userScrolled = false;
50
+ else if (!layoutOnly || userScrolled) return;
51
+ centredKey = key;
42
52
  const strip = nav.getBoundingClientRect();
43
53
  const link = current.getBoundingClientRect();
44
54
  nav.scrollLeft += link.left + link.width / 2 - (strip.left + strip.width / 2);
45
55
  };
56
+ const onUser = () => { userScrolled = true; };
57
+ nav.addEventListener("pointerdown", onUser, { passive: true });
58
+ nav.addEventListener("touchstart", onUser, { passive: true });
59
+ nav.addEventListener("wheel", onUser, { passive: true });
60
+ nav.addEventListener("keydown", onUser);
46
61
  let live = true;
47
- reveal();
62
+ reveal(false);
48
63
  // A web font swap widens the tabs without resizing the strip, so the
49
64
  // resize observer alone would leave the current tab where it was.
50
- void document.fonts?.ready.then(() => { if (live) reveal(); });
51
- const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(reveal);
65
+ void document.fonts?.ready.then(() => { if (live) reveal(true); });
66
+ const resize = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(() => reveal(true));
52
67
  resize?.observe(nav);
53
- // Links that a caller replaces (sections loaded later, a count that
54
- // changes) move the current tab as surely as a new aria-current does.
55
- const links = new MutationObserver(reveal);
68
+ // Links that a caller replaces (sections loaded later, a new aria-current)
69
+ // can change the current link; the same current link is left where it is.
70
+ const links = new MutationObserver(() => reveal(false));
56
71
  links.observe(nav, { subtree: true, childList: true, characterData: true, attributes: true, attributeFilter: ["aria-current"] });
57
72
  return () => {
58
73
  live = false;
59
74
  resize?.disconnect();
60
75
  links.disconnect();
76
+ nav.removeEventListener("pointerdown", onUser);
77
+ nav.removeEventListener("touchstart", onUser);
78
+ nav.removeEventListener("wheel", onUser);
79
+ nav.removeEventListener("keydown", onUser);
61
80
  };
62
81
  }, [ref, enabled]);
63
82
  }