@readme/markdown 13.8.0 → 13.8.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/README.md CHANGED
@@ -125,9 +125,29 @@ To make changes to the RDMD engine locally, run the local development server. Cl
125
125
 
126
126
  If you make changes to the docs or how the markdown is rendered, you may need to update the visual regression snapshots by running `make updateSnapshot`. Running these browser tests requires `docker`. Follow the docker [install instructions for mac](https://docs.docker.com/docker-for-mac/install/). You may want to increase the [memory usage](https://docs.docker.com/docker-for-mac/#resources). If you have not already, you'll need to create an account for `docker hub` and [sign-in locally](https://docs.docker.com/docker-for-mac/#docker-hub).
127
127
 
128
+ ### Linking Changes to the Main Repo
129
+
130
+ To test local changes in the main readme app, use `npm pack` (not `npm link`) and clear the webpack cache:
131
+
132
+ **In this repo, run:**
133
+
134
+ ```
135
+ npm run build && npm pack
136
+ ```
137
+
138
+ **In the main app repo, run:**
139
+
140
+ ```
141
+ npm install ../markdown/readme-markdown-<version>.tgz
142
+ rm -rf webpack-cache
143
+ make start
144
+ ```
145
+
146
+ > **Note:** You must clear `webpack-cache` after every reinstall or the app will serve the old version.
147
+
128
148
  ### Linking Changes to Storybook
129
149
 
130
- There's currently some `babel` issues that prevent `npm link`ing to our main repo. `npm link`ing does work with our storybook app.
150
+ `npm link` works with the storybook app:
131
151
 
132
152
  **In this repo, run:**
133
153
 
@@ -17,6 +17,26 @@ function buildLinkMap(nav: HTMLElement) {
17
17
  return map;
18
18
  }
19
19
 
20
+ const VISIBLE_RATIO = 0.4;
21
+ /** Tolerance for subpixel rounding when checking if scrolled to the bottom. */
22
+ const SCROLL_BOTTOM_TOLERANCE = 1;
23
+
24
+ /**
25
+ * Walk up the DOM to find the nearest scrollable ancestor.
26
+ * Falls back to `window` when the page itself scrolls.
27
+ */
28
+ function getScrollParent(el: HTMLElement): HTMLElement | Window {
29
+ let parent = el.parentElement;
30
+ while (parent) {
31
+ const { overflow, overflowY } = getComputedStyle(parent);
32
+ if (/(auto|scroll)/.test(overflow + overflowY) && parent.scrollHeight > parent.clientHeight) {
33
+ return parent;
34
+ }
35
+ parent = parent.parentElement;
36
+ }
37
+ return window;
38
+ }
39
+
20
40
  /**
21
41
  * Watches headings in the viewport and toggles `active` on the
22
42
  * corresponding TOC links so the reader always knows where they are.
@@ -44,7 +64,16 @@ function useScrollHighlight(navRef: React.RefObject<HTMLElement | null>) {
44
64
  if (headings.length === 0) return undefined;
45
65
 
46
66
  let activeId: string | null = null;
67
+ let clickLocked = false;
47
68
  const visible = new Set<string>();
69
+ const scrollParent = getScrollParent(headings[0]);
70
+
71
+ const isAtBottom = () => {
72
+ if (scrollParent instanceof Window) {
73
+ return window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
74
+ }
75
+ return scrollParent.scrollTop + scrollParent.clientHeight >= scrollParent.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
76
+ };
48
77
 
49
78
  const activate = (id: string | null) => {
50
79
  if (id === activeId) return;
@@ -66,22 +95,69 @@ function useScrollHighlight(navRef: React.RefObject<HTMLElement | null>) {
66
95
  }
67
96
  };
68
97
 
98
+ const updateActive = () => {
99
+ if (clickLocked) return;
100
+
101
+ if (isAtBottom()) {
102
+ activate(headings[headings.length - 1].id);
103
+ return;
104
+ }
105
+
106
+ const topmost = headings.find(el => visible.has(el.id));
107
+ if (topmost) activate(topmost.id);
108
+ };
109
+
69
110
  const observer = new IntersectionObserver(
70
111
  entries => {
71
112
  entries.forEach(e => {
72
113
  if (e.isIntersecting) visible.add(e.target.id);
73
114
  else visible.delete(e.target.id);
74
115
  });
75
- // Highlight the topmost visible heading; if none are visible,
76
- // keep the last active one so the user still has context.
77
- const topmost = headings.find(el => visible.has(el.id));
78
- if (topmost) activate(topmost.id);
116
+ updateActive();
79
117
  },
80
- { rootMargin: '0px 0px -60% 0px', threshold: 0 },
118
+ { rootMargin: `0px 0px -${(1 - VISIBLE_RATIO) * 100}% 0px`, threshold: 0 },
81
119
  );
82
120
 
121
+ // Check on scroll so bottom-of-page detection works even when
122
+ // no headings are crossing the intersection boundary.
123
+ const scrollTarget = scrollParent instanceof Window ? window : scrollParent;
124
+ const onScroll = () => { updateActive(); };
125
+
126
+ // Click a ToC link → immediately activate it, suppress the observer
127
+ // until the smooth scroll finishes, then hand control back.
128
+ const onClick = (e: MouseEvent) => {
129
+ const anchor = (e.target as HTMLElement).closest?.('a[href^="#"]');
130
+ if (!anchor) return;
131
+ const id = decodeURIComponent(anchor.getAttribute('href')!.slice(1));
132
+ if (!linkMap.has(id)) return;
133
+
134
+ e.preventDefault();
135
+ activate(id);
136
+ clickLocked = true;
137
+
138
+ const unlock = () => { clickLocked = false; };
139
+ scrollTarget.addEventListener('scrollend', unlock, { once: true });
140
+
141
+ document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
142
+ };
143
+
83
144
  headings.forEach(el => { observer.observe(el); });
84
- return () => { observer.disconnect(); };
145
+ scrollTarget.addEventListener('scroll', onScroll, { passive: true });
146
+ nav.addEventListener('click', onClick);
147
+
148
+ // Set initial active state for the first heading visible in the viewport,
149
+ // falling back to the first heading if none is in the observation zone yet.
150
+ const initialHeading = headings.find(el => {
151
+ const rect = el.getBoundingClientRect();
152
+ return rect.top >= 0 && rect.top < window.innerHeight * VISIBLE_RATIO;
153
+ }) || headings[0];
154
+ activate(initialHeading.id);
155
+
156
+ return () => {
157
+ observer.disconnect();
158
+ scrollTarget.removeEventListener('scroll', onScroll);
159
+ nav.removeEventListener('click', onClick);
160
+ };
85
161
  }, [navRef, linkCount]);
86
162
  }
87
163
 
package/dist/main.js CHANGED
@@ -12087,6 +12087,24 @@ function buildLinkMap(nav) {
12087
12087
  });
12088
12088
  return map;
12089
12089
  }
12090
+ const VISIBLE_RATIO = 0.4;
12091
+ /** Tolerance for subpixel rounding when checking if scrolled to the bottom. */
12092
+ const SCROLL_BOTTOM_TOLERANCE = 1;
12093
+ /**
12094
+ * Walk up the DOM to find the nearest scrollable ancestor.
12095
+ * Falls back to `window` when the page itself scrolls.
12096
+ */
12097
+ function getScrollParent(el) {
12098
+ let parent = el.parentElement;
12099
+ while (parent) {
12100
+ const { overflow, overflowY } = getComputedStyle(parent);
12101
+ if (/(auto|scroll)/.test(overflow + overflowY) && parent.scrollHeight > parent.clientHeight) {
12102
+ return parent;
12103
+ }
12104
+ parent = parent.parentElement;
12105
+ }
12106
+ return window;
12107
+ }
12090
12108
  /**
12091
12109
  * Watches headings in the viewport and toggles `active` on the
12092
12110
  * corresponding TOC links so the reader always knows where they are.
@@ -12113,7 +12131,15 @@ function useScrollHighlight(navRef) {
12113
12131
  if (headings.length === 0)
12114
12132
  return undefined;
12115
12133
  let activeId = null;
12134
+ let clickLocked = false;
12116
12135
  const visible = new Set();
12136
+ const scrollParent = getScrollParent(headings[0]);
12137
+ const isAtBottom = () => {
12138
+ if (scrollParent instanceof Window) {
12139
+ return window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
12140
+ }
12141
+ return scrollParent.scrollTop + scrollParent.clientHeight >= scrollParent.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
12142
+ };
12117
12143
  const activate = (id) => {
12118
12144
  if (id === activeId)
12119
12145
  return;
@@ -12133,6 +12159,17 @@ function useScrollHighlight(navRef) {
12133
12159
  }
12134
12160
  }
12135
12161
  };
12162
+ const updateActive = () => {
12163
+ if (clickLocked)
12164
+ return;
12165
+ if (isAtBottom()) {
12166
+ activate(headings[headings.length - 1].id);
12167
+ return;
12168
+ }
12169
+ const topmost = headings.find(el => visible.has(el.id));
12170
+ if (topmost)
12171
+ activate(topmost.id);
12172
+ };
12136
12173
  const observer = new IntersectionObserver(entries => {
12137
12174
  entries.forEach(e => {
12138
12175
  if (e.isIntersecting)
@@ -12140,14 +12177,43 @@ function useScrollHighlight(navRef) {
12140
12177
  else
12141
12178
  visible.delete(e.target.id);
12142
12179
  });
12143
- // Highlight the topmost visible heading; if none are visible,
12144
- // keep the last active one so the user still has context.
12145
- const topmost = headings.find(el => visible.has(el.id));
12146
- if (topmost)
12147
- activate(topmost.id);
12148
- }, { rootMargin: '0px 0px -60% 0px', threshold: 0 });
12180
+ updateActive();
12181
+ }, { rootMargin: `0px 0px -${(1 - VISIBLE_RATIO) * 100}% 0px`, threshold: 0 });
12182
+ // Check on scroll so bottom-of-page detection works even when
12183
+ // no headings are crossing the intersection boundary.
12184
+ const scrollTarget = scrollParent instanceof Window ? window : scrollParent;
12185
+ const onScroll = () => { updateActive(); };
12186
+ // Click a ToC link → immediately activate it, suppress the observer
12187
+ // until the smooth scroll finishes, then hand control back.
12188
+ const onClick = (e) => {
12189
+ const anchor = e.target.closest?.('a[href^="#"]');
12190
+ if (!anchor)
12191
+ return;
12192
+ const id = decodeURIComponent(anchor.getAttribute('href').slice(1));
12193
+ if (!linkMap.has(id))
12194
+ return;
12195
+ e.preventDefault();
12196
+ activate(id);
12197
+ clickLocked = true;
12198
+ const unlock = () => { clickLocked = false; };
12199
+ scrollTarget.addEventListener('scrollend', unlock, { once: true });
12200
+ document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
12201
+ };
12149
12202
  headings.forEach(el => { observer.observe(el); });
12150
- return () => { observer.disconnect(); };
12203
+ scrollTarget.addEventListener('scroll', onScroll, { passive: true });
12204
+ nav.addEventListener('click', onClick);
12205
+ // Set initial active state for the first heading visible in the viewport,
12206
+ // falling back to the first heading if none is in the observation zone yet.
12207
+ const initialHeading = headings.find(el => {
12208
+ const rect = el.getBoundingClientRect();
12209
+ return rect.top >= 0 && rect.top < window.innerHeight * VISIBLE_RATIO;
12210
+ }) || headings[0];
12211
+ activate(initialHeading.id);
12212
+ return () => {
12213
+ observer.disconnect();
12214
+ scrollTarget.removeEventListener('scroll', onScroll);
12215
+ nav.removeEventListener('click', onClick);
12216
+ };
12151
12217
  }, [navRef, linkCount]);
12152
12218
  }
12153
12219
  function TableOfContents({ children }) {
@@ -98719,25 +98785,31 @@ function makeVariableNode(varName, rawValue) {
98719
98785
  }
98720
98786
  /**
98721
98787
  * A remark plugin that parses {user.<field>} patterns from text nodes and
98722
- * mdxTextExpression nodes, creating Variable nodes for runtime resolution.
98788
+ * mdx expression nodes, creating Variable nodes for runtime resolution.
98723
98789
  *
98724
- * Handles both:
98790
+ * Handles:
98725
98791
  * - `text` nodes: when safeMode is true or after expression evaluation
98726
98792
  * - `mdxTextExpression` nodes: when mdxExpression has parsed {user.*} before evaluation
98793
+ * - `mdxFlowExpression` nodes: when {user.*} appears on its own line (e.g. inside JSX table cells)
98727
98794
  *
98728
98795
  * Supports any user field: name, email, email_verified, exp, iat, etc.
98729
98796
  */
98797
+ function visitExpressionNode(node, index, parent) {
98798
+ if (index === undefined || !parent)
98799
+ return;
98800
+ const wrapped = `{${(node.value ?? '').trim()}}`;
98801
+ const matches = [...wrapped.matchAll(USER_VAR_REGEX)];
98802
+ if (matches.length !== 1)
98803
+ return;
98804
+ const varName = matches[0][1] || matches[0][2];
98805
+ parent.children.splice(index, 1, makeVariableNode(varName, wrapped));
98806
+ }
98730
98807
  const variablesTextTransformer = () => tree => {
98731
- // Handle mdxTextExpression nodes (e.g. {user.name} parsed by mdxExpression)
98732
98808
  visit(tree, 'mdxTextExpression', (node, index, parent) => {
98733
- if (index === undefined || !parent)
98734
- return;
98735
- const wrapped = `{${(node.value ?? '').trim()}}`; // Wrap the expression value in {} to match the USER_VAR_REGEX pattern
98736
- const matches = [...wrapped.matchAll(USER_VAR_REGEX)];
98737
- if (matches.length !== 1)
98738
- return;
98739
- const varName = matches[0][1] || matches[0][2];
98740
- parent.children.splice(index, 1, makeVariableNode(varName, wrapped));
98809
+ visitExpressionNode(node, index, parent);
98810
+ });
98811
+ visit(tree, 'mdxFlowExpression', (node, index, parent) => {
98812
+ visitExpressionNode(node, index, parent);
98741
98813
  });
98742
98814
  visit(tree, 'text', (node, index, parent) => {
98743
98815
  if (index === undefined || !parent)
package/dist/main.node.js CHANGED
@@ -19670,14 +19670,14 @@ function getWindowScrollBarX(element) {
19670
19670
  }
19671
19671
  ;// ./node_modules/@popperjs/core/lib/dom-utils/getComputedStyle.js
19672
19672
 
19673
- function getComputedStyle(element) {
19673
+ function getComputedStyle_getComputedStyle(element) {
19674
19674
  return getWindow(element).getComputedStyle(element);
19675
19675
  }
19676
19676
  ;// ./node_modules/@popperjs/core/lib/dom-utils/isScrollParent.js
19677
19677
 
19678
19678
  function isScrollParent(element) {
19679
19679
  // Firefox wants us to check `-x` and `-y` variations as well
19680
- var _getComputedStyle = getComputedStyle(element),
19680
+ var _getComputedStyle = getComputedStyle_getComputedStyle(element),
19681
19681
  overflow = _getComputedStyle.overflow,
19682
19682
  overflowX = _getComputedStyle.overflowX,
19683
19683
  overflowY = _getComputedStyle.overflowY;
@@ -19849,7 +19849,7 @@ function isTableElement(element) {
19849
19849
 
19850
19850
  function getTrueOffsetParent(element) {
19851
19851
  if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837
19852
- getComputedStyle(element).position === 'fixed') {
19852
+ getComputedStyle_getComputedStyle(element).position === 'fixed') {
19853
19853
  return null;
19854
19854
  }
19855
19855
 
@@ -19864,7 +19864,7 @@ function getContainingBlock(element) {
19864
19864
 
19865
19865
  if (isIE && isHTMLElement(element)) {
19866
19866
  // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport
19867
- var elementCss = getComputedStyle(element);
19867
+ var elementCss = getComputedStyle_getComputedStyle(element);
19868
19868
 
19869
19869
  if (elementCss.position === 'fixed') {
19870
19870
  return null;
@@ -19878,7 +19878,7 @@ function getContainingBlock(element) {
19878
19878
  }
19879
19879
 
19880
19880
  while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) {
19881
- var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that
19881
+ var css = getComputedStyle_getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that
19882
19882
  // create a containing block.
19883
19883
  // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
19884
19884
 
@@ -19898,11 +19898,11 @@ function getOffsetParent(element) {
19898
19898
  var window = getWindow(element);
19899
19899
  var offsetParent = getTrueOffsetParent(element);
19900
19900
 
19901
- while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') {
19901
+ while (offsetParent && isTableElement(offsetParent) && getComputedStyle_getComputedStyle(offsetParent).position === 'static') {
19902
19902
  offsetParent = getTrueOffsetParent(offsetParent);
19903
19903
  }
19904
19904
 
19905
- if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) {
19905
+ if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle_getComputedStyle(offsetParent).position === 'static')) {
19906
19906
  return window;
19907
19907
  }
19908
19908
 
@@ -20447,7 +20447,7 @@ function mapToStyles(_ref2) {
20447
20447
  if (offsetParent === getWindow(popper)) {
20448
20448
  offsetParent = getDocumentElement(popper);
20449
20449
 
20450
- if (getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {
20450
+ if (getComputedStyle_getComputedStyle(offsetParent).position !== 'static' && position === 'absolute') {
20451
20451
  heightProp = 'scrollHeight';
20452
20452
  widthProp = 'scrollWidth';
20453
20453
  }
@@ -20759,7 +20759,7 @@ function getDocumentRect(element) {
20759
20759
  var x = -winScroll.scrollLeft + getWindowScrollBarX(element);
20760
20760
  var y = -winScroll.scrollTop;
20761
20761
 
20762
- if (getComputedStyle(body || html).direction === 'rtl') {
20762
+ if (getComputedStyle_getComputedStyle(body || html).direction === 'rtl') {
20763
20763
  x += math_max(html.clientWidth, body ? body.clientWidth : 0) - width;
20764
20764
  }
20765
20765
 
@@ -20841,7 +20841,7 @@ function getClientRectFromMixedType(element, clippingParent, strategy) {
20841
20841
 
20842
20842
  function getClippingParents(element) {
20843
20843
  var clippingParents = listScrollParents(getParentNode(element));
20844
- var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle(element).position) >= 0;
20844
+ var canEscapeClipping = ['absolute', 'fixed'].indexOf(getComputedStyle_getComputedStyle(element).position) >= 0;
20845
20845
  var clipperElement = canEscapeClipping && isHTMLElement(element) ? getOffsetParent(element) : element;
20846
20846
 
20847
20847
  if (!isElement(clipperElement)) {
@@ -24683,6 +24683,24 @@ function buildLinkMap(nav) {
24683
24683
  });
24684
24684
  return map;
24685
24685
  }
24686
+ const VISIBLE_RATIO = 0.4;
24687
+ /** Tolerance for subpixel rounding when checking if scrolled to the bottom. */
24688
+ const SCROLL_BOTTOM_TOLERANCE = 1;
24689
+ /**
24690
+ * Walk up the DOM to find the nearest scrollable ancestor.
24691
+ * Falls back to `window` when the page itself scrolls.
24692
+ */
24693
+ function TableOfContents_getScrollParent(el) {
24694
+ let parent = el.parentElement;
24695
+ while (parent) {
24696
+ const { overflow, overflowY } = getComputedStyle(parent);
24697
+ if (/(auto|scroll)/.test(overflow + overflowY) && parent.scrollHeight > parent.clientHeight) {
24698
+ return parent;
24699
+ }
24700
+ parent = parent.parentElement;
24701
+ }
24702
+ return window;
24703
+ }
24686
24704
  /**
24687
24705
  * Watches headings in the viewport and toggles `active` on the
24688
24706
  * corresponding TOC links so the reader always knows where they are.
@@ -24709,7 +24727,15 @@ function useScrollHighlight(navRef) {
24709
24727
  if (headings.length === 0)
24710
24728
  return undefined;
24711
24729
  let activeId = null;
24730
+ let clickLocked = false;
24712
24731
  const visible = new Set();
24732
+ const scrollParent = TableOfContents_getScrollParent(headings[0]);
24733
+ const isAtBottom = () => {
24734
+ if (scrollParent instanceof Window) {
24735
+ return window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
24736
+ }
24737
+ return scrollParent.scrollTop + scrollParent.clientHeight >= scrollParent.scrollHeight - SCROLL_BOTTOM_TOLERANCE;
24738
+ };
24713
24739
  const activate = (id) => {
24714
24740
  if (id === activeId)
24715
24741
  return;
@@ -24729,6 +24755,17 @@ function useScrollHighlight(navRef) {
24729
24755
  }
24730
24756
  }
24731
24757
  };
24758
+ const updateActive = () => {
24759
+ if (clickLocked)
24760
+ return;
24761
+ if (isAtBottom()) {
24762
+ activate(headings[headings.length - 1].id);
24763
+ return;
24764
+ }
24765
+ const topmost = headings.find(el => visible.has(el.id));
24766
+ if (topmost)
24767
+ activate(topmost.id);
24768
+ };
24732
24769
  const observer = new IntersectionObserver(entries => {
24733
24770
  entries.forEach(e => {
24734
24771
  if (e.isIntersecting)
@@ -24736,14 +24773,43 @@ function useScrollHighlight(navRef) {
24736
24773
  else
24737
24774
  visible.delete(e.target.id);
24738
24775
  });
24739
- // Highlight the topmost visible heading; if none are visible,
24740
- // keep the last active one so the user still has context.
24741
- const topmost = headings.find(el => visible.has(el.id));
24742
- if (topmost)
24743
- activate(topmost.id);
24744
- }, { rootMargin: '0px 0px -60% 0px', threshold: 0 });
24776
+ updateActive();
24777
+ }, { rootMargin: `0px 0px -${(1 - VISIBLE_RATIO) * 100}% 0px`, threshold: 0 });
24778
+ // Check on scroll so bottom-of-page detection works even when
24779
+ // no headings are crossing the intersection boundary.
24780
+ const scrollTarget = scrollParent instanceof Window ? window : scrollParent;
24781
+ const onScroll = () => { updateActive(); };
24782
+ // Click a ToC link → immediately activate it, suppress the observer
24783
+ // until the smooth scroll finishes, then hand control back.
24784
+ const onClick = (e) => {
24785
+ const anchor = e.target.closest?.('a[href^="#"]');
24786
+ if (!anchor)
24787
+ return;
24788
+ const id = decodeURIComponent(anchor.getAttribute('href').slice(1));
24789
+ if (!linkMap.has(id))
24790
+ return;
24791
+ e.preventDefault();
24792
+ activate(id);
24793
+ clickLocked = true;
24794
+ const unlock = () => { clickLocked = false; };
24795
+ scrollTarget.addEventListener('scrollend', unlock, { once: true });
24796
+ document.getElementById(id)?.scrollIntoView({ behavior: 'smooth' });
24797
+ };
24745
24798
  headings.forEach(el => { observer.observe(el); });
24746
- return () => { observer.disconnect(); };
24799
+ scrollTarget.addEventListener('scroll', onScroll, { passive: true });
24800
+ nav.addEventListener('click', onClick);
24801
+ // Set initial active state for the first heading visible in the viewport,
24802
+ // falling back to the first heading if none is in the observation zone yet.
24803
+ const initialHeading = headings.find(el => {
24804
+ const rect = el.getBoundingClientRect();
24805
+ return rect.top >= 0 && rect.top < window.innerHeight * VISIBLE_RATIO;
24806
+ }) || headings[0];
24807
+ activate(initialHeading.id);
24808
+ return () => {
24809
+ observer.disconnect();
24810
+ scrollTarget.removeEventListener('scroll', onScroll);
24811
+ nav.removeEventListener('click', onClick);
24812
+ };
24747
24813
  }, [navRef, linkCount]);
24748
24814
  }
24749
24815
  function TableOfContents({ children }) {
@@ -118913,25 +118979,31 @@ function makeVariableNode(varName, rawValue) {
118913
118979
  }
118914
118980
  /**
118915
118981
  * A remark plugin that parses {user.<field>} patterns from text nodes and
118916
- * mdxTextExpression nodes, creating Variable nodes for runtime resolution.
118982
+ * mdx expression nodes, creating Variable nodes for runtime resolution.
118917
118983
  *
118918
- * Handles both:
118984
+ * Handles:
118919
118985
  * - `text` nodes: when safeMode is true or after expression evaluation
118920
118986
  * - `mdxTextExpression` nodes: when mdxExpression has parsed {user.*} before evaluation
118987
+ * - `mdxFlowExpression` nodes: when {user.*} appears on its own line (e.g. inside JSX table cells)
118921
118988
  *
118922
118989
  * Supports any user field: name, email, email_verified, exp, iat, etc.
118923
118990
  */
118991
+ function visitExpressionNode(node, index, parent) {
118992
+ if (index === undefined || !parent)
118993
+ return;
118994
+ const wrapped = `{${(node.value ?? '').trim()}}`;
118995
+ const matches = [...wrapped.matchAll(USER_VAR_REGEX)];
118996
+ if (matches.length !== 1)
118997
+ return;
118998
+ const varName = matches[0][1] || matches[0][2];
118999
+ parent.children.splice(index, 1, makeVariableNode(varName, wrapped));
119000
+ }
118924
119001
  const variablesTextTransformer = () => tree => {
118925
- // Handle mdxTextExpression nodes (e.g. {user.name} parsed by mdxExpression)
118926
119002
  visit(tree, 'mdxTextExpression', (node, index, parent) => {
118927
- if (index === undefined || !parent)
118928
- return;
118929
- const wrapped = `{${(node.value ?? '').trim()}}`; // Wrap the expression value in {} to match the USER_VAR_REGEX pattern
118930
- const matches = [...wrapped.matchAll(USER_VAR_REGEX)];
118931
- if (matches.length !== 1)
118932
- return;
118933
- const varName = matches[0][1] || matches[0][2];
118934
- parent.children.splice(index, 1, makeVariableNode(varName, wrapped));
119003
+ visitExpressionNode(node, index, parent);
119004
+ });
119005
+ visit(tree, 'mdxFlowExpression', (node, index, parent) => {
119006
+ visitExpressionNode(node, index, parent);
118935
119007
  });
118936
119008
  visit(tree, 'text', (node, index, parent) => {
118937
119009
  if (index === undefined || !parent)