@botiverse/raft-ui 0.59.2 → 0.60.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 (56) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/dist/components/composer/composer-attachment.recipe.mjs +2 -3
  3. package/dist/components/composer/composer-attachments-scroll-area.d.mts +6 -0
  4. package/dist/components/composer/composer-attachments-scroll-area.mjs +20 -31
  5. package/dist/components/composer/composer.d.mts +8 -2
  6. package/dist/components/composer/composer.mjs +7 -9
  7. package/dist/components/composer/composer.recipe.mjs +1 -0
  8. package/dist/components/context-menu/context-menu.d.mts +1 -1
  9. package/dist/components/dropdown-menu/dropdown-menu.d.mts +1 -1
  10. package/dist/components/dropdown-menu/dropdown-menu.mjs +1 -1
  11. package/dist/components/kbd/index.d.mts +5 -2
  12. package/dist/components/kbd/kbd-keys.d.mts +12 -0
  13. package/dist/components/kbd/kbd-keys.mjs +297 -0
  14. package/dist/components/kbd/kbd-shortcut.d.mts +34 -0
  15. package/dist/components/kbd/kbd-shortcut.mjs +79 -0
  16. package/dist/components/kbd/kbd.d.mts +55 -2
  17. package/dist/components/kbd/kbd.mjs +39 -15
  18. package/dist/components/markdown/markdown-parts.d.mts +7 -2
  19. package/dist/components/markdown/markdown-parts.mjs +8 -3
  20. package/dist/components/markdown/markdown.recipe.mjs +1 -5
  21. package/dist/components/message-list/message-list.mjs +9 -15
  22. package/dist/components/message-list/message-list.recipe.mjs +1 -1
  23. package/dist/components/scroll-area/class-names.mjs +19 -0
  24. package/dist/components/scroll-area/gutters-binding.mjs +4 -0
  25. package/dist/components/scroll-area/index.d.mts +6 -2
  26. package/dist/components/scroll-area/scroll-area-gutters.d.mts +15 -0
  27. package/dist/components/scroll-area/scroll-area-gutters.mjs +60 -0
  28. package/dist/components/scroll-area/scroll-area-textarea.d.mts +14 -0
  29. package/dist/components/scroll-area/scroll-area-textarea.mjs +50 -0
  30. package/dist/components/scroll-area/scroll-area.css +720 -0
  31. package/dist/components/scroll-area/scroll-area.d.mts +24 -15
  32. package/dist/components/scroll-area/scroll-area.mjs +67 -45
  33. package/dist/components/scroll-area/scroll-geometry.mjs +58 -0
  34. package/dist/components/scroll-area/scroll-to-element.mjs +67 -0
  35. package/dist/components/scroll-area/types.d.mts +224 -0
  36. package/dist/components/scroll-area/use-rail-pointer.mjs +93 -0
  37. package/dist/components/scroll-area/use-scroll-area-container.d.mts +26 -0
  38. package/dist/components/scroll-area/use-scroll-area-container.mjs +150 -0
  39. package/dist/components/scroll-area/use-scrollbars.mjs +480 -0
  40. package/dist/components/search/search-trigger.d.mts +1 -1
  41. package/dist/components/sidebar/sidebar.d.mts +1 -1
  42. package/dist/components/sidebar/sidebar.mjs +24 -20
  43. package/dist/components/sidebar/sidebar.recipe.mjs +1 -6
  44. package/dist/components/tooltip/tooltip.mjs +8 -5
  45. package/dist/components/virtual-list/VirtualList.mjs +1 -1
  46. package/dist/index.d.mts +7 -3
  47. package/dist/index.mjs +5 -2
  48. package/dist/lib/inline-part.mjs +13 -0
  49. package/dist/lib/use-merged-ref.mjs +73 -47
  50. package/dist/lib/use-platform.d.mts +13 -0
  51. package/dist/lib/use-platform.mjs +29 -0
  52. package/dist/styles.css +1 -0
  53. package/dist/wip.d.mts +5 -1
  54. package/dist/wip.mjs +3 -1
  55. package/package.json +1 -1
  56. package/dist/components/scroll-area/scroll-area.recipe.mjs +0 -23
@@ -0,0 +1,150 @@
1
+ import { useMergedRef } from "../../lib/use-merged-ref.mjs";
2
+ import { scrollAreaClassNames } from "./class-names.mjs";
3
+ import { scrollToElement } from "./scroll-to-element.mjs";
4
+ import { effectiveHideDelay, scrollAreaContainerClassName, useScrollbars } from "./use-scrollbars.mjs";
5
+ import { useCallback, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef } from "react";
6
+ //#region src/components/scroll-area/use-scroll-area-container.ts
7
+ const railInsetEdges = [
8
+ "top",
9
+ "bottom",
10
+ "left",
11
+ "right"
12
+ ];
13
+ /**
14
+ * The root's own variables: `railInset`, and the names of its container's scroll timelines, unique
15
+ * to this area, so that an area nested in another's container never takes the outer one's
16
+ * timeline (or makes it ambiguous).
17
+ */
18
+ function rootStyle(timelines, railInset) {
19
+ const vars = {
20
+ "--r-scroll-area-timeline-x": `--r-scroll-area-x-${timelines}`,
21
+ "--r-scroll-area-timeline-y": `--r-scroll-area-y-${timelines}`
22
+ };
23
+ for (const edge of railInsetEdges) {
24
+ const value = railInset[edge];
25
+ if (value !== void 0) vars[`--r-scroll-area-rail-inset-${edge}`] = `${value}px`;
26
+ }
27
+ return vars;
28
+ }
29
+ function useScrollAreaContainer({ actionsRef, containerRef: ownContainerRef, contentRef, fade = false, fadeRef, frameRef: ownFrameRef, guttersPosition = "sticky", hideDelay = 600, onEdgeChange, orientation = "vertical", overscroll = "none", railInset, size = "medium", trigger = "hover" } = {}) {
30
+ const sticky = guttersPosition === "sticky";
31
+ const containerRef = useRef(null);
32
+ const frameRef = useRef(null);
33
+ const guttersRef = useRef(null);
34
+ const [forwardContainer] = useMergedRef(ownContainerRef);
35
+ const [forwardFrame] = useMergedRef(ownFrameRef);
36
+ const setContainerRef = useCallback((element) => {
37
+ containerRef.current = element;
38
+ forwardContainer(element);
39
+ }, [forwardContainer]);
40
+ const setFrameRef = useCallback((element) => {
41
+ frameRef.current = element;
42
+ forwardFrame(element);
43
+ }, [forwardFrame]);
44
+ const { measure: measureBars, rails, scrollable, setDragging, xThumbRef, yThumbRef } = useScrollbars({
45
+ containerRef,
46
+ contentRef,
47
+ fade,
48
+ fadeRef,
49
+ guttersPosition,
50
+ guttersRef,
51
+ hideDelay: effectiveHideDelay(trigger, hideDelay),
52
+ onEdgeChange,
53
+ orientation,
54
+ rootRef: sticky ? containerRef : frameRef
55
+ });
56
+ const measure = useCallback(() => measureBars(), [measureBars]);
57
+ const orientationRef = useRef(orientation);
58
+ useLayoutEffect(() => {
59
+ orientationRef.current = orientation;
60
+ }, [orientation]);
61
+ const scrollToElement$1 = useCallback((element, options) => {
62
+ const container = containerRef.current;
63
+ if (container) scrollToElement(container, element, orientationRef.current, options);
64
+ }, []);
65
+ useImperativeHandle(actionsRef, () => ({
66
+ measure,
67
+ scrollToElement: scrollToElement$1
68
+ }), [measure, scrollToElement$1]);
69
+ const timelines = useId().replace(/[^\w-]/g, "");
70
+ const insetTop = railInset?.top;
71
+ const insetBottom = railInset?.bottom;
72
+ const insetLeft = railInset?.left;
73
+ const insetRight = railInset?.right;
74
+ const blockStyle = useMemo(() => rootStyle(timelines, {
75
+ bottom: insetBottom,
76
+ left: insetLeft,
77
+ right: insetRight,
78
+ top: insetTop
79
+ }), [
80
+ timelines,
81
+ insetTop,
82
+ insetBottom,
83
+ insetLeft,
84
+ insetRight
85
+ ]);
86
+ const blockClassName = scrollAreaClassNames({
87
+ fade,
88
+ guttersPosition,
89
+ orientation,
90
+ overscroll,
91
+ rails,
92
+ size,
93
+ trigger
94
+ });
95
+ const tabIndex = scrollable ? 0 : void 0;
96
+ const containerProps = useMemo(() => ({
97
+ className: sticky ? blockClassName : scrollAreaContainerClassName,
98
+ ref: setContainerRef,
99
+ style: sticky ? blockStyle : void 0,
100
+ tabIndex
101
+ }), [
102
+ blockClassName,
103
+ blockStyle,
104
+ setContainerRef,
105
+ sticky,
106
+ tabIndex
107
+ ]);
108
+ const frameProps = useMemo(() => sticky ? void 0 : {
109
+ className: blockClassName,
110
+ ref: setFrameRef,
111
+ style: blockStyle
112
+ }, [
113
+ blockClassName,
114
+ blockStyle,
115
+ setFrameRef,
116
+ sticky
117
+ ]);
118
+ const guttersProps = useMemo(() => ({ binding: {
119
+ containerRef,
120
+ guttersRef,
121
+ onDraggingChange: setDragging,
122
+ rails,
123
+ xThumbRef,
124
+ yThumbRef
125
+ } }), [
126
+ rails,
127
+ setDragging,
128
+ xThumbRef,
129
+ yThumbRef
130
+ ]);
131
+ return useMemo(() => ({
132
+ containerProps,
133
+ frameProps,
134
+ guttersPosition,
135
+ guttersProps,
136
+ measure,
137
+ scrollToElement: scrollToElement$1,
138
+ scrollable
139
+ }), [
140
+ containerProps,
141
+ frameProps,
142
+ guttersPosition,
143
+ guttersProps,
144
+ measure,
145
+ scrollToElement$1,
146
+ scrollable
147
+ ]);
148
+ }
149
+ //#endregion
150
+ export { useScrollAreaContainer };
@@ -0,0 +1,480 @@
1
+ import { overflows, scrollOverflow, thumbLength, thumbOffset, thumbPlacement } from "./scroll-geometry.mjs";
2
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
3
+ //#region src/components/scroll-area/use-scrollbars.ts
4
+ const noRails = {
5
+ xRail: false,
6
+ yRail: false
7
+ };
8
+ const noEdges = {
9
+ xEnd: false,
10
+ xStart: false,
11
+ yEnd: false,
12
+ yStart: false
13
+ };
14
+ /** `onEdgeChange`'s physical edges: `true` when the area is scrolled all the way to it. */
15
+ function physicalEdges(edges, rtl) {
16
+ return {
17
+ bottom: !edges.yEnd,
18
+ left: !(rtl ? edges.xEnd : edges.xStart),
19
+ right: !(rtl ? edges.xStart : edges.xEnd),
20
+ top: !edges.yStart
21
+ };
22
+ }
23
+ /** Sets a style property unless it already has that value, so an unchanged frame touches no style. */
24
+ function writeVar(element, name, value) {
25
+ if (element.style.getPropertyValue(name) !== value) element.style.setProperty(name, value);
26
+ }
27
+ const px = (value) => `${value}px`;
28
+ /** Rounds to the device pixel grid, so the thumb's edges are crisp and don't shimmer between frames. */
29
+ function snap(value) {
30
+ const ratio = typeof window !== "undefined" && window.devicePixelRatio || 1;
31
+ return Math.round(value * ratio) / ratio;
32
+ }
33
+ /**
34
+ * Scroll-driven animations: all sticky gutters need, as they are inside the container that names
35
+ * the timelines. The same condition gates the sticky `@supports` block in scroll-area.css.
36
+ */
37
+ const stickyScrollTimelineCondition = "(animation-timeline: scroll())";
38
+ /**
39
+ * Scroll-driven animations, and `timeline-scope` for the frame, whose container is a sibling of
40
+ * the absolute gutters. The same condition gates the absolute `@supports` block in scroll-area.css.
41
+ */
42
+ const scrollTimelineCondition = "(animation-timeline: scroll()) and (timeline-scope: none)";
43
+ /**
44
+ * Whether the browser moves the thumbs itself, from the scroll (scroll-area.css), so the script
45
+ * only sizes them. Sticky gutters don't need `timeline-scope`, so a browser without it still
46
+ * animates them.
47
+ */
48
+ function supportsScrollTimeline(position = "absolute") {
49
+ return typeof CSS !== "undefined" && typeof CSS.supports === "function" && CSS.supports(position === "sticky" ? "(animation-timeline: scroll())" : "(animation-timeline: scroll()) and (timeline-scope: none)");
50
+ }
51
+ /**
52
+ * With absolute gutters, the class of the container (a direct child of the frame): it hides the
53
+ * native scrollbar, names the scroll timelines and takes `overscroll`. The binding's `className`
54
+ * carries it; with sticky gutters the container is the root and carries the root's classes.
55
+ */
56
+ const scrollAreaContainerClassName = "r-scroll-area__container";
57
+ /**
58
+ * Whether the CSS animates the thumbs from the container's timeline, so the script only sizes them.
59
+ * With sticky gutters: the container is the root (`--sticky-gutters`) and the gutters its child,
60
+ * so they see the timeline it names. With absolute gutters: the container is a direct child of the
61
+ * root with `scrollAreaContainerClassName`, and the gutters come after it (the CSS selects the
62
+ * thumbs as `.r-scroll-area__container ~ .r-scroll-area__gutters …`, and the root hoists the
63
+ * names with `timeline-scope`). Otherwise the script moves them, so a container bound some other
64
+ * way still gets a working thumb.
65
+ */
66
+ function timelineDrives(container, root, gutters, position) {
67
+ if (!supportsScrollTimeline(position) || gutters === null) return false;
68
+ if (position === "sticky") return container === root && root.classList.contains("r-scroll-area--sticky-gutters") && gutters.parentElement === root;
69
+ return root.classList.contains("r-scroll-area--absolute-gutters") && container.parentElement === root && container.classList.contains("r-scroll-area__container") && gutters.parentElement === root && (container.compareDocumentPosition(gutters) & Node.DOCUMENT_POSITION_FOLLOWING) !== 0;
70
+ }
71
+ /**
72
+ * Why a sticky container's own layout would count the gutters layer, its first child, as one of
73
+ * its items, or `null` when it lays its children out as a block or a gapless flex column.
74
+ */
75
+ function stickyLayoutProblem(style) {
76
+ const display = style.display;
77
+ if (display === "grid" || display === "inline-grid") return "is a grid, so the gutters layer takes its first cell";
78
+ if (display !== "flex" && display !== "inline-flex") return null;
79
+ if (!style.flexDirection.startsWith("column")) return "is a flex row, so the gutters layer is a zero-width item and the gutters collapse";
80
+ const gap = Number.parseFloat(style.rowGap);
81
+ if (Number.isFinite(gap) && gap > 0) return "is a flex column with a gap, so the gutters layer adds a gap before the first item";
82
+ return null;
83
+ }
84
+ /** Each sticky container warned about once, in development. */
85
+ const warnedContainers = /* @__PURE__ */ new WeakSet();
86
+ function warnStickyLayout(container, style) {
87
+ if (process.env.NODE_ENV === "production" || warnedContainers.has(container)) return;
88
+ const problem = stickyLayoutProblem(style);
89
+ if (problem === null) return;
90
+ warnedContainers.add(container);
91
+ console.warn(`raft-ui: a ScrollArea container with sticky gutters ${problem}. The gutters layer is the container's first child, in flow: lay the content out in one wrapper inside it (ScrollArea's contentClassName), or bind the container with useScrollAreaContainer({ guttersPosition: "absolute" }).`, container);
92
+ }
93
+ /** `--r-scroll-area-thumb-min-length` when the CSS isn't loaded (or isn't a px length). */
94
+ const defaultMinThumbLength = 24;
95
+ /** With `trigger="scroll"` the scrollbars show only while scrolling, so they stay at least this long (ms) to be seen and grabbed. */
96
+ const minScrollTriggerHideDelay = 100;
97
+ /** The `hideDelay` actually used: at least `minScrollTriggerHideDelay` when `trigger="scroll"`. */
98
+ function effectiveHideDelay(trigger, hideDelay) {
99
+ return trigger === "scroll" ? Math.max(hideDelay, minScrollTriggerHideDelay) : hideDelay;
100
+ }
101
+ /**
102
+ * Everything the overlay scrollbars need from a native scroll container,
103
+ * the root's container, which the gutters lie over as a sibling.
104
+ *
105
+ * The container is whatever element the root's `containerRef` holds after
106
+ * a commit: the hook listens to its `scroll` events itself (passive), so a
107
+ * container needs no handler wired to the scrollbars, and binds again when
108
+ * a render finds another element there.
109
+ *
110
+ * Two paths. The layout path (mounting, a ResizeObserver or MutationObserver
111
+ * notification, `measure()`) reads the container's client and scroll sizes,
112
+ * the gutters' lengths and the writing direction, caches them and draws.
113
+ * The ResizeObserver watches the container (the viewport), the content
114
+ * (`contentRef`, or each element child of the container) and each gutter.
115
+ * The scroll path only asks for one animation frame, however many scroll
116
+ * events come; that frame reads the offset and the scroll size, right after
117
+ * the offset forced the layout (so it costs nothing more), and draws. The
118
+ * scroll size is read there because the content can grow with nothing
119
+ * observed resizing (a list's rows translated past its spacer), and a stale
120
+ * size leaves the thumb's length behind.
121
+ *
122
+ * Drawing works like a native scrollbar: each thumb's length and place come
123
+ * from the current sizes, every frame, with no transition. The length is
124
+ * written as `--r-scroll-area-thumb-length` (px, rounded to device pixels).
125
+ * Where the browser has scroll-driven animations the thumb is moved by one
126
+ * (scroll-area.css), on the compositor, from the scroll itself, on the
127
+ * container's named timeline: the script writes only
128
+ * `--r-scroll-area-thumb-travel` (gutter minus length), and only when a size
129
+ * changes. Elsewhere the script writes the place as a `translate3d`, in the
130
+ * same frame as the length. With `fade` it also writes the px left past
131
+ * each edge on the container.
132
+ *
133
+ * Scrolling renders nothing: `--scrolling`, `--dragging`, `--rtl` and the
134
+ * `--overflow-*` edge classes are set on the root with `classList` (and set
135
+ * again after each render, which would replace them), and `onEdgeChange` is
136
+ * called from the frame. React renders only when a gutter comes or goes.
137
+ */
138
+ function useScrollbars({ containerRef, contentRef, fadeRef, guttersPosition, rootRef, guttersRef, orientation, hideDelay, fade = false, onEdgeChange }) {
139
+ const [rails, setRails] = useState(noRails);
140
+ const xThumbRef = useRef(null);
141
+ const yThumbRef = useRef(null);
142
+ const latest = useRef({
143
+ fade,
144
+ guttersPosition,
145
+ hideDelay,
146
+ onEdgeChange,
147
+ orientation
148
+ });
149
+ useLayoutEffect(() => {
150
+ latest.current = {
151
+ fade,
152
+ guttersPosition,
153
+ hideDelay,
154
+ onEdgeChange,
155
+ orientation
156
+ };
157
+ });
158
+ const state = useRef({
159
+ dragging: false,
160
+ edges: noEdges,
161
+ /** The pending animation frame, 0 when none. */
162
+ frame: 0,
163
+ idleTimer: void 0,
164
+ layout: {
165
+ clientHeight: 0,
166
+ clientWidth: 0,
167
+ minLength: defaultMinThumbLength,
168
+ railX: 0,
169
+ railY: 0,
170
+ scrollHeight: 0,
171
+ scrollWidth: 0
172
+ },
173
+ observer: null,
174
+ rails: noRails,
175
+ rtl: false,
176
+ scrolling: false,
177
+ /** `supportsScrollTimeline()`, read with the layout. */
178
+ timeline: false
179
+ });
180
+ /**
181
+ * The classes that change while scrolling: written here, never rendered. The edges and the
182
+ * direction on the root (callers style from them); scrolling and dragging on the gutters layer,
183
+ * so toggling them at each scroll's start and end restyles only the gutters, never the whole
184
+ * content (a long list) under the root.
185
+ */
186
+ const applyClasses = useCallback(() => {
187
+ const root = rootRef.current;
188
+ if (!root) return;
189
+ const { dragging, edges, rtl, scrolling } = state.current;
190
+ const list = root.classList;
191
+ list.toggle("r-scroll-area--rtl", rtl);
192
+ list.toggle("r-scroll-area--overflow-y-start", edges.yStart);
193
+ list.toggle("r-scroll-area--overflow-y-end", edges.yEnd);
194
+ list.toggle("r-scroll-area--overflow-x-start", edges.xStart);
195
+ list.toggle("r-scroll-area--overflow-x-end", edges.xEnd);
196
+ const gutters = guttersRef.current?.classList;
197
+ gutters?.toggle("r-scroll-area__gutters--scrolling", scrolling);
198
+ gutters?.toggle("r-scroll-area__gutters--dragging", dragging);
199
+ }, [guttersRef, rootRef]);
200
+ /** `measure`, for `draw`, which is declared before it. */
201
+ const remeasure = useRef(() => {});
202
+ /** Reads the offset once and draws everything that follows from it and the cached sizes. */
203
+ const draw = useCallback(() => {
204
+ const container = containerRef.current;
205
+ const root = rootRef.current;
206
+ if (!container || !root) return;
207
+ const current = state.current;
208
+ const { fade: fadeContent, onEdgeChange: notify } = latest.current;
209
+ const { layout } = current;
210
+ const scrollTop = container.scrollTop;
211
+ const scrollLeft = Math.abs(container.scrollLeft);
212
+ layout.scrollHeight = container.scrollHeight;
213
+ layout.scrollWidth = container.scrollWidth;
214
+ const metrics = {
215
+ ...layout,
216
+ scrollLeft,
217
+ scrollTop
218
+ };
219
+ const { timeline } = current;
220
+ const axes = latest.current.orientation;
221
+ if (axes !== "horizontal" && thumbPlacement(metrics, "y") !== null !== current.rails.yRail || axes !== "vertical" && thumbPlacement(metrics, "x") !== null !== current.rails.xRail) {
222
+ remeasure.current();
223
+ return;
224
+ }
225
+ for (const axis of ["y", "x"]) {
226
+ const element = axis === "y" ? yThumbRef.current : xThumbRef.current;
227
+ const placement = current.rails[axis === "y" ? "yRail" : "xRail"] ? thumbPlacement(metrics, axis) : null;
228
+ if (!element || !placement) continue;
229
+ const rail = axis === "y" ? layout.railY : layout.railX;
230
+ const length = snap(thumbLength(placement.ratio, rail, layout.minLength));
231
+ const sign = axis === "x" && current.rtl ? -1 : 1;
232
+ writeVar(element, "--r-scroll-area-thumb-length", px(length));
233
+ if (timeline) {
234
+ writeVar(element, "--r-scroll-area-thumb-travel", px(sign * Math.max(0, rail - length)));
235
+ continue;
236
+ }
237
+ const at = sign * snap(thumbOffset(placement.progress, rail, length));
238
+ writeVar(element, "transform", axis === "y" ? `translate3d(0px, ${px(at)}, 0px)` : `translate3d(${px(at)}, 0px, 0px)`);
239
+ }
240
+ const overflow = scrollOverflow(metrics, {
241
+ x: current.rails.xRail && thumbPlacement(metrics, "x") !== null,
242
+ y: current.rails.yRail && thumbPlacement(metrics, "y") !== null
243
+ });
244
+ const masked = !fadeContent ? null : latest.current.guttersPosition === "absolute" ? container : fadeRef?.current ?? null;
245
+ if (masked) {
246
+ writeVar(masked, "--r-scroll-area-overflow-y-start", px(overflow.yStart));
247
+ writeVar(masked, "--r-scroll-area-overflow-y-end", px(overflow.yEnd));
248
+ writeVar(masked, "--r-scroll-area-overflow-x-start", px(overflow.xStart));
249
+ writeVar(masked, "--r-scroll-area-overflow-x-end", px(overflow.xEnd));
250
+ }
251
+ const next = {
252
+ xEnd: overflows(overflow.xEnd),
253
+ xStart: overflows(overflow.xStart),
254
+ yEnd: overflows(overflow.yEnd),
255
+ yStart: overflows(overflow.yStart)
256
+ };
257
+ const previous = current.edges;
258
+ if (previous.yStart === next.yStart && previous.yEnd === next.yEnd && previous.xStart === next.xStart && previous.xEnd === next.xEnd) return;
259
+ current.edges = next;
260
+ applyClasses();
261
+ if (!notify) return;
262
+ const edges = physicalEdges(next, current.rtl);
263
+ const before = physicalEdges(previous, current.rtl);
264
+ if (edges.top !== before.top || edges.bottom !== before.bottom || edges.left !== before.left || edges.right !== before.right) notify(edges);
265
+ }, [
266
+ applyClasses,
267
+ containerRef,
268
+ fadeRef,
269
+ rootRef
270
+ ]);
271
+ /** At most one frame at a time: however many scroll events come in, the thumb is drawn once a frame. */
272
+ const scheduleDraw = useCallback(() => {
273
+ const current = state.current;
274
+ if (current.frame !== 0) return;
275
+ if (typeof requestAnimationFrame !== "function") {
276
+ applyClasses();
277
+ draw();
278
+ return;
279
+ }
280
+ current.frame = requestAnimationFrame(() => {
281
+ current.frame = 0;
282
+ applyClasses();
283
+ draw();
284
+ });
285
+ }, [applyClasses, draw]);
286
+ /**
287
+ * The layout path: reads the container's sizes and the gutters' lengths into the cache, then draws.
288
+ * `layout` re-reads the writing direction, the minimum thumb length and who moves the thumbs
289
+ * too, which scrolling can't change.
290
+ */
291
+ const measure = useCallback((layout = false) => {
292
+ const container = containerRef.current;
293
+ const root = rootRef.current;
294
+ if (!container || !root) return;
295
+ const current = state.current;
296
+ const axes = latest.current.orientation;
297
+ if (layout) {
298
+ const position = latest.current.guttersPosition;
299
+ current.timeline = timelineDrives(container, root, guttersRef.current, position);
300
+ const style = getComputedStyle(container);
301
+ current.rtl = axes !== "vertical" && style.direction === "rtl";
302
+ if (position === "sticky") {
303
+ writeVar(root, "--r-scroll-area-padding-top", style.paddingTop || "0px");
304
+ writeVar(root, "--r-scroll-area-padding-left", style.paddingLeft || "0px");
305
+ writeVar(root, "--r-scroll-area-padding-right", style.paddingRight || "0px");
306
+ warnStickyLayout(container, style);
307
+ }
308
+ const min = Number.parseFloat(getComputedStyle(root).getPropertyValue("--r-scroll-area-thumb-min-length"));
309
+ current.layout.minLength = Number.isFinite(min) ? min : defaultMinThumbLength;
310
+ }
311
+ const cache = current.layout;
312
+ cache.clientHeight = container.clientHeight;
313
+ cache.clientWidth = container.clientWidth;
314
+ cache.scrollHeight = container.scrollHeight;
315
+ cache.scrollWidth = container.scrollWidth;
316
+ writeVar(root, "--r-scroll-area-viewport-height", px(cache.clientHeight));
317
+ writeVar(root, "--r-scroll-area-viewport-width", px(cache.clientWidth));
318
+ const sizes = {
319
+ ...cache,
320
+ scrollLeft: 0,
321
+ scrollTop: 0
322
+ };
323
+ const next = {
324
+ xRail: axes !== "vertical" && thumbPlacement(sizes, "x") !== null,
325
+ yRail: axes !== "horizontal" && thumbPlacement(sizes, "y") !== null
326
+ };
327
+ const yRail = yThumbRef.current?.parentElement;
328
+ const xRail = xThumbRef.current?.parentElement;
329
+ cache.railY = next.yRail && yRail ? yRail.clientHeight : 0;
330
+ cache.railX = next.xRail && xRail ? xRail.clientWidth : 0;
331
+ if (next.xRail !== current.rails.xRail || next.yRail !== current.rails.yRail) {
332
+ current.rails = next;
333
+ setRails(next);
334
+ }
335
+ applyClasses();
336
+ draw();
337
+ }, [
338
+ applyClasses,
339
+ containerRef,
340
+ draw,
341
+ guttersRef,
342
+ rootRef
343
+ ]);
344
+ useLayoutEffect(() => {
345
+ remeasure.current = () => measure(true);
346
+ }, [measure]);
347
+ /** The container's `scroll` listener: marks it scrolling until `hideDelay` after the last one, and asks for a frame. */
348
+ const handleScroll = useCallback(() => {
349
+ const current = state.current;
350
+ current.scrolling = true;
351
+ scheduleDraw();
352
+ clearTimeout(current.idleTimer);
353
+ current.idleTimer = setTimeout(() => {
354
+ current.scrolling = false;
355
+ applyClasses();
356
+ }, latest.current.hideDelay);
357
+ }, [applyClasses, scheduleDraw]);
358
+ const setDragging = useCallback((dragging) => {
359
+ state.current.dragging = dragging;
360
+ applyClasses();
361
+ }, [applyClasses]);
362
+ /** Binds the observers and the scroll listener to `container`; returns what undoes it. */
363
+ const bind = useCallback((container, content, root) => {
364
+ container.addEventListener("scroll", handleScroll, { passive: true });
365
+ measure(true);
366
+ const classes = root && typeof MutationObserver !== "undefined" ? new MutationObserver(applyClasses) : null;
367
+ if (root) classes?.observe(root, {
368
+ attributeFilter: ["class"],
369
+ attributes: true
370
+ });
371
+ const stopListening = () => {
372
+ container.removeEventListener("scroll", handleScroll);
373
+ classes?.disconnect();
374
+ };
375
+ if (typeof ResizeObserver === "undefined") return stopListening;
376
+ const observer = new ResizeObserver(() => measure(true));
377
+ state.current.observer = observer;
378
+ for (const element of [
379
+ container,
380
+ content,
381
+ yThumbRef.current?.parentElement,
382
+ xThumbRef.current?.parentElement
383
+ ]) if (element) observer.observe(element);
384
+ const disconnect = () => {
385
+ stopListening();
386
+ observer.disconnect();
387
+ if (state.current.observer === observer) state.current.observer = null;
388
+ };
389
+ if (content) return disconnect;
390
+ const observed = /* @__PURE__ */ new Set();
391
+ const observeChildElements = () => {
392
+ for (const child of observed) {
393
+ if (child.parentNode === container) continue;
394
+ observed.delete(child);
395
+ observer.unobserve(child);
396
+ }
397
+ for (const child of container.children) {
398
+ if (observed.has(child)) continue;
399
+ observed.add(child);
400
+ observer.observe(child);
401
+ }
402
+ };
403
+ observeChildElements();
404
+ const mutations = typeof MutationObserver === "undefined" ? null : new MutationObserver(() => {
405
+ observeChildElements();
406
+ measure(true);
407
+ });
408
+ mutations?.observe(container, { childList: true });
409
+ return () => {
410
+ mutations?.disconnect();
411
+ disconnect();
412
+ };
413
+ }, [
414
+ applyClasses,
415
+ handleScroll,
416
+ measure
417
+ ]);
418
+ /** What the observers are bound to now, and how to undo it. */
419
+ const binding = useRef(null);
420
+ useLayoutEffect(() => {
421
+ const container = containerRef.current;
422
+ const content = contentRef?.current ?? null;
423
+ const root = rootRef.current;
424
+ const key = [
425
+ bind,
426
+ orientation,
427
+ fade,
428
+ guttersPosition
429
+ ];
430
+ const current = binding.current;
431
+ if (current && current.container === container && current.content === content && current.root === root && current.key.every((value, index) => value === key[index])) return;
432
+ current?.unbind();
433
+ binding.current = {
434
+ container,
435
+ content,
436
+ key,
437
+ root,
438
+ unbind: container ? bind(container, content, root) : () => {}
439
+ };
440
+ });
441
+ useLayoutEffect(() => () => {
442
+ binding.current?.unbind();
443
+ binding.current = null;
444
+ }, []);
445
+ useLayoutEffect(() => {
446
+ if (!rails.xRail && !rails.yRail) return void 0;
447
+ measure(true);
448
+ const observer = state.current.observer;
449
+ const elements = [yThumbRef.current?.parentElement, xThumbRef.current?.parentElement].filter((element) => element != null);
450
+ for (const element of elements) observer?.observe(element);
451
+ return () => {
452
+ for (const element of elements) observer?.unobserve(element);
453
+ };
454
+ }, [
455
+ rails.xRail,
456
+ rails.yRail,
457
+ measure
458
+ ]);
459
+ useLayoutEffect(() => applyClasses());
460
+ useEffect(() => {
461
+ const current = state.current;
462
+ return () => {
463
+ clearTimeout(current.idleTimer);
464
+ if (current.frame !== 0) cancelAnimationFrame(current.frame);
465
+ current.frame = 0;
466
+ current.scrolling = false;
467
+ current.dragging = false;
468
+ };
469
+ }, []);
470
+ return {
471
+ measure,
472
+ rails,
473
+ scrollable: rails.xRail || rails.yRail,
474
+ setDragging,
475
+ xThumbRef,
476
+ yThumbRef
477
+ };
478
+ }
479
+ //#endregion
480
+ export { effectiveHideDelay, scrollAreaContainerClassName, scrollTimelineCondition, stickyLayoutProblem, stickyScrollTimelineCondition, supportsScrollTimeline, useScrollbars };
@@ -9,7 +9,7 @@ export declare function SearchTrigger({ className, render, ...props }: SearchTri
9
9
  export type SearchTriggerLabelProps = useRender.ComponentProps<"span">;
10
10
  /** The placeholder ("Search Raft"), truncated before the shortcut gives way. */
11
11
  export declare function SearchTriggerLabel({ className, render, ...props }: SearchTriggerLabelProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
12
- export type SearchTriggerShortcutProps = KbdProps;
12
+ export type SearchTriggerShortcutProps = Omit<KbdProps, "size">;
13
13
  /** The shortcut that opens search, at the right end. */
14
14
  export declare function SearchTriggerShortcut({ className, ...props }: SearchTriggerShortcutProps): import("react").JSX.Element;
15
15
  //#endregion
@@ -11,5 +11,5 @@ export declare function SidebarGroupLabel({ className, render, ...props }: Sideb
11
11
  export type SidebarBodyProps = useRender.ComponentProps<"div">;
12
12
  export declare function SidebarBody({ className, render, ...props }: SidebarBodyProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
13
13
  export type SidebarContentProps = useRender.ComponentProps<"div">;
14
- export declare function SidebarContent({ children, className, render, ...props }: SidebarContentProps): import("react").JSX.Element;
14
+ export declare function SidebarContent({ children, className, ...props }: SidebarContentProps): import("react").JSX.Element;
15
15
  //#endregion