@allxsmith/bestax-bulma 5.47.0 → 5.48.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/dist/bestax.css +1 -1
- package/dist/bestax.css.map +1 -1
- package/dist/esm/components/ScrollArea.js +225 -0
- package/dist/esm/components/ScrollArea.js.map +1 -0
- package/dist/extras.css +1 -1
- package/dist/extras.css.map +1 -1
- package/dist/index.cjs +215 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.esm.js +1 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/types/components/ScrollArea.d.ts +105 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types-cjs/components/ScrollArea.d.ts +105 -0
- package/dist/types-cjs/index.d.ts +1 -0
- package/dist/versions/bestax-no-dark-mode.css +1 -1
- package/dist/versions/bestax-no-dark-mode.css.map +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css +1 -1
- package/dist/versions/bestax-no-helpers-prefixed.css.map +1 -1
- package/dist/versions/bestax-no-helpers.css +1 -1
- package/dist/versions/bestax-no-helpers.css.map +1 -1
- package/dist/versions/bestax-prefixed.css +1 -1
- package/dist/versions/bestax-prefixed.css.map +1 -1
- package/package.json +1 -1
- package/src/scss/components/_index.scss +1 -0
- package/src/scss/components/_scrollarea.scss +248 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/* @allxsmith/bestax-bulma — AI agents: see AGENTS.md in the package root, or https://bestax.io/llms.txt */
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { forwardRef, useEffect, useRef, useCallback, useState } from 'react';
|
|
4
|
+
import { usePrefixedClassNames, classNames } from '../helpers/classNames.js';
|
|
5
|
+
import { useBulmaClasses } from '../helpers/useBulmaClasses.js';
|
|
6
|
+
import { useClientLayoutEffect } from '../helpers/useClientLayoutEffect.js';
|
|
7
|
+
import { useLatestRef } from '../helpers/useLatestRef.js';
|
|
8
|
+
import { warnOnce } from '../helpers/devWarnings.js';
|
|
9
|
+
|
|
10
|
+
const EDGES = ['top', 'bottom', 'start', 'end'];
|
|
11
|
+
// Layout can land a fraction of a pixel short of an edge, so anything closer
|
|
12
|
+
// than this counts as there.
|
|
13
|
+
const SLACK = 1;
|
|
14
|
+
const sameMeasure = (a, b) => !!a &&
|
|
15
|
+
a.overflowing === b.overflowing &&
|
|
16
|
+
a.barInline === b.barInline &&
|
|
17
|
+
a.barBlock === b.barBlock &&
|
|
18
|
+
EDGES.every(edge => a.edges[edge] === b.edges[edge]);
|
|
19
|
+
/**
|
|
20
|
+
* The `ScrollArea` component renders a box whose content scrolls inside it, on the browser's own scrollbars in Bulma's colors.
|
|
21
|
+
*
|
|
22
|
+
* The bars are the system's, made thin and colored from Bulma's tokens, so
|
|
23
|
+
* they follow dark mode and keep the system's own settings. While the
|
|
24
|
+
* content overflows, the viewport is a region you can tab to and scroll by
|
|
25
|
+
* keyboard, Safari included, named by `aria-label` or `aria-labelledby`.
|
|
26
|
+
* While it fits, it's a plain box. Setting the bars' colors turns off any
|
|
27
|
+
* `::-webkit-scrollbar` styles inside it.
|
|
28
|
+
*
|
|
29
|
+
* @function
|
|
30
|
+
* @param {ScrollAreaProps} props - Props for the ScrollArea component.
|
|
31
|
+
* @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the viewport.
|
|
32
|
+
* @returns {JSX.Element} The rendered scroll area.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* <ScrollArea height={240} aria-label="Release notes">
|
|
36
|
+
* <Content>…</Content>
|
|
37
|
+
* </ScrollArea>
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* <ScrollArea maxHeight="20rem" aria-label="Messages" hasFade>
|
|
41
|
+
* …
|
|
42
|
+
* </ScrollArea>
|
|
43
|
+
*/
|
|
44
|
+
const ScrollArea = forwardRef(({ className, style, textColor, bgColor, height, maxHeight, orientation = 'both', showScrollbars = 'auto', hasStableGutter, hasFade, onEdgeReach, onScroll, onFocus, onBlur, children, ...props }, ref) => {
|
|
45
|
+
const { bulmaHelperClasses, rest } = useBulmaClasses({
|
|
46
|
+
color: textColor,
|
|
47
|
+
backgroundColor: bgColor,
|
|
48
|
+
...props,
|
|
49
|
+
});
|
|
50
|
+
const frameClass = usePrefixedClassNames('scroll-area', {
|
|
51
|
+
'is-vertical': orientation === 'vertical',
|
|
52
|
+
'is-horizontal': orientation === 'horizontal',
|
|
53
|
+
'has-hover-scrollbars': showScrollbars === 'hover',
|
|
54
|
+
'has-visible-scrollbars': showScrollbars === 'always',
|
|
55
|
+
'has-stable-gutter': !!hasStableGutter,
|
|
56
|
+
'has-fade': !!hasFade,
|
|
57
|
+
});
|
|
58
|
+
const viewportClass = usePrefixedClassNames('scroll-area-viewport');
|
|
59
|
+
const named = Boolean(rest['aria-label'] || rest['aria-labelledby']);
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (!named) {
|
|
62
|
+
warnOnce('ScrollArea:unnamed', '[bestax-bulma] <ScrollArea> has no accessible name. Pass ' +
|
|
63
|
+
'aria-label or aria-labelledby, so a screen reader can say what ' +
|
|
64
|
+
'took focus when the area overflows.');
|
|
65
|
+
}
|
|
66
|
+
}, [named]);
|
|
67
|
+
const viewportRef = useRef(null);
|
|
68
|
+
const setRefs = useCallback((node) => {
|
|
69
|
+
viewportRef.current = node;
|
|
70
|
+
if (typeof ref === 'function')
|
|
71
|
+
return ref(node);
|
|
72
|
+
if (ref)
|
|
73
|
+
ref.current = node;
|
|
74
|
+
}, [ref]);
|
|
75
|
+
// Nothing is known about the overflow until the client measures it, so
|
|
76
|
+
// the server renders a plain scroller and hydrates as one.
|
|
77
|
+
const [measured, setMeasured] = useState(null);
|
|
78
|
+
const [focused, setFocused] = useState(false);
|
|
79
|
+
// The last measurement, read by the scroll handler to see which edges
|
|
80
|
+
// it just reached, and the direction and bar sizes, which only change
|
|
81
|
+
// with layout, so a scroll doesn't read computed styles.
|
|
82
|
+
const lastRef = useRef(null);
|
|
83
|
+
const layoutRef = useRef({ rtl: false, barInline: 0, barBlock: 0 });
|
|
84
|
+
const onEdgeReachRef = useLatestRef(onEdgeReach);
|
|
85
|
+
const measure = useCallback((fromScroll) => {
|
|
86
|
+
const el = viewportRef.current;
|
|
87
|
+
/* istanbul ignore next: measuring only runs while it's mounted */
|
|
88
|
+
if (!el)
|
|
89
|
+
return;
|
|
90
|
+
const scrollsY = orientation !== 'horizontal';
|
|
91
|
+
const scrollsX = orientation !== 'vertical';
|
|
92
|
+
const maxTop = el.scrollHeight - el.clientHeight;
|
|
93
|
+
const maxLeft = el.scrollWidth - el.clientWidth;
|
|
94
|
+
const overflowY = scrollsY && maxTop > SLACK;
|
|
95
|
+
const overflowX = scrollsX && maxLeft > SLACK;
|
|
96
|
+
// Right to left, scrollLeft runs from 0 at the start to negative.
|
|
97
|
+
const fromStart = layoutRef.current.rtl
|
|
98
|
+
? -el.scrollLeft
|
|
99
|
+
: el.scrollLeft;
|
|
100
|
+
const next = {
|
|
101
|
+
overflowing: overflowY || overflowX,
|
|
102
|
+
edges: {
|
|
103
|
+
top: overflowY && el.scrollTop > SLACK,
|
|
104
|
+
bottom: overflowY && maxTop - el.scrollTop > SLACK,
|
|
105
|
+
start: overflowX && fromStart > SLACK,
|
|
106
|
+
end: overflowX && maxLeft - fromStart > SLACK,
|
|
107
|
+
},
|
|
108
|
+
barInline: layoutRef.current.barInline,
|
|
109
|
+
barBlock: layoutRef.current.barBlock,
|
|
110
|
+
};
|
|
111
|
+
const last = lastRef.current;
|
|
112
|
+
lastRef.current = next;
|
|
113
|
+
if (fromScroll && last) {
|
|
114
|
+
for (const edge of EDGES) {
|
|
115
|
+
if (last.edges[edge] && !next.edges[edge]) {
|
|
116
|
+
onEdgeReachRef.current?.(edge);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
setMeasured(prev => (sameMeasure(prev, next) ? prev : next));
|
|
121
|
+
}, [orientation, onEdgeReachRef]);
|
|
122
|
+
// Layout: the direction and the bars' sizes, then the overflow. It runs
|
|
123
|
+
// before paint, and again whenever the viewport or anything in it
|
|
124
|
+
// changes size, or the children change.
|
|
125
|
+
useClientLayoutEffect(() => {
|
|
126
|
+
const el = viewportRef.current;
|
|
127
|
+
/* istanbul ignore next: the effect only runs once it's mounted */
|
|
128
|
+
if (!el)
|
|
129
|
+
return;
|
|
130
|
+
const relayout = () => {
|
|
131
|
+
const cs = getComputedStyle(el);
|
|
132
|
+
const px = (value) => parseFloat(value) || 0;
|
|
133
|
+
layoutRef.current = {
|
|
134
|
+
rtl: cs.direction === 'rtl',
|
|
135
|
+
barInline: Math.max(0, el.offsetWidth -
|
|
136
|
+
el.clientWidth -
|
|
137
|
+
px(cs.borderLeftWidth) -
|
|
138
|
+
px(cs.borderRightWidth)),
|
|
139
|
+
barBlock: Math.max(0, el.offsetHeight -
|
|
140
|
+
el.clientHeight -
|
|
141
|
+
px(cs.borderTopWidth) -
|
|
142
|
+
px(cs.borderBottomWidth)),
|
|
143
|
+
};
|
|
144
|
+
measure(false);
|
|
145
|
+
};
|
|
146
|
+
relayout();
|
|
147
|
+
if (typeof ResizeObserver === 'undefined')
|
|
148
|
+
return;
|
|
149
|
+
const resizes = new ResizeObserver(relayout);
|
|
150
|
+
const observeAll = () => {
|
|
151
|
+
resizes.disconnect();
|
|
152
|
+
resizes.observe(el);
|
|
153
|
+
for (const child of Array.from(el.children))
|
|
154
|
+
resizes.observe(child);
|
|
155
|
+
};
|
|
156
|
+
observeAll();
|
|
157
|
+
// Content that changes can overflow before any size is reported: a
|
|
158
|
+
// child added or taken away, or text React rewrites in place, which
|
|
159
|
+
// resizes no element when the area's height is fixed, and inline
|
|
160
|
+
// children, which a ResizeObserver doesn't report at all.
|
|
161
|
+
const mutations = typeof MutationObserver === 'undefined'
|
|
162
|
+
? null
|
|
163
|
+
: new MutationObserver(records => {
|
|
164
|
+
if (records.some(record => record.target === el && record.type === 'childList')) {
|
|
165
|
+
observeAll();
|
|
166
|
+
}
|
|
167
|
+
relayout();
|
|
168
|
+
});
|
|
169
|
+
mutations?.observe(el, {
|
|
170
|
+
childList: true,
|
|
171
|
+
characterData: true,
|
|
172
|
+
subtree: true,
|
|
173
|
+
});
|
|
174
|
+
return () => {
|
|
175
|
+
resizes.disconnect();
|
|
176
|
+
mutations?.disconnect();
|
|
177
|
+
};
|
|
178
|
+
}, [measure]);
|
|
179
|
+
const handleScroll = (event) => {
|
|
180
|
+
onScroll?.(event);
|
|
181
|
+
measure(true);
|
|
182
|
+
};
|
|
183
|
+
const handleFocus = (event) => {
|
|
184
|
+
onFocus?.(event);
|
|
185
|
+
if (event.target === event.currentTarget)
|
|
186
|
+
setFocused(true);
|
|
187
|
+
};
|
|
188
|
+
const handleBlur = (event) => {
|
|
189
|
+
onBlur?.(event);
|
|
190
|
+
if (event.target === event.currentTarget)
|
|
191
|
+
setFocused(false);
|
|
192
|
+
};
|
|
193
|
+
// A tab stop while it overflows. Taking tabIndex off the focused element
|
|
194
|
+
// would drop focus to the page, so a viewport that stops overflowing
|
|
195
|
+
// keeps it until focus leaves.
|
|
196
|
+
const isRegion = !!measured?.overflowing || focused;
|
|
197
|
+
// The name goes with the role: ARIA prohibits naming a plain div.
|
|
198
|
+
const { 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledby, ...viewportRest } = rest;
|
|
199
|
+
const edgeAttributes = {};
|
|
200
|
+
if (measured) {
|
|
201
|
+
for (const edge of EDGES) {
|
|
202
|
+
if (measured.edges[edge])
|
|
203
|
+
edgeAttributes[`data-overflow-${edge}`] = '';
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const viewportStyle = measured
|
|
207
|
+
? {
|
|
208
|
+
'--scroll-area-bar-inline-size': `${measured.barInline}px`,
|
|
209
|
+
'--scroll-area-bar-block-size': `${measured.barBlock}px`,
|
|
210
|
+
}
|
|
211
|
+
: undefined;
|
|
212
|
+
return (jsx("div", { className: classNames(frameClass, bulmaHelperClasses, className), style: height !== undefined || maxHeight !== undefined
|
|
213
|
+
? {
|
|
214
|
+
...style,
|
|
215
|
+
// Only what's given, so `maxHeight` alone keeps a height
|
|
216
|
+
// the style sets.
|
|
217
|
+
...(height !== undefined && { height }),
|
|
218
|
+
...(maxHeight !== undefined && { maxHeight }),
|
|
219
|
+
}
|
|
220
|
+
: style, children: jsx("div", { ref: setRefs, className: viewportClass, style: viewportStyle, ...viewportRest, ...edgeAttributes, role: isRegion ? 'region' : undefined, "aria-label": isRegion ? ariaLabel : undefined, "aria-labelledby": isRegion ? ariaLabelledby : undefined, tabIndex: isRegion ? 0 : undefined, onScroll: handleScroll, onFocus: handleFocus, onBlur: handleBlur, children: children }) }));
|
|
221
|
+
});
|
|
222
|
+
ScrollArea.displayName = 'ScrollArea';
|
|
223
|
+
|
|
224
|
+
export { ScrollArea };
|
|
225
|
+
//# sourceMappingURL=ScrollArea.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollArea.js","sources":["../../../src/components/ScrollArea.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport { classNames, usePrefixedClassNames } from '../helpers/classNames';\nimport {\n useBulmaClasses,\n BulmaClassesProps,\n validColors,\n} from '../helpers/useBulmaClasses';\nimport { useClientLayoutEffect } from '../helpers/useClientLayoutEffect';\nimport { useLatestRef } from '../helpers/useLatestRef';\nimport { warnOnce } from '../helpers/devWarnings';\n\n/** Which way the content scrolls. */\nexport type ScrollAreaOrientation = 'vertical' | 'horizontal' | 'both';\n\n/** When the scrollbars show. */\nexport type ScrollAreaScrollbars = 'auto' | 'hover' | 'always';\n\n/** An edge of the content: the block's top and bottom, the line's start and end. */\nexport type ScrollAreaEdge = 'top' | 'bottom' | 'start' | 'end';\n\nconst EDGES: readonly ScrollAreaEdge[] = ['top', 'bottom', 'start', 'end'];\n\n// Layout can land a fraction of a pixel short of an edge, so anything closer\n// than this counts as there.\nconst SLACK = 1;\n\n/**\n * Props for the ScrollArea component.\n *\n * @helperProps The helper props go on the outer `<div>`, the `.scroll-area`\n * frame, with `className` and `style`. The `ref`, the name, `onScroll` and\n * the other attributes go on the `<div>` inside it, the viewport that\n * scrolls.\n *\n * @extraProp {React.Ref<HTMLDivElement>} [ref] - Ref forwarded to the viewport, the element that scrolls, for `scrollTo` and the like.\n * @extraProp {string} [aria-label] - Names the region, such as \"Release notes\". It needs a name, so pass this or `aria-labelledby`, and a development build warns when it has neither. The name is rendered with the region's role, while the content overflows, since ARIA doesn't allow naming a plain `div`.\n * @extraProp {string} [aria-labelledby] - The id of a visible heading that names the region, in place of `aria-label`.\n * @extraProp {React.UIEventHandler<HTMLDivElement>} [onScroll] - Called as the viewport scrolls.\n */\nexport interface ScrollAreaProps\n extends\n Omit<React.HTMLAttributes<HTMLDivElement>, 'color' | 'role' | 'tabIndex'>,\n Omit<BulmaClassesProps, 'color' | 'backgroundColor'> {\n /** Additional CSS classes for the outer frame. */\n className?: string;\n /** Inline styles for the outer frame. */\n style?: React.CSSProperties;\n /** What scrolls. */\n children?: React.ReactNode;\n /**\n * The area's height. A number is pixels, and a string is any CSS length\n * (`\"20rem\"`, `\"50vh\"`, `\"100%\"`).\n */\n height?: number | string;\n /**\n * The height it grows to with its content, then scrolls past. A number is\n * pixels. Use it in place of `height` for an area that's only as tall as\n * it needs to be.\n */\n maxHeight?: number | string;\n /**\n * Which way it scrolls. `vertical` and `horizontal` clip the other axis,\n * and `both` scrolls whichever overflows.\n */\n orientation?: ScrollAreaOrientation;\n /**\n * When the scrollbars show. `auto`: whenever the content overflows, the\n * way the system shows them. `hover`: colorless until the pointer is over\n * the area or focus is in it, which hides that the area scrolls until\n * then, so keep it for places where the layout makes that plain.\n * `always`: on every axis it scrolls, even when the content fits. Where the\n * system draws its bars over the content (macOS by default, phones and\n * tablets), they still show only while you scroll, and in forced colors\n * they always show.\n */\n showScrollbars?: ScrollAreaScrollbars;\n /**\n * Keeps room for the vertical scrollbar whether or not it shows, so the\n * content doesn't shift when it starts to overflow. It does nothing where\n * the system draws its bars over the content.\n */\n hasStableGutter?: boolean;\n /**\n * Fades the content out toward each edge that has more past it, without\n * fading the scrollbar. It's off in forced colors and when the user asks\n * for more contrast.\n */\n hasFade?: boolean;\n /**\n * Called with the edge when scrolling reaches it, once each time it\n * arrives, such as to load older messages at the top. It isn't called for\n * the edge the area starts at, or when the content changes size.\n */\n onEdgeReach?: (edge: ScrollAreaEdge) => void;\n /** Text color helper. */\n textColor?: (typeof validColors)[number] | 'inherit' | 'current';\n /** Background color helper. */\n bgColor?: (typeof validColors)[number] | 'inherit' | 'current';\n}\n\n/** What a measurement of the viewport found. */\ninterface Measured {\n /** Overflows on an axis it scrolls. */\n overflowing: boolean;\n /** Each edge that has more content past it. */\n edges: Record<ScrollAreaEdge, boolean>;\n /** The vertical scrollbar's width, and the horizontal one's height. */\n barInline: number;\n barBlock: number;\n}\n\nconst sameMeasure = (a: Measured | null, b: Measured): boolean =>\n !!a &&\n a.overflowing === b.overflowing &&\n a.barInline === b.barInline &&\n a.barBlock === b.barBlock &&\n EDGES.every(edge => a.edges[edge] === b.edges[edge]);\n\n/**\n * The `ScrollArea` component renders a box whose content scrolls inside it, on the browser's own scrollbars in Bulma's colors.\n *\n * The bars are the system's, made thin and colored from Bulma's tokens, so\n * they follow dark mode and keep the system's own settings. While the\n * content overflows, the viewport is a region you can tab to and scroll by\n * keyboard, Safari included, named by `aria-label` or `aria-labelledby`.\n * While it fits, it's a plain box. Setting the bars' colors turns off any\n * `::-webkit-scrollbar` styles inside it.\n *\n * @function\n * @param {ScrollAreaProps} props - Props for the ScrollArea component.\n * @param {React.Ref<HTMLDivElement>} ref - Forwarded ref to the viewport.\n * @returns {JSX.Element} The rendered scroll area.\n *\n * @example\n * <ScrollArea height={240} aria-label=\"Release notes\">\n * <Content>…</Content>\n * </ScrollArea>\n *\n * @example\n * <ScrollArea maxHeight=\"20rem\" aria-label=\"Messages\" hasFade>\n * …\n * </ScrollArea>\n */\nexport const ScrollArea = forwardRef<HTMLDivElement, ScrollAreaProps>(\n (\n {\n className,\n style,\n textColor,\n bgColor,\n height,\n maxHeight,\n orientation = 'both',\n showScrollbars = 'auto',\n hasStableGutter,\n hasFade,\n onEdgeReach,\n onScroll,\n onFocus,\n onBlur,\n children,\n ...props\n },\n ref\n ) => {\n const { bulmaHelperClasses, rest } = useBulmaClasses({\n color: textColor,\n backgroundColor: bgColor,\n ...props,\n });\n\n const frameClass = usePrefixedClassNames('scroll-area', {\n 'is-vertical': orientation === 'vertical',\n 'is-horizontal': orientation === 'horizontal',\n 'has-hover-scrollbars': showScrollbars === 'hover',\n 'has-visible-scrollbars': showScrollbars === 'always',\n 'has-stable-gutter': !!hasStableGutter,\n 'has-fade': !!hasFade,\n });\n const viewportClass = usePrefixedClassNames('scroll-area-viewport');\n\n const named = Boolean(rest['aria-label'] || rest['aria-labelledby']);\n useEffect(() => {\n if (!named) {\n warnOnce(\n 'ScrollArea:unnamed',\n '[bestax-bulma] <ScrollArea> has no accessible name. Pass ' +\n 'aria-label or aria-labelledby, so a screen reader can say what ' +\n 'took focus when the area overflows.'\n );\n }\n }, [named]);\n\n const viewportRef = useRef<HTMLDivElement | null>(null);\n const setRefs = useCallback(\n (node: HTMLDivElement | null) => {\n viewportRef.current = node;\n if (typeof ref === 'function') return ref(node);\n if (ref) ref.current = node;\n },\n [ref]\n );\n\n // Nothing is known about the overflow until the client measures it, so\n // the server renders a plain scroller and hydrates as one.\n const [measured, setMeasured] = useState<Measured | null>(null);\n const [focused, setFocused] = useState(false);\n // The last measurement, read by the scroll handler to see which edges\n // it just reached, and the direction and bar sizes, which only change\n // with layout, so a scroll doesn't read computed styles.\n const lastRef = useRef<Measured | null>(null);\n const layoutRef = useRef({ rtl: false, barInline: 0, barBlock: 0 });\n const onEdgeReachRef = useLatestRef(onEdgeReach);\n\n const measure = useCallback(\n (fromScroll: boolean) => {\n const el = viewportRef.current;\n /* istanbul ignore next: measuring only runs while it's mounted */\n if (!el) return;\n const scrollsY = orientation !== 'horizontal';\n const scrollsX = orientation !== 'vertical';\n const maxTop = el.scrollHeight - el.clientHeight;\n const maxLeft = el.scrollWidth - el.clientWidth;\n const overflowY = scrollsY && maxTop > SLACK;\n const overflowX = scrollsX && maxLeft > SLACK;\n // Right to left, scrollLeft runs from 0 at the start to negative.\n const fromStart = layoutRef.current.rtl\n ? -el.scrollLeft\n : el.scrollLeft;\n const next: Measured = {\n overflowing: overflowY || overflowX,\n edges: {\n top: overflowY && el.scrollTop > SLACK,\n bottom: overflowY && maxTop - el.scrollTop > SLACK,\n start: overflowX && fromStart > SLACK,\n end: overflowX && maxLeft - fromStart > SLACK,\n },\n barInline: layoutRef.current.barInline,\n barBlock: layoutRef.current.barBlock,\n };\n const last = lastRef.current;\n lastRef.current = next;\n if (fromScroll && last) {\n for (const edge of EDGES) {\n if (last.edges[edge] && !next.edges[edge]) {\n onEdgeReachRef.current?.(edge);\n }\n }\n }\n setMeasured(prev => (sameMeasure(prev, next) ? prev : next));\n },\n [orientation, onEdgeReachRef]\n );\n\n // Layout: the direction and the bars' sizes, then the overflow. It runs\n // before paint, and again whenever the viewport or anything in it\n // changes size, or the children change.\n useClientLayoutEffect(() => {\n const el = viewportRef.current;\n /* istanbul ignore next: the effect only runs once it's mounted */\n if (!el) return;\n const relayout = () => {\n const cs = getComputedStyle(el);\n const px = (value: string) => parseFloat(value) || 0;\n layoutRef.current = {\n rtl: cs.direction === 'rtl',\n barInline: Math.max(\n 0,\n el.offsetWidth -\n el.clientWidth -\n px(cs.borderLeftWidth) -\n px(cs.borderRightWidth)\n ),\n barBlock: Math.max(\n 0,\n el.offsetHeight -\n el.clientHeight -\n px(cs.borderTopWidth) -\n px(cs.borderBottomWidth)\n ),\n };\n measure(false);\n };\n relayout();\n if (typeof ResizeObserver === 'undefined') return;\n const resizes = new ResizeObserver(relayout);\n const observeAll = () => {\n resizes.disconnect();\n resizes.observe(el);\n for (const child of Array.from(el.children)) resizes.observe(child);\n };\n observeAll();\n // Content that changes can overflow before any size is reported: a\n // child added or taken away, or text React rewrites in place, which\n // resizes no element when the area's height is fixed, and inline\n // children, which a ResizeObserver doesn't report at all.\n const mutations =\n typeof MutationObserver === 'undefined'\n ? null\n : new MutationObserver(records => {\n if (\n records.some(\n record => record.target === el && record.type === 'childList'\n )\n ) {\n observeAll();\n }\n relayout();\n });\n mutations?.observe(el, {\n childList: true,\n characterData: true,\n subtree: true,\n });\n return () => {\n resizes.disconnect();\n mutations?.disconnect();\n };\n }, [measure]);\n\n const handleScroll = (event: React.UIEvent<HTMLDivElement>) => {\n onScroll?.(event);\n measure(true);\n };\n const handleFocus = (event: React.FocusEvent<HTMLDivElement>) => {\n onFocus?.(event);\n if (event.target === event.currentTarget) setFocused(true);\n };\n const handleBlur = (event: React.FocusEvent<HTMLDivElement>) => {\n onBlur?.(event);\n if (event.target === event.currentTarget) setFocused(false);\n };\n\n // A tab stop while it overflows. Taking tabIndex off the focused element\n // would drop focus to the page, so a viewport that stops overflowing\n // keeps it until focus leaves.\n const isRegion = !!measured?.overflowing || focused;\n // The name goes with the role: ARIA prohibits naming a plain div.\n const {\n 'aria-label': ariaLabel,\n 'aria-labelledby': ariaLabelledby,\n ...viewportRest\n } = rest;\n const edgeAttributes: Record<string, string> = {};\n if (measured) {\n for (const edge of EDGES) {\n if (measured.edges[edge]) edgeAttributes[`data-overflow-${edge}`] = '';\n }\n }\n const viewportStyle = measured\n ? ({\n '--scroll-area-bar-inline-size': `${measured.barInline}px`,\n '--scroll-area-bar-block-size': `${measured.barBlock}px`,\n } as React.CSSProperties)\n : undefined;\n\n return (\n <div\n className={classNames(frameClass, bulmaHelperClasses, className)}\n style={\n height !== undefined || maxHeight !== undefined\n ? {\n ...style,\n // Only what's given, so `maxHeight` alone keeps a height\n // the style sets.\n ...(height !== undefined && { height }),\n ...(maxHeight !== undefined && { maxHeight }),\n }\n : style\n }\n >\n <div\n ref={setRefs}\n className={viewportClass}\n style={viewportStyle}\n {...viewportRest}\n {...edgeAttributes}\n role={isRegion ? 'region' : undefined}\n aria-label={isRegion ? ariaLabel : undefined}\n aria-labelledby={isRegion ? ariaLabelledby : undefined}\n tabIndex={isRegion ? 0 : undefined}\n onScroll={handleScroll}\n onFocus={handleFocus}\n onBlur={handleBlur}\n >\n {children}\n </div>\n </div>\n );\n }\n);\n\nScrollArea.displayName = 'ScrollArea';\n\nexport default ScrollArea;\n"],"names":["_jsx"],"mappings":"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"}
|