@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.
- package/CHANGELOG.md +38 -0
- package/dist/components/composer/composer-attachment.recipe.mjs +2 -3
- package/dist/components/composer/composer-attachments-scroll-area.d.mts +6 -0
- package/dist/components/composer/composer-attachments-scroll-area.mjs +20 -31
- package/dist/components/composer/composer.d.mts +8 -2
- package/dist/components/composer/composer.mjs +7 -9
- package/dist/components/composer/composer.recipe.mjs +1 -0
- package/dist/components/context-menu/context-menu.d.mts +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.d.mts +1 -1
- package/dist/components/dropdown-menu/dropdown-menu.mjs +1 -1
- package/dist/components/kbd/index.d.mts +5 -2
- package/dist/components/kbd/kbd-keys.d.mts +12 -0
- package/dist/components/kbd/kbd-keys.mjs +297 -0
- package/dist/components/kbd/kbd-shortcut.d.mts +34 -0
- package/dist/components/kbd/kbd-shortcut.mjs +79 -0
- package/dist/components/kbd/kbd.d.mts +55 -2
- package/dist/components/kbd/kbd.mjs +39 -15
- package/dist/components/markdown/markdown-parts.d.mts +7 -2
- package/dist/components/markdown/markdown-parts.mjs +8 -3
- package/dist/components/markdown/markdown.recipe.mjs +1 -5
- package/dist/components/message-list/message-list.mjs +9 -15
- package/dist/components/message-list/message-list.recipe.mjs +1 -1
- package/dist/components/scroll-area/class-names.mjs +19 -0
- package/dist/components/scroll-area/gutters-binding.mjs +4 -0
- package/dist/components/scroll-area/index.d.mts +6 -2
- package/dist/components/scroll-area/scroll-area-gutters.d.mts +15 -0
- package/dist/components/scroll-area/scroll-area-gutters.mjs +60 -0
- package/dist/components/scroll-area/scroll-area-textarea.d.mts +14 -0
- package/dist/components/scroll-area/scroll-area-textarea.mjs +50 -0
- package/dist/components/scroll-area/scroll-area.css +720 -0
- package/dist/components/scroll-area/scroll-area.d.mts +24 -15
- package/dist/components/scroll-area/scroll-area.mjs +67 -45
- package/dist/components/scroll-area/scroll-geometry.mjs +58 -0
- package/dist/components/scroll-area/scroll-to-element.mjs +67 -0
- package/dist/components/scroll-area/types.d.mts +224 -0
- package/dist/components/scroll-area/use-rail-pointer.mjs +93 -0
- package/dist/components/scroll-area/use-scroll-area-container.d.mts +26 -0
- package/dist/components/scroll-area/use-scroll-area-container.mjs +150 -0
- package/dist/components/scroll-area/use-scrollbars.mjs +480 -0
- package/dist/components/search/search-trigger.d.mts +1 -1
- package/dist/components/sidebar/sidebar.d.mts +1 -1
- package/dist/components/sidebar/sidebar.mjs +24 -20
- package/dist/components/sidebar/sidebar.recipe.mjs +1 -6
- package/dist/components/tooltip/tooltip.mjs +8 -5
- package/dist/components/virtual-list/VirtualList.mjs +1 -1
- package/dist/index.d.mts +7 -3
- package/dist/index.mjs +5 -2
- package/dist/lib/inline-part.mjs +13 -0
- package/dist/lib/use-merged-ref.mjs +73 -47
- package/dist/lib/use-platform.d.mts +13 -0
- package/dist/lib/use-platform.mjs +29 -0
- package/dist/styles.css +1 -0
- package/dist/wip.d.mts +5 -1
- package/dist/wip.mjs +3 -1
- package/package.json +1 -1
- 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,
|
|
14
|
+
export declare function SidebarContent({ children, className, ...props }: SidebarContentProps): import("react").JSX.Element;
|
|
15
15
|
//#endregion
|