@stream-mdx/react 0.3.0 → 0.5.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +14 -4
  2. package/README.md +65 -14
  3. package/dist/components/bottom-stick-scroll-area.cjs +344 -0
  4. package/dist/components/bottom-stick-scroll-area.d.cts +36 -0
  5. package/dist/components/bottom-stick-scroll-area.d.ts +36 -0
  6. package/dist/components/bottom-stick-scroll-area.mjs +320 -0
  7. package/dist/components/index.cjs +511 -176
  8. package/dist/components/index.d.cts +3 -1
  9. package/dist/components/index.d.ts +3 -1
  10. package/dist/components/index.mjs +358 -26
  11. package/dist/{index-D7px9jug.d.cts → index-BVhp_pcF.d.ts} +4 -1
  12. package/dist/{index-D7px9jug.d.ts → index-EaeouYp2.d.cts} +4 -1
  13. package/dist/index.cjs +2146 -1145
  14. package/dist/index.d.cts +6 -4
  15. package/dist/index.d.ts +6 -4
  16. package/dist/index.mjs +3835 -2836
  17. package/dist/mdx-client.cjs +1 -1
  18. package/dist/mdx-client.mjs +1 -1
  19. package/dist/mdx-coordinator.cjs +28 -7
  20. package/dist/mdx-coordinator.d.cts +2 -2
  21. package/dist/mdx-coordinator.d.ts +2 -2
  22. package/dist/mdx-coordinator.mjs +28 -7
  23. package/dist/renderer/node-views.cjs +273 -24
  24. package/dist/renderer/node-views.d.cts +3 -1
  25. package/dist/renderer/node-views.d.ts +3 -1
  26. package/dist/renderer/node-views.mjs +274 -25
  27. package/dist/renderer/patch-batching.cjs +2 -0
  28. package/dist/renderer/patch-batching.d.cts +1 -1
  29. package/dist/renderer/patch-batching.d.ts +1 -1
  30. package/dist/renderer/patch-batching.mjs +2 -1
  31. package/dist/renderer/patch-commit-scheduler.cjs +82 -30
  32. package/dist/renderer/patch-commit-scheduler.d.cts +15 -3
  33. package/dist/renderer/patch-commit-scheduler.d.ts +15 -3
  34. package/dist/renderer/patch-commit-scheduler.mjs +84 -30
  35. package/dist/renderer/store.cjs +382 -36
  36. package/dist/renderer/store.d.cts +3 -0
  37. package/dist/renderer/store.d.ts +3 -0
  38. package/dist/renderer/store.mjs +382 -36
  39. package/dist/renderer.cjs +3386 -2727
  40. package/dist/renderer.d.cts +15 -12
  41. package/dist/renderer.d.ts +15 -12
  42. package/dist/renderer.mjs +3410 -2751
  43. package/dist/server.cjs +4842 -0
  44. package/dist/server.d.cts +254 -0
  45. package/dist/server.d.ts +254 -0
  46. package/dist/server.mjs +4824 -0
  47. package/dist/{streaming-markdown-Dp1IDgMT.d.ts → streaming-markdown-CEI9eDZm.d.ts} +3 -1
  48. package/dist/{streaming-markdown-DSC4L0xR.d.cts → streaming-markdown-DRisClXb.d.cts} +3 -1
  49. package/dist/streaming-markdown.cjs +3476 -2794
  50. package/dist/streaming-markdown.d.cts +4 -2
  51. package/dist/streaming-markdown.d.ts +4 -2
  52. package/dist/streaming-markdown.mjs +3436 -2754
  53. package/dist/utils/inline-html.d.cts +3 -1
  54. package/dist/utils/inline-html.d.ts +3 -1
  55. package/package.json +19 -3
  56. package/shadcn/README.md +28 -0
  57. package/shadcn/bottom-stick-scroll-area.tsx +358 -0
package/CHANGELOG.md CHANGED
@@ -1,13 +1,23 @@
1
1
  # @stream-mdx/react
2
2
 
3
- ## 0.1.1
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Promote the current hardening and product-surface tranche as `0.5.0`.
8
+
9
+ This release rolls up:
10
+
11
+ - renderer/store correctness hardening
12
+ - seeded smoke and scheduler-parity release discipline
13
+ - benchmark methodology and comparison cleanup
14
+ - docs, showcase, and TUI/product surface maturation
4
15
 
5
16
  ### Patch Changes
6
17
 
7
- - Refine streaming scheduling and list layout, add worker append batching/debug state support, and refresh docs/README examples.
8
18
  - Updated dependencies
9
- - @stream-mdx/core@0.1.1
10
- - @stream-mdx/worker@0.1.1
19
+ - @stream-mdx/core@0.5.0
20
+ - @stream-mdx/worker@0.5.0
11
21
 
12
22
  ## 0.1.0
13
23
 
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # `@stream-mdx/react`
2
2
 
3
- React renderer for StreamMDX. Exposes the `<StreamingMarkdown />` component, scheduling/backpressure hooks, and the public types used by consumers.
3
+ `@stream-mdx/react` is the renderer package for StreamMDX. It owns the React component surface, the renderer store, patch scheduling, server rendering helpers, and the packaged bottom-stick scroll component used by the docs/demo surfaces.
4
4
 
5
- Most apps should install `stream-mdx` instead of this package directly. Use `@stream-mdx/react` when you want the React surface without the unscoped wrapper.
5
+ **Most apps should install [`stream-mdx`](../stream-mdx/README.md) instead.** Use this package directly when you want the React layer without the convenience wrapper.
6
6
 
7
7
  ## Install
8
8
 
@@ -10,19 +10,34 @@ Most apps should install `stream-mdx` instead of this package directly. Use `@st
10
10
  npm install @stream-mdx/react @stream-mdx/worker
11
11
  ```
12
12
 
13
- `react` and `react-dom` are peer dependencies.
13
+ Peer dependencies:
14
14
 
15
- ## Quickstart
15
+ | Package | Range |
16
+ | --- | --- |
17
+ | `react` | `>=18.2.0` |
18
+ | `react-dom` | `>=18.2.0` |
19
+
20
+ ## Primary Exports
16
21
 
17
- Copy the hosted worker bundle into your app’s static assets:
22
+ | Export | Purpose |
23
+ | --- | --- |
24
+ | `@stream-mdx/react` | Main React surface including `<StreamingMarkdown />` |
25
+ | `@stream-mdx/react/server` | Server/static render helpers |
26
+ | `@stream-mdx/react/components` | Shared UI component exports |
27
+ | `@stream-mdx/react/components/bottom-stick-scroll-area` | Packaged sticky-bottom scroll container |
28
+ | `@stream-mdx/react/renderer` | Lower-level renderer exports |
29
+ | `@stream-mdx/react/renderer/patch-commit-scheduler` | Scheduler implementation surface |
30
+ | `@stream-mdx/react/renderer/store` | Renderer store surface |
31
+ | `@stream-mdx/react/mdx-client` | MDX client helpers |
32
+ | `@stream-mdx/react/mdx-coordinator` | MDX coordination helpers |
33
+
34
+ ## Quickstart
18
35
 
19
36
  ```bash
20
37
  mkdir -p public/workers
21
38
  cp node_modules/@stream-mdx/worker/dist/hosted/markdown-worker.js public/workers/markdown-worker.js
22
39
  ```
23
40
 
24
- Render streaming markdown from a client component:
25
-
26
41
  ```tsx
27
42
  "use client";
28
43
 
@@ -33,19 +48,55 @@ export function Demo({ text }: { text: string }) {
33
48
  <StreamingMarkdown
34
49
  text={text}
35
50
  worker="/workers/markdown-worker.js"
36
- features={{ html: true, tables: true, math: true, mdx: true }}
51
+ features={{ html: true, tables: true, math: true, mdx: true, footnotes: true }}
37
52
  mdxCompileMode="worker"
38
53
  />
39
54
  );
40
55
  }
41
56
  ```
42
57
 
43
- ## Docs
58
+ ## Typical Uses
59
+
60
+ | Use case | API |
61
+ | --- | --- |
62
+ | Browser/client rendering | `<StreamingMarkdown />` |
63
+ | SSR / SSG / static export rendering | `MarkdownBlocksRenderer` from `@stream-mdx/react/server` |
64
+ | Rich streaming/chat container | `BottomStickScrollArea` |
65
+ | Advanced renderer internals | `renderer/store`, `renderer/patch-commit-scheduler` |
66
+
67
+ ### Server-side block rendering
68
+
69
+ ```tsx
70
+ import { ComponentRegistry, MarkdownBlocksRenderer } from "@stream-mdx/react/server";
71
+
72
+ return <MarkdownBlocksRenderer blocks={blocks} componentRegistry={new ComponentRegistry()} />;
73
+ ```
74
+
75
+ ### Bottom-stick scroll area
76
+
77
+ ```tsx
78
+ import { BottomStickScrollArea } from "@stream-mdx/react/components/bottom-stick-scroll-area";
79
+
80
+ <BottomStickScrollArea className="h-[32rem]">{children}</BottomStickScrollArea>;
81
+ ```
82
+
83
+ Behavior notes:
84
+
85
+ - sticks to bottom while content appends
86
+ - detaches on upward user scroll
87
+ - supports smooth return-to-bottom behavior
88
+ - exposes debug hooks used by deterministic checks in the repo
89
+
90
+ ## When To Use This Package Directly
44
91
 
45
- - API reference: `docs/PUBLIC_API.md`
46
- - React integration guide: `docs/REACT_INTEGRATION_GUIDE.md`
47
- - Plugins & worker customization: `docs/STREAMING_MARKDOWN_PLUGINS_COOKBOOK.md`
92
+ - You are shipping a library that wants `@stream-mdx/react` explicitly in `peerDependencies` or `dependencies`.
93
+ - You need server rendering helpers without the convenience wrapper import path.
94
+ - You are working on renderer internals and want the lower-level exports directly.
48
95
 
49
- ## Addons
96
+ ## Documentation
50
97
 
51
- - `@stream-mdx/mermaid` (optional Mermaid diagram renderer)
98
+ - [`../../docs/PUBLIC_API.md`](../../docs/PUBLIC_API.md)
99
+ - [`../../docs/REACT_INTEGRATION_GUIDE.md`](../../docs/REACT_INTEGRATION_GUIDE.md)
100
+ - [`../../docs/SECURITY_MODEL.md`](../../docs/SECURITY_MODEL.md)
101
+ - [`../../docs/STREAMING_CORRECTNESS_CONTRACT.md`](../../docs/STREAMING_CORRECTNESS_CONTRACT.md)
102
+ - Docs site: <https://stream-mdx.vercel.app/docs>
@@ -0,0 +1,344 @@
1
+ "use client";
2
+ "use strict";
3
+ "use client";
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/components/bottom-stick-scroll-area.tsx
23
+ var bottom_stick_scroll_area_exports = {};
24
+ __export(bottom_stick_scroll_area_exports, {
25
+ BottomStickScrollArea: () => BottomStickScrollArea
26
+ });
27
+ module.exports = __toCommonJS(bottom_stick_scroll_area_exports);
28
+ var import_react = require("react");
29
+ var import_jsx_runtime = require("react/jsx-runtime");
30
+ var EPS_BOTTOM_PX = 2;
31
+ var EPS_OVERFLOW_PX = 1;
32
+ var DETACH_SCROLL_DELTA_PX = 1;
33
+ var RETURN_MIN_MS = 180;
34
+ var RETURN_MAX_MS = 520;
35
+ var RETURN_PX_PER_MS = 2.2;
36
+ var RETURN_SETTLE_FRAMES = 2;
37
+ function clamp01(value) {
38
+ if (value < 0) return 0;
39
+ if (value > 1) return 1;
40
+ return value;
41
+ }
42
+ function easeOutCubic(value) {
43
+ return 1 - (1 - value) ** 3;
44
+ }
45
+ function joinClasses(...parts) {
46
+ return parts.filter(Boolean).join(" ");
47
+ }
48
+ function measure(viewport) {
49
+ const scrollTop = viewport.scrollTop;
50
+ const scrollHeight = viewport.scrollHeight;
51
+ const clientHeight = viewport.clientHeight;
52
+ const maxScrollTop = Math.max(0, scrollHeight - clientHeight);
53
+ const distanceToBottom = Math.max(0, maxScrollTop - scrollTop);
54
+ const isOverflowing = scrollHeight > clientHeight + EPS_OVERFLOW_PX;
55
+ return { scrollTop, scrollHeight, clientHeight, maxScrollTop, distanceToBottom, isOverflowing };
56
+ }
57
+ function DefaultJumpButton({ canJump, jumpToBottom }) {
58
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
59
+ "button",
60
+ {
61
+ type: "button",
62
+ onClick: jumpToBottom,
63
+ "aria-label": "Scroll to bottom",
64
+ style: {
65
+ pointerEvents: canJump ? "auto" : "none",
66
+ borderRadius: 9999,
67
+ border: "1px solid color-mix(in srgb, currentColor 20%, transparent)",
68
+ width: 40,
69
+ height: 40,
70
+ display: "grid",
71
+ placeItems: "center",
72
+ boxShadow: "0 8px 20px rgba(0,0,0,0.18)",
73
+ background: "color-mix(in srgb, var(--background, #fff) 92%, transparent)",
74
+ color: "var(--foreground, #111)",
75
+ cursor: canJump ? "pointer" : "default"
76
+ },
77
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { "aria-hidden": true, children: "\u2193" })
78
+ }
79
+ );
80
+ }
81
+ function BottomStickScrollArea({
82
+ children,
83
+ className,
84
+ viewportClassName,
85
+ contentClassName,
86
+ showJumpToBottom = true,
87
+ onDebugStateChange,
88
+ onModeChange,
89
+ debugDomAttributes = false,
90
+ renderJumpToBottom,
91
+ jumpContainerClassName
92
+ }) {
93
+ const viewportRef = (0, import_react.useRef)(null);
94
+ const contentRef = (0, import_react.useRef)(null);
95
+ const modeRef = (0, import_react.useRef)("STICKY_INSTANT");
96
+ const [mode, setMode] = (0, import_react.useState)("STICKY_INSTANT");
97
+ const [isOverflowing, setIsOverflowing] = (0, import_react.useState)(false);
98
+ const [distanceToBottom, setDistanceToBottom] = (0, import_react.useState)(0);
99
+ const prevScrollTopRef = (0, import_react.useRef)(0);
100
+ const lastProgrammaticWriteRef = (0, import_react.useRef)(null);
101
+ const programmaticWriteCountRef = (0, import_react.useRef)(0);
102
+ const pendingStickRafRef = (0, import_react.useRef)(null);
103
+ const stickRequestedRef = (0, import_react.useRef)(false);
104
+ const returnRafRef = (0, import_react.useRef)(null);
105
+ const returnAnimRef = (0, import_react.useRef)(null);
106
+ const setModeBoth = (0, import_react.useCallback)(
107
+ (next) => {
108
+ modeRef.current = next;
109
+ setMode(next);
110
+ onModeChange?.(next);
111
+ },
112
+ [onModeChange]
113
+ );
114
+ const updateDebug = (0, import_react.useCallback)(() => {
115
+ const viewport = viewportRef.current;
116
+ if (!viewport) return;
117
+ const m = measure(viewport);
118
+ if (debugDomAttributes) {
119
+ viewport.dataset.stickyMode = modeRef.current;
120
+ viewport.dataset.distanceToBottom = String(m.distanceToBottom);
121
+ viewport.dataset.isOverflowing = m.isOverflowing ? "1" : "0";
122
+ viewport.dataset.programmaticWrites = String(programmaticWriteCountRef.current);
123
+ }
124
+ onDebugStateChange?.({
125
+ mode: modeRef.current,
126
+ isOverflowing: m.isOverflowing,
127
+ distanceToBottom: m.distanceToBottom,
128
+ scrollTop: m.scrollTop,
129
+ maxScrollTop: m.maxScrollTop,
130
+ scrollHeight: m.scrollHeight,
131
+ clientHeight: m.clientHeight,
132
+ programmaticWrites: programmaticWriteCountRef.current
133
+ });
134
+ setDistanceToBottom(
135
+ (previous) => Math.abs(previous - m.distanceToBottom) <= 0.1 ? previous : m.distanceToBottom
136
+ );
137
+ }, [debugDomAttributes, onDebugStateChange]);
138
+ const writeScrollTop = (0, import_react.useCallback)((nextTop) => {
139
+ const viewport = viewportRef.current;
140
+ if (!viewport) return;
141
+ viewport.scrollTop = nextTop;
142
+ programmaticWriteCountRef.current += 1;
143
+ lastProgrammaticWriteRef.current = { at: performance.now(), top: nextTop };
144
+ }, []);
145
+ const cancelPendingStick = (0, import_react.useCallback)(() => {
146
+ if (pendingStickRafRef.current !== null) {
147
+ cancelAnimationFrame(pendingStickRafRef.current);
148
+ pendingStickRafRef.current = null;
149
+ }
150
+ stickRequestedRef.current = false;
151
+ }, []);
152
+ const cancelReturn = (0, import_react.useCallback)(() => {
153
+ if (returnRafRef.current !== null) {
154
+ cancelAnimationFrame(returnRafRef.current);
155
+ returnRafRef.current = null;
156
+ }
157
+ returnAnimRef.current = null;
158
+ }, []);
159
+ const stickToBottomInstant = (0, import_react.useCallback)(() => {
160
+ const viewport = viewportRef.current;
161
+ if (!viewport) return;
162
+ stickRequestedRef.current = true;
163
+ if (pendingStickRafRef.current !== null) return;
164
+ pendingStickRafRef.current = requestAnimationFrame(() => {
165
+ pendingStickRafRef.current = null;
166
+ if (!stickRequestedRef.current) return;
167
+ stickRequestedRef.current = false;
168
+ if (modeRef.current !== "STICKY_INSTANT") return;
169
+ const m = measure(viewport);
170
+ writeScrollTop(m.maxScrollTop);
171
+ updateDebug();
172
+ });
173
+ }, [updateDebug, writeScrollTop]);
174
+ const startReturnToBottomSmooth = (0, import_react.useCallback)(() => {
175
+ const viewport = viewportRef.current;
176
+ if (!viewport) return;
177
+ cancelPendingStick();
178
+ cancelReturn();
179
+ const initial = measure(viewport);
180
+ if (initial.distanceToBottom <= EPS_BOTTOM_PX) {
181
+ setModeBoth("STICKY_INSTANT");
182
+ stickToBottomInstant();
183
+ updateDebug();
184
+ return;
185
+ }
186
+ const durationMs = Math.max(
187
+ RETURN_MIN_MS,
188
+ Math.min(RETURN_MAX_MS, initial.distanceToBottom / RETURN_PX_PER_MS)
189
+ );
190
+ returnAnimRef.current = {
191
+ startTime: performance.now(),
192
+ startTop: initial.scrollTop,
193
+ durationMs,
194
+ settleFrames: 0
195
+ };
196
+ setModeBoth("RETURNING_SMOOTH");
197
+ const tick = (now) => {
198
+ const viewportCurrent = viewportRef.current;
199
+ const anim = returnAnimRef.current;
200
+ if (!viewportCurrent || !anim || modeRef.current !== "RETURNING_SMOOTH") {
201
+ cancelReturn();
202
+ return;
203
+ }
204
+ const before = measure(viewportCurrent);
205
+ const progress = clamp01((now - anim.startTime) / anim.durationMs);
206
+ const eased = easeOutCubic(progress);
207
+ const targetBottom = before.maxScrollTop;
208
+ let nextTop = anim.startTop + (targetBottom - anim.startTop) * eased;
209
+ if (nextTop < before.scrollTop) {
210
+ nextTop = before.scrollTop;
211
+ }
212
+ writeScrollTop(nextTop);
213
+ const after = measure(viewportCurrent);
214
+ if (after.distanceToBottom <= EPS_BOTTOM_PX) {
215
+ anim.settleFrames += 1;
216
+ } else {
217
+ anim.settleFrames = 0;
218
+ }
219
+ const doneByTime = progress >= 1;
220
+ const doneBySettle = anim.settleFrames >= RETURN_SETTLE_FRAMES && progress >= 0.6;
221
+ if (doneByTime && doneBySettle) {
222
+ cancelReturn();
223
+ setModeBoth("STICKY_INSTANT");
224
+ stickToBottomInstant();
225
+ updateDebug();
226
+ return;
227
+ }
228
+ updateDebug();
229
+ returnRafRef.current = requestAnimationFrame(tick);
230
+ };
231
+ returnRafRef.current = requestAnimationFrame(tick);
232
+ }, [cancelPendingStick, cancelReturn, setModeBoth, stickToBottomInstant, updateDebug, writeScrollTop]);
233
+ const onViewportScroll = (0, import_react.useCallback)(
234
+ (event) => {
235
+ const viewport = event.currentTarget;
236
+ const m = measure(viewport);
237
+ setIsOverflowing((previous) => previous === m.isOverflowing ? previous : m.isOverflowing);
238
+ const previousTop = prevScrollTopRef.current;
239
+ const delta = m.scrollTop - previousTop;
240
+ const lastWrite = lastProgrammaticWriteRef.current;
241
+ const wroteRecently = lastWrite !== null && performance.now() - lastWrite.at < 60;
242
+ const isLikelyOwnWrite = wroteRecently && lastWrite !== null && Math.abs(m.scrollTop - lastWrite.top) <= 2;
243
+ if (modeRef.current === "RETURNING_SMOOTH") {
244
+ if (!isLikelyOwnWrite && delta < -DETACH_SCROLL_DELTA_PX) {
245
+ cancelReturn();
246
+ setModeBoth("DETACHED");
247
+ }
248
+ } else if (modeRef.current === "STICKY_INSTANT") {
249
+ if (!isLikelyOwnWrite && delta < -DETACH_SCROLL_DELTA_PX && m.isOverflowing) {
250
+ cancelPendingStick();
251
+ setModeBoth("DETACHED");
252
+ }
253
+ } else if (modeRef.current === "DETACHED") {
254
+ if (m.distanceToBottom <= EPS_BOTTOM_PX) {
255
+ setModeBoth("STICKY_INSTANT");
256
+ stickToBottomInstant();
257
+ }
258
+ }
259
+ prevScrollTopRef.current = m.scrollTop;
260
+ updateDebug();
261
+ },
262
+ [cancelPendingStick, cancelReturn, setModeBoth, stickToBottomInstant, updateDebug]
263
+ );
264
+ (0, import_react.useEffect)(() => {
265
+ const viewport = viewportRef.current;
266
+ const content = contentRef.current;
267
+ if (!viewport || !content) return;
268
+ const observer = new ResizeObserver(() => {
269
+ const m = measure(viewport);
270
+ setIsOverflowing((previous) => previous === m.isOverflowing ? previous : m.isOverflowing);
271
+ if (modeRef.current === "STICKY_INSTANT") {
272
+ stickToBottomInstant();
273
+ }
274
+ updateDebug();
275
+ });
276
+ observer.observe(content);
277
+ observer.observe(viewport);
278
+ return () => observer.disconnect();
279
+ }, [stickToBottomInstant, updateDebug]);
280
+ (0, import_react.useLayoutEffect)(() => {
281
+ const viewport = viewportRef.current;
282
+ if (!viewport) return;
283
+ setModeBoth("STICKY_INSTANT");
284
+ const initial = measure(viewport);
285
+ writeScrollTop(initial.maxScrollTop);
286
+ prevScrollTopRef.current = viewport.scrollTop;
287
+ setIsOverflowing(initial.isOverflowing);
288
+ setDistanceToBottom(initial.distanceToBottom);
289
+ updateDebug();
290
+ }, [setModeBoth, updateDebug, writeScrollTop]);
291
+ (0, import_react.useEffect)(() => {
292
+ return () => {
293
+ cancelPendingStick();
294
+ cancelReturn();
295
+ };
296
+ }, [cancelPendingStick, cancelReturn]);
297
+ const canJump = isOverflowing && mode === "DETACHED";
298
+ const jumpButton = renderJumpToBottom?.({
299
+ mode,
300
+ canJump,
301
+ isOverflowing,
302
+ distanceToBottom,
303
+ jumpToBottom: startReturnToBottomSmooth
304
+ }) ?? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DefaultJumpButton, { canJump, jumpToBottom: startReturnToBottomSmooth });
305
+ const jumpContainerClassNameBase = "pointer-events-none absolute z-20";
306
+ const jumpContainerStyle = {
307
+ position: "absolute",
308
+ zIndex: 20,
309
+ left: 0,
310
+ right: 0,
311
+ bottom: 12,
312
+ display: "flex",
313
+ justifyContent: "center",
314
+ opacity: canJump ? 1 : 0,
315
+ transform: canJump ? "translateY(0px)" : "translateY(4px)",
316
+ transition: "opacity 150ms ease, transform 150ms ease",
317
+ pointerEvents: "none"
318
+ };
319
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: joinClasses("relative h-full w-full overflow-hidden", className), children: [
320
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
321
+ "div",
322
+ {
323
+ ref: viewportRef,
324
+ className: joinClasses("h-full w-full overflow-auto", viewportClassName),
325
+ onScroll: onViewportScroll,
326
+ "data-testid": "sticky-scroll-viewport",
327
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: contentRef, className: joinClasses("flex min-h-full w-full flex-col", contentClassName), children })
328
+ }
329
+ ),
330
+ showJumpToBottom ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
331
+ "div",
332
+ {
333
+ className: joinClasses(jumpContainerClassNameBase, jumpContainerClassName),
334
+ style: jumpContainerStyle,
335
+ "aria-hidden": !canJump,
336
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: canJump ? "pointer-events-auto" : "pointer-events-none", children: jumpButton })
337
+ }
338
+ ) : null
339
+ ] });
340
+ }
341
+ // Annotate the CommonJS export names for ESM import in node:
342
+ 0 && (module.exports = {
343
+ BottomStickScrollArea
344
+ });
@@ -0,0 +1,36 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type BottomStickMode = "STICKY_INSTANT" | "DETACHED" | "RETURNING_SMOOTH";
5
+ type BottomStickDebugState = {
6
+ mode: BottomStickMode;
7
+ isOverflowing: boolean;
8
+ distanceToBottom: number;
9
+ scrollTop: number;
10
+ maxScrollTop: number;
11
+ scrollHeight: number;
12
+ clientHeight: number;
13
+ programmaticWrites: number;
14
+ };
15
+ type BottomStickJumpButtonRenderState = {
16
+ mode: BottomStickMode;
17
+ canJump: boolean;
18
+ isOverflowing: boolean;
19
+ distanceToBottom: number;
20
+ jumpToBottom: () => void;
21
+ };
22
+ type BottomStickScrollAreaProps = {
23
+ children: ReactNode;
24
+ className?: string;
25
+ viewportClassName?: string;
26
+ contentClassName?: string;
27
+ showJumpToBottom?: boolean;
28
+ onDebugStateChange?: (state: BottomStickDebugState) => void;
29
+ onModeChange?: (mode: BottomStickMode) => void;
30
+ debugDomAttributes?: boolean;
31
+ renderJumpToBottom?: (state: BottomStickJumpButtonRenderState) => ReactNode;
32
+ jumpContainerClassName?: string;
33
+ };
34
+ declare function BottomStickScrollArea({ children, className, viewportClassName, contentClassName, showJumpToBottom, onDebugStateChange, onModeChange, debugDomAttributes, renderJumpToBottom, jumpContainerClassName, }: BottomStickScrollAreaProps): react_jsx_runtime.JSX.Element;
35
+
36
+ export { type BottomStickDebugState, type BottomStickJumpButtonRenderState, type BottomStickMode, BottomStickScrollArea, type BottomStickScrollAreaProps };
@@ -0,0 +1,36 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
3
+
4
+ type BottomStickMode = "STICKY_INSTANT" | "DETACHED" | "RETURNING_SMOOTH";
5
+ type BottomStickDebugState = {
6
+ mode: BottomStickMode;
7
+ isOverflowing: boolean;
8
+ distanceToBottom: number;
9
+ scrollTop: number;
10
+ maxScrollTop: number;
11
+ scrollHeight: number;
12
+ clientHeight: number;
13
+ programmaticWrites: number;
14
+ };
15
+ type BottomStickJumpButtonRenderState = {
16
+ mode: BottomStickMode;
17
+ canJump: boolean;
18
+ isOverflowing: boolean;
19
+ distanceToBottom: number;
20
+ jumpToBottom: () => void;
21
+ };
22
+ type BottomStickScrollAreaProps = {
23
+ children: ReactNode;
24
+ className?: string;
25
+ viewportClassName?: string;
26
+ contentClassName?: string;
27
+ showJumpToBottom?: boolean;
28
+ onDebugStateChange?: (state: BottomStickDebugState) => void;
29
+ onModeChange?: (mode: BottomStickMode) => void;
30
+ debugDomAttributes?: boolean;
31
+ renderJumpToBottom?: (state: BottomStickJumpButtonRenderState) => ReactNode;
32
+ jumpContainerClassName?: string;
33
+ };
34
+ declare function BottomStickScrollArea({ children, className, viewportClassName, contentClassName, showJumpToBottom, onDebugStateChange, onModeChange, debugDomAttributes, renderJumpToBottom, jumpContainerClassName, }: BottomStickScrollAreaProps): react_jsx_runtime.JSX.Element;
35
+
36
+ export { type BottomStickDebugState, type BottomStickJumpButtonRenderState, type BottomStickMode, BottomStickScrollArea, type BottomStickScrollAreaProps };