@mui/internal-docs-infra 0.11.1-canary.16 → 0.11.1-canary.17
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mui/internal-docs-infra",
|
|
3
|
-
"version": "0.11.1-canary.
|
|
3
|
+
"version": "0.11.1-canary.17",
|
|
4
4
|
"author": "MUI Team",
|
|
5
5
|
"description": "MUI Infra - internal documentation creation tools.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -768,5 +768,5 @@
|
|
|
768
768
|
"bin": {
|
|
769
769
|
"docs-infra": "./cli/index.mjs"
|
|
770
770
|
},
|
|
771
|
-
"gitSha": "
|
|
771
|
+
"gitSha": "5f0871264ec4d7c78d5fd7e62888de0f7bf61cda"
|
|
772
772
|
}
|
|
@@ -1146,12 +1146,19 @@ export const createEditableEngine = ctx => {
|
|
|
1146
1146
|
state.repeatFlushId = null;
|
|
1147
1147
|
// The user may have moved focus or cleared the selection in the
|
|
1148
1148
|
// 100ms since the last repeat keydown (e.g. clicked elsewhere,
|
|
1149
|
-
// unmounted, blurred). The debounced flush is best-effort; if
|
|
1150
|
-
// there's no live selection inside the editable
|
|
1151
|
-
// — the next real event will pick up state.
|
|
1152
|
-
//
|
|
1149
|
+
// unmounted, blurred). The debounced flush is best-effort; if the
|
|
1150
|
+
// engine is gone or there's no live selection inside the editable
|
|
1151
|
+
// any more, skip — the next real event will pick up state.
|
|
1152
|
+
//
|
|
1153
|
+
// Bail out before touching `window`: a stray timer can fire after
|
|
1154
|
+
// teardown, and in a test environment the `window` global may already
|
|
1155
|
+
// be removed, so `window.getSelection()` would throw a `ReferenceError`
|
|
1156
|
+
// (an unhandled rejection that can mask real failures).
|
|
1157
|
+
if (state.disconnected || typeof window === 'undefined') {
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
1153
1160
|
const selection = window.getSelection();
|
|
1154
|
-
if (
|
|
1161
|
+
if (!selection || selection.rangeCount === 0 || !element.contains(selection.getRangeAt(0).startContainer)) {
|
|
1155
1162
|
return;
|
|
1156
1163
|
}
|
|
1157
1164
|
flushChanges();
|
|
@@ -48,6 +48,14 @@ export type UseCodeWindowResult<ToggleElement extends HTMLElement = HTMLElement,
|
|
|
48
48
|
* so the anchor stays put against the panel's own scroll rather than the
|
|
49
49
|
* page. When left unattached, the page is compensated — the right default
|
|
50
50
|
* for code that grows the document flow. Forwarded from `useScrollAnchor`.
|
|
51
|
+
*
|
|
52
|
+
* When attached, this element is also treated as the horizontal scroll
|
|
53
|
+
* owner: the scrollbar-gutter swap (`data-scrollbar-gutter`) and the
|
|
54
|
+
* collapse scroll-back run on it instead of the inner `<pre>`. Use this when
|
|
55
|
+
* the window owns both scroll axes so the horizontal scrollbar sits at the
|
|
56
|
+
* window's edge (in view) rather than at the bottom of the inner `<pre>`,
|
|
57
|
+
* which can extend past the window's height and scroll out of view. Your
|
|
58
|
+
* gutter CSS must then key off this element's attribute.
|
|
51
59
|
*/
|
|
52
60
|
scrollContainerRef: React.RefObject<ScrollElement | null>;
|
|
53
61
|
/**
|
|
@@ -53,33 +53,38 @@ function cancelScheduled(handle) {
|
|
|
53
53
|
* Smoothly slides the `<code>` element back to the left edge over `duration`
|
|
54
54
|
* ms using an ease-out cubic via the Web Animations API.
|
|
55
55
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
56
|
+
* `scrollEl` is whichever element owns the horizontal scroll — the inner
|
|
57
|
+
* `<pre>` by default, or an attached scroll container (see `scrollContainerRef`)
|
|
58
|
+
* when the code block is rendered inside a fixed-size window. `code` is this
|
|
59
|
+
* code window's own `<code>` (scoped to its container by the caller) so a shared
|
|
60
|
+
* scroll container holding several blocks animates the right one.
|
|
61
|
+
*
|
|
62
|
+
* Used during collapse instead of tweening `scrollEl.scrollLeft` because the
|
|
63
|
+
* scrollbar-gutter animation forces `overflow-x: hidden` on `scrollEl`, which
|
|
58
64
|
* snaps `scrollLeft` to 0 instantly. Animating a transform on the inner
|
|
59
65
|
* `code` element produces the same visual effect, isn't reset by the overflow
|
|
60
|
-
* change, and is naturally clipped by the
|
|
66
|
+
* change, and is naturally clipped by the scroll element's hidden overflow.
|
|
61
67
|
*
|
|
62
68
|
* Honors `prefers-reduced-motion` by snapping immediately.
|
|
63
69
|
*/
|
|
64
|
-
function smoothCollapseScrollLeft(
|
|
65
|
-
const startLeft =
|
|
70
|
+
function smoothCollapseScrollLeft(scrollEl, code, duration) {
|
|
71
|
+
const startLeft = scrollEl.scrollLeft;
|
|
66
72
|
if (startLeft <= 0) {
|
|
67
73
|
return null;
|
|
68
74
|
}
|
|
69
|
-
const code = pre.querySelector('code');
|
|
70
|
-
if (!code || typeof code.animate !== 'function') {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
75
|
|
|
74
76
|
// Cancel any leftover scroll-back animation from a previous toggle so we
|
|
75
77
|
// don't end up with two transforms competing on the same element.
|
|
76
|
-
scrollbackAnimations.get(
|
|
77
|
-
scrollbackAnimations.delete(
|
|
78
|
+
scrollbackAnimations.get(scrollEl)?.cancel();
|
|
79
|
+
scrollbackAnimations.delete(scrollEl);
|
|
78
80
|
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// Snap the actual scroll position back to the left edge now. When we can
|
|
82
|
+
// animate, the WAAPI transform below visually compensates by translating the
|
|
83
|
+
// element from `-startLeft` back to `0`; otherwise (no WAAPI, no `code`,
|
|
84
|
+
// reduced motion, or zero duration) this stands as an instant snap — still
|
|
85
|
+
// the correct collapsed end state.
|
|
86
|
+
scrollEl.scrollLeft = 0;
|
|
87
|
+
if (!code || typeof code.animate !== 'function' || prefersReducedMotion() || duration <= 0) {
|
|
83
88
|
return null;
|
|
84
89
|
}
|
|
85
90
|
const anim = code.animate([{
|
|
@@ -91,10 +96,10 @@ function smoothCollapseScrollLeft(pre, duration) {
|
|
|
91
96
|
easing: 'cubic-bezier(0, 0, 0.2, 1)',
|
|
92
97
|
fill: 'none'
|
|
93
98
|
});
|
|
94
|
-
scrollbackAnimations.set(
|
|
99
|
+
scrollbackAnimations.set(scrollEl, anim);
|
|
95
100
|
const onSettle = () => {
|
|
96
|
-
if (scrollbackAnimations.get(
|
|
97
|
-
scrollbackAnimations.delete(
|
|
101
|
+
if (scrollbackAnimations.get(scrollEl) === anim) {
|
|
102
|
+
scrollbackAnimations.delete(scrollEl);
|
|
98
103
|
}
|
|
99
104
|
};
|
|
100
105
|
anim.finished.then(onSettle, onSettle);
|
|
@@ -106,74 +111,76 @@ function isElementInViewport(element) {
|
|
|
106
111
|
}
|
|
107
112
|
|
|
108
113
|
/**
|
|
109
|
-
* Measures the horizontal scrollbar height of
|
|
114
|
+
* Measures the horizontal scrollbar height of the scroll element by
|
|
110
115
|
* temporarily forcing `overflow-x: scroll`.
|
|
111
116
|
*/
|
|
112
|
-
function measureScrollbarHeight(
|
|
113
|
-
const prevOverflow =
|
|
114
|
-
|
|
115
|
-
const scrollbarHeight =
|
|
116
|
-
|
|
117
|
+
function measureScrollbarHeight(scrollEl) {
|
|
118
|
+
const prevOverflow = scrollEl.style.overflowX;
|
|
119
|
+
scrollEl.style.overflowX = 'scroll';
|
|
120
|
+
const scrollbarHeight = scrollEl.offsetHeight - scrollEl.clientHeight;
|
|
121
|
+
scrollEl.style.overflowX = prevOverflow;
|
|
117
122
|
return scrollbarHeight;
|
|
118
123
|
}
|
|
119
|
-
function clearGutterState(
|
|
120
|
-
cancelScheduled(gutterCleanupTimers.get(
|
|
121
|
-
gutterCleanupTimers.delete(
|
|
122
|
-
const flipTimer = gutterFlipTimers.get(
|
|
124
|
+
function clearGutterState(scrollEl) {
|
|
125
|
+
cancelScheduled(gutterCleanupTimers.get(scrollEl));
|
|
126
|
+
gutterCleanupTimers.delete(scrollEl);
|
|
127
|
+
const flipTimer = gutterFlipTimers.get(scrollEl);
|
|
123
128
|
if (flipTimer !== undefined) {
|
|
124
129
|
clearTimeout(flipTimer);
|
|
125
|
-
gutterFlipTimers.delete(
|
|
130
|
+
gutterFlipTimers.delete(scrollEl);
|
|
126
131
|
}
|
|
127
|
-
|
|
132
|
+
scrollEl.removeAttribute(GUTTER_STATE_ATTRIBUTE);
|
|
128
133
|
}
|
|
129
|
-
function
|
|
130
|
-
scrollbackAnimations.get(
|
|
131
|
-
scrollbackAnimations.delete(
|
|
132
|
-
clearGutterState(
|
|
134
|
+
function cancelAllForScrollEl(scrollEl) {
|
|
135
|
+
scrollbackAnimations.get(scrollEl)?.cancel();
|
|
136
|
+
scrollbackAnimations.delete(scrollEl);
|
|
137
|
+
clearGutterState(scrollEl);
|
|
133
138
|
}
|
|
134
139
|
|
|
135
140
|
/**
|
|
136
141
|
* Drives a from→to transition on the `data-scrollbar-gutter` attribute of
|
|
137
|
-
*
|
|
138
|
-
* a real scrollbar and equivalent padding-bottom.
|
|
142
|
+
* the scroll element, which the consumer's CSS hooks into to animate the swap
|
|
143
|
+
* between a real scrollbar and equivalent padding-bottom.
|
|
144
|
+
*
|
|
145
|
+
* `scrollEl` is whichever element owns the horizontal scroll — the inner
|
|
146
|
+
* `<pre>` by default, or the attached `scrollContainerRef` when the code block
|
|
147
|
+
* is rendered inside a fixed-size window. `code` is this code window's own
|
|
148
|
+
* `<code>` (scoped to its container by the caller).
|
|
139
149
|
*
|
|
140
150
|
* Skips the animation when content doesn't overflow (no scrollbar exists)
|
|
141
151
|
* or when the browser uses overlay scrollbars (zero height).
|
|
142
152
|
*/
|
|
143
|
-
function animateScrollbarGutter(
|
|
144
|
-
const scrollbarHeight = measureScrollbarHeight(
|
|
153
|
+
function animateScrollbarGutter(scrollEl, code, from, to, durationMs) {
|
|
154
|
+
const scrollbarHeight = measureScrollbarHeight(scrollEl);
|
|
145
155
|
if (scrollbarHeight === 0) {
|
|
146
156
|
return; // Overlay scrollbars, nothing to do
|
|
147
157
|
}
|
|
148
158
|
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
// scrollWidth
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
} else if (pre.scrollWidth <= pre.clientWidth) {
|
|
159
|
+
// Decide from this code window's own `<code>`, not from `scrollEl` — the
|
|
160
|
+
// scroll owner may be a shared container wrapping other content. `code`'s
|
|
161
|
+
// `scrollWidth` reflects hidden frames (via `min-width: fit-content`), so it
|
|
162
|
+
// predicts the post-expand width and still reflects the wide source during
|
|
163
|
+
// collapse; compare it against the scroll owner's visible width.
|
|
164
|
+
if (!code || code.scrollWidth <= scrollEl.clientWidth) {
|
|
158
165
|
return;
|
|
159
166
|
}
|
|
160
|
-
clearGutterState(
|
|
161
|
-
|
|
167
|
+
clearGutterState(scrollEl);
|
|
168
|
+
scrollEl.setAttribute(GUTTER_STATE_ATTRIBUTE, from);
|
|
162
169
|
|
|
163
170
|
// Move into the transition state on the next macrotask. Tracked so the
|
|
164
171
|
// flip can be cancelled if the component unmounts before it fires.
|
|
165
172
|
const flipTimer = setTimeout(() => {
|
|
166
|
-
gutterFlipTimers.delete(
|
|
167
|
-
|
|
173
|
+
gutterFlipTimers.delete(scrollEl);
|
|
174
|
+
scrollEl.setAttribute(GUTTER_STATE_ATTRIBUTE, to);
|
|
168
175
|
}, 0);
|
|
169
|
-
gutterFlipTimers.set(
|
|
176
|
+
gutterFlipTimers.set(scrollEl, flipTimer);
|
|
170
177
|
|
|
171
178
|
// Schedule cleanup on the animation timeline so DevTools throttling
|
|
172
179
|
// scales it together with the CSS transition.
|
|
173
|
-
const cleanup = scheduleOnAnimationTimeline(
|
|
174
|
-
clearGutterState(
|
|
180
|
+
const cleanup = scheduleOnAnimationTimeline(scrollEl, durationMs + 30, () => {
|
|
181
|
+
clearGutterState(scrollEl);
|
|
175
182
|
});
|
|
176
|
-
gutterCleanupTimers.set(
|
|
183
|
+
gutterCleanupTimers.set(scrollEl, cleanup);
|
|
177
184
|
}
|
|
178
185
|
const DEFAULT_ANCHOR_SELECTOR = '[data-frame-type="highlighted"], [data-frame-type="focus"]';
|
|
179
186
|
const DEFAULT_COLLAPSIBLE_SELECTOR = '[data-collapsible]';
|
|
@@ -218,7 +225,7 @@ export function useCodeWindow(options = {}) {
|
|
|
218
225
|
collapsibleProbeSelector = DEFAULT_COLLAPSIBLE_SELECTOR
|
|
219
226
|
} = options;
|
|
220
227
|
const toggleRef = React.useRef(null);
|
|
221
|
-
const
|
|
228
|
+
const lastScrollElRef = React.useRef(null);
|
|
222
229
|
const {
|
|
223
230
|
containerRef,
|
|
224
231
|
scrollContainerRef,
|
|
@@ -226,10 +233,10 @@ export function useCodeWindow(options = {}) {
|
|
|
226
233
|
} = useScrollAnchor();
|
|
227
234
|
React.useEffect(() => {
|
|
228
235
|
return () => {
|
|
229
|
-
const
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
236
|
+
const scrollEl = lastScrollElRef.current;
|
|
237
|
+
if (scrollEl) {
|
|
238
|
+
cancelAllForScrollEl(scrollEl);
|
|
239
|
+
lastScrollElRef.current = null;
|
|
233
240
|
}
|
|
234
241
|
};
|
|
235
242
|
}, []);
|
|
@@ -247,32 +254,42 @@ export function useCodeWindow(options = {}) {
|
|
|
247
254
|
if (!anchor) {
|
|
248
255
|
return;
|
|
249
256
|
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
257
|
+
|
|
258
|
+
// The element whose horizontal scrollbar we smooth: the attached scroll
|
|
259
|
+
// container when one is provided (the code block lives inside a
|
|
260
|
+
// fixed-size window that owns both scroll axes), otherwise the inner
|
|
261
|
+
// `<pre>`, which scrolls horizontally on its own.
|
|
262
|
+
const scrollEl = scrollContainerRef.current ?? container.querySelector('pre');
|
|
263
|
+
// Scope content lookups to *this* code window's `container`, never to
|
|
264
|
+
// `scrollEl`: an attached scroll container may wrap several code blocks or
|
|
265
|
+
// unrelated content, so `scrollEl.querySelector('code')` could match the
|
|
266
|
+
// wrong block. The overflow decision and scroll-back both use this code.
|
|
267
|
+
const code = container.querySelector('code');
|
|
268
|
+
if (scrollEl) {
|
|
269
|
+
lastScrollElRef.current = scrollEl;
|
|
253
270
|
if (direction === 'collapse') {
|
|
254
271
|
// Smoothly return horizontal scroll to the left edge. We animate
|
|
255
272
|
// via a transform on the inner `code` element rather than
|
|
256
|
-
// tweening `
|
|
273
|
+
// tweening `scrollEl.scrollLeft`, because the gutter animation below
|
|
257
274
|
// sets `overflow-x: hidden` which would snap `scrollLeft` to 0
|
|
258
275
|
// instantly. Both animations start in the same frame: the
|
|
259
276
|
// scroll-back resets `scrollLeft` to 0 up front, so the gutter
|
|
260
277
|
// swap's `overflow-x` change has nothing left to snap.
|
|
261
|
-
smoothCollapseScrollLeft(
|
|
262
|
-
animateScrollbarGutter(
|
|
278
|
+
smoothCollapseScrollLeft(scrollEl, code, scrollBackDuration);
|
|
279
|
+
animateScrollbarGutter(scrollEl, code, 'collapse-from', 'collapse-to', collapseDuration);
|
|
263
280
|
}
|
|
264
281
|
if (direction === 'expand') {
|
|
265
282
|
// Cancel any in-flight collapse scroll-back so its leftover
|
|
266
283
|
// transform can't drift the code horizontally during expand.
|
|
267
|
-
scrollbackAnimations.get(
|
|
268
|
-
scrollbackAnimations.delete(
|
|
269
|
-
if (collapsibleProbeSelector &&
|
|
270
|
-
animateScrollbarGutter(
|
|
284
|
+
scrollbackAnimations.get(scrollEl)?.cancel();
|
|
285
|
+
scrollbackAnimations.delete(scrollEl);
|
|
286
|
+
if (collapsibleProbeSelector && container.querySelector(collapsibleProbeSelector)) {
|
|
287
|
+
animateScrollbarGutter(scrollEl, code, 'expand-from', 'expand-to', expandDuration);
|
|
271
288
|
}
|
|
272
289
|
}
|
|
273
290
|
}
|
|
274
291
|
rawAnchorScroll(anchor, direction === 'collapse' ? collapseDuration : expandDuration);
|
|
275
|
-
}, [containerRef, rawAnchorScroll, anchorSelector, collapsibleProbeSelector, collapseDuration, expandDuration, scrollBackDuration]);
|
|
292
|
+
}, [containerRef, scrollContainerRef, rawAnchorScroll, anchorSelector, collapsibleProbeSelector, collapseDuration, expandDuration, scrollBackDuration]);
|
|
276
293
|
return {
|
|
277
294
|
containerRef,
|
|
278
295
|
scrollContainerRef,
|
|
@@ -42,10 +42,16 @@ export function useScrollAnchor() {
|
|
|
42
42
|
activeSessionCleanupRef.current = null;
|
|
43
43
|
|
|
44
44
|
// Snapshot the scroll target at session start so a later ref change
|
|
45
|
-
// doesn't redirect compensation mid-flight.
|
|
46
|
-
|
|
45
|
+
// doesn't redirect compensation mid-flight. `scrollElement` is the attached
|
|
46
|
+
// container (if any); `scrollTarget` is what receives the user-interaction
|
|
47
|
+
// listeners (the container or the window).
|
|
48
|
+
const scrollElement = scrollContainerRef.current;
|
|
49
|
+
const scrollTarget = scrollElement ?? window;
|
|
47
50
|
const interactionTarget = scrollTarget;
|
|
48
|
-
|
|
51
|
+
|
|
52
|
+
// Mutable so it can be re-baselined when an attached container can't yet
|
|
53
|
+
// absorb a delta (see below).
|
|
54
|
+
let initialTop = anchor.getBoundingClientRect().top;
|
|
49
55
|
let active = true;
|
|
50
56
|
let cleanupTimer;
|
|
51
57
|
|
|
@@ -58,8 +64,25 @@ export function useScrollAnchor() {
|
|
|
58
64
|
return;
|
|
59
65
|
}
|
|
60
66
|
const delta = anchor.getBoundingClientRect().top - initialTop;
|
|
61
|
-
if (Math.abs(delta)
|
|
62
|
-
|
|
67
|
+
if (Math.abs(delta) <= 0.5) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (!scrollElement) {
|
|
71
|
+
window.scrollBy(0, delta);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const before = scrollElement.scrollTop;
|
|
75
|
+
scrollElement.scrollBy(0, delta);
|
|
76
|
+
const remainder = delta - (scrollElement.scrollTop - before);
|
|
77
|
+
if (Math.abs(remainder) > 0.5) {
|
|
78
|
+
// The container couldn't absorb this part — it isn't scrollable yet
|
|
79
|
+
// (its content hasn't exceeded its `max-height`). Re-baseline instead
|
|
80
|
+
// of forcing the difference elsewhere: scrolling the page would shift
|
|
81
|
+
// the surrounding layout, and carrying the delta forward would snap the
|
|
82
|
+
// anchor back the instant the container becomes scrollable. Accepting
|
|
83
|
+
// the small drift now keeps the surrounding layout still and lets the
|
|
84
|
+
// container hold the anchor smoothly from here on.
|
|
85
|
+
initialTop += remainder;
|
|
63
86
|
}
|
|
64
87
|
});
|
|
65
88
|
function cleanup() {
|