docusaurus-theme-openapi-docs 0.0.0-1294 → 0.0.0-1310

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 (114) hide show
  1. package/lib/markdown/schema.js +4 -5
  2. package/lib/theme/ApiExplorer/ApiCodeBlock/Container/index.js +4 -2
  3. package/lib/theme/ApiExplorer/ApiCodeBlock/Content/String.js +9 -6
  4. package/lib/theme/ApiExplorer/ApiCodeBlock/Line/index.d.ts +1 -1
  5. package/lib/theme/ApiExplorer/ApiCodeBlock/index.d.ts +1 -1
  6. package/lib/theme/ApiExplorer/Authorization/index.js +9 -10
  7. package/lib/theme/ApiExplorer/Body/index.js +4 -5
  8. package/lib/theme/ApiExplorer/CodeTabs/index.d.ts +1 -1
  9. package/lib/theme/ApiExplorer/CodeTabs/index.js +6 -5
  10. package/lib/theme/ApiExplorer/Export/index.js +9 -2
  11. package/lib/theme/ApiExplorer/FormFileUpload/index.js +1 -2
  12. package/lib/theme/ApiExplorer/FormLabel/index.js +1 -2
  13. package/lib/theme/ApiExplorer/FormTextInput/index.js +1 -2
  14. package/lib/theme/ApiExplorer/LiveEditor/index.js +1 -2
  15. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.js +5 -3
  16. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.js +1 -2
  17. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.js +1 -2
  18. package/lib/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.js +1 -2
  19. package/lib/theme/ApiExplorer/ParamOptions/index.js +2 -3
  20. package/lib/theme/ApiExplorer/Request/index.js +17 -18
  21. package/lib/theme/ApiExplorer/Response/index.js +7 -11
  22. package/lib/theme/ApiExplorer/SecuritySchemes/index.js +57 -50
  23. package/lib/theme/ApiExplorer/Server/index.js +2 -3
  24. package/lib/theme/ApiTabs/index.d.ts +1 -1
  25. package/lib/theme/ApiTabs/index.js +7 -7
  26. package/lib/theme/DiscriminatorTabs/index.d.ts +1 -1
  27. package/lib/theme/DiscriminatorTabs/index.js +6 -5
  28. package/lib/theme/Example/index.js +3 -4
  29. package/lib/theme/MimeTabs/index.d.ts +1 -1
  30. package/lib/theme/MimeTabs/index.js +6 -5
  31. package/lib/theme/OperationTabs/index.d.ts +1 -1
  32. package/lib/theme/OperationTabs/index.js +6 -5
  33. package/lib/theme/ParamsDetails/index.js +1 -2
  34. package/lib/theme/ParamsItem/index.js +7 -8
  35. package/lib/theme/RequestSchema/index.js +4 -6
  36. package/lib/theme/ResponseExamples/index.js +3 -4
  37. package/lib/theme/ResponseSchema/index.js +2 -3
  38. package/lib/theme/Schema/index.js +8 -9
  39. package/lib/theme/SchemaExpansion/index.js +4 -5
  40. package/lib/theme/SchemaItem/index.js +9 -10
  41. package/lib/theme/SchemaTabs/index.d.ts +1 -1
  42. package/lib/theme/SchemaTabs/index.js +6 -5
  43. package/lib/theme/StatusCodes/index.js +2 -4
  44. package/lib/theme/TabItem/index.d.ts +5 -0
  45. package/lib/theme/TabItem/index.js +51 -0
  46. package/lib/theme/TabItem/styles.module.css +3 -0
  47. package/lib/theme/Tabs/index.d.ts +5 -0
  48. package/lib/theme/Tabs/index.js +148 -0
  49. package/lib/theme/Tabs/styles.module.css +7 -0
  50. package/lib/theme/translationIds.d.ts +1 -99
  51. package/lib/theme/translationIds.js +0 -116
  52. package/lib/theme/utils/codeBlockUtils.d.ts +28 -0
  53. package/lib/theme/utils/codeBlockUtils.js +223 -0
  54. package/lib/theme/utils/reactUtils.d.ts +1 -0
  55. package/lib/theme/utils/reactUtils.js +23 -0
  56. package/lib/theme/utils/scrollUtils.d.ts +7 -0
  57. package/lib/theme/utils/scrollUtils.js +175 -0
  58. package/lib/theme/utils/tabsUtils.d.ts +47 -0
  59. package/lib/theme/utils/tabsUtils.js +299 -0
  60. package/lib/theme/utils/useCodeWordWrap.d.ts +8 -0
  61. package/lib/theme/utils/useCodeWordWrap.js +84 -0
  62. package/lib/theme/utils/useMutationObserver.d.ts +3 -0
  63. package/lib/theme/utils/useMutationObserver.js +34 -0
  64. package/package.json +4 -4
  65. package/src/markdown/schema.ts +4 -5
  66. package/src/theme/ApiExplorer/ApiCodeBlock/Container/index.tsx +2 -1
  67. package/src/theme/ApiExplorer/ApiCodeBlock/Content/String.tsx +8 -7
  68. package/src/theme/ApiExplorer/ApiCodeBlock/Line/index.tsx +1 -1
  69. package/src/theme/ApiExplorer/ApiCodeBlock/index.tsx +1 -1
  70. package/src/theme/ApiExplorer/Authorization/index.tsx +9 -10
  71. package/src/theme/ApiExplorer/Body/index.tsx +7 -5
  72. package/src/theme/ApiExplorer/CodeTabs/index.tsx +5 -5
  73. package/src/theme/ApiExplorer/Export/index.tsx +6 -2
  74. package/src/theme/ApiExplorer/FormFileUpload/index.tsx +1 -2
  75. package/src/theme/ApiExplorer/FormLabel/index.tsx +1 -2
  76. package/src/theme/ApiExplorer/FormTextInput/index.tsx +1 -2
  77. package/src/theme/ApiExplorer/LiveEditor/index.tsx +1 -2
  78. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamArrayFormItem.tsx +5 -3
  79. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamBooleanFormItem.tsx +1 -2
  80. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamMultiSelectFormItem.tsx +1 -2
  81. package/src/theme/ApiExplorer/ParamOptions/ParamFormItems/ParamSelectFormItem.tsx +1 -2
  82. package/src/theme/ApiExplorer/ParamOptions/index.tsx +2 -3
  83. package/src/theme/ApiExplorer/Request/index.tsx +23 -18
  84. package/src/theme/ApiExplorer/Response/index.tsx +10 -8
  85. package/src/theme/ApiExplorer/SecuritySchemes/index.tsx +60 -52
  86. package/src/theme/ApiExplorer/Server/index.tsx +8 -3
  87. package/src/theme/ApiTabs/index.tsx +8 -8
  88. package/src/theme/DiscriminatorTabs/index.tsx +6 -5
  89. package/src/theme/Example/index.tsx +3 -4
  90. package/src/theme/MimeTabs/index.tsx +9 -8
  91. package/src/theme/OperationTabs/index.tsx +5 -4
  92. package/src/theme/ParamsDetails/index.tsx +1 -2
  93. package/src/theme/ParamsItem/index.tsx +13 -8
  94. package/src/theme/RequestSchema/index.tsx +4 -5
  95. package/src/theme/ResponseExamples/index.tsx +3 -4
  96. package/src/theme/ResponseSchema/index.tsx +2 -3
  97. package/src/theme/Schema/index.tsx +8 -9
  98. package/src/theme/SchemaExpansion/index.tsx +4 -5
  99. package/src/theme/SchemaItem/index.tsx +18 -10
  100. package/src/theme/SchemaTabs/index.tsx +6 -5
  101. package/src/theme/StatusCodes/index.tsx +2 -3
  102. package/src/theme/TabItem/index.tsx +61 -0
  103. package/src/theme/TabItem/styles.module.css +3 -0
  104. package/src/theme/Tabs/index.tsx +164 -0
  105. package/src/theme/Tabs/styles.module.css +7 -0
  106. package/src/theme/translationIds.ts +37 -113
  107. package/src/theme/utils/codeBlockUtils.ts +296 -0
  108. package/src/theme/utils/reactUtils.ts +22 -0
  109. package/src/theme/utils/scrollUtils.tsx +153 -0
  110. package/src/theme/utils/tabsUtils.tsx +329 -0
  111. package/src/theme/utils/useCodeWordWrap.ts +110 -0
  112. package/src/theme/utils/useMutationObserver.ts +43 -0
  113. package/src/theme-classic.d.ts +0 -96
  114. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,296 @@
1
+ /* ============================================================================
2
+ * Portions Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ * Portions Copyright (c) Palo Alto Networks
4
+ *
5
+ * Vendored subset of @docusaurus/theme-common/src/utils/codeBlockUtils.tsx (MIT)
6
+ * to remove the dependency on @docusaurus/theme-common/internal.
7
+ * See: https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1140
8
+ *
9
+ * This source code is licensed under the MIT license found in the
10
+ * LICENSE file in the root directory of this source tree.
11
+ * ========================================================================== */
12
+
13
+ import type { CSSProperties } from "react";
14
+
15
+ import rangeParser from "parse-numeric-range";
16
+ import type { PrismTheme, PrismThemeEntry } from "prism-react-renderer";
17
+
18
+ const codeBlockTitleRegex = /title=(?<quote>["'])(?<title>.*?)\1/;
19
+ const metastringLinesRangeRegex = /\{(?<range>[\d,-]+)\}/;
20
+
21
+ const popularCommentPatterns = {
22
+ js: { start: "\\/\\/", end: "" },
23
+ jsBlock: { start: "\\/\\*", end: "\\*\\/" },
24
+ jsx: { start: "\\{\\s*\\/\\*", end: "\\*\\/\\s*\\}" },
25
+ bash: { start: "#", end: "" },
26
+ html: { start: "<!--", end: "-->" },
27
+ } as const;
28
+
29
+ const commentPatterns = {
30
+ ...popularCommentPatterns,
31
+ lua: { start: "--", end: "" },
32
+ wasm: { start: "\\;\\;", end: "" },
33
+ tex: { start: "%", end: "" },
34
+ vb: { start: "['‘’]", end: "" },
35
+ vbnet: { start: "(?:_\\s*)?['‘’]", end: "" },
36
+ rem: { start: "[Rr][Ee][Mm]\\b", end: "" },
37
+ f90: { start: "!", end: "" },
38
+ ml: { start: "\\(\\*", end: "\\*\\)" },
39
+ cobol: { start: "\\*>", end: "" },
40
+ } as const;
41
+
42
+ type CommentType = keyof typeof commentPatterns;
43
+ const popularCommentTypes = Object.keys(
44
+ popularCommentPatterns
45
+ ) as CommentType[];
46
+
47
+ export type MagicCommentConfig = {
48
+ className: string;
49
+ line?: string;
50
+ block?: { start: string; end: string };
51
+ };
52
+
53
+ function getCommentPattern(
54
+ languages: CommentType[],
55
+ magicCommentDirectives: MagicCommentConfig[]
56
+ ) {
57
+ const commentPattern = languages
58
+ .map((lang) => {
59
+ const { start, end } = commentPatterns[lang];
60
+ return `(?:${start}\\s*(${magicCommentDirectives
61
+ .flatMap((d) => [d.line, d.block?.start, d.block?.end].filter(Boolean))
62
+ .join("|")})\\s*${end})`;
63
+ })
64
+ .join("|");
65
+ return new RegExp(`^\\s*(?:${commentPattern})\\s*$`);
66
+ }
67
+
68
+ function getAllMagicCommentDirectiveStyles(
69
+ lang: string,
70
+ magicCommentDirectives: MagicCommentConfig[]
71
+ ) {
72
+ switch (lang) {
73
+ case "js":
74
+ case "javascript":
75
+ case "ts":
76
+ case "typescript":
77
+ return getCommentPattern(["js", "jsBlock"], magicCommentDirectives);
78
+
79
+ case "jsx":
80
+ case "tsx":
81
+ return getCommentPattern(
82
+ ["js", "jsBlock", "jsx"],
83
+ magicCommentDirectives
84
+ );
85
+
86
+ case "html":
87
+ return getCommentPattern(
88
+ ["js", "jsBlock", "html"],
89
+ magicCommentDirectives
90
+ );
91
+
92
+ case "python":
93
+ case "py":
94
+ case "bash":
95
+ return getCommentPattern(["bash"], magicCommentDirectives);
96
+
97
+ case "markdown":
98
+ case "md":
99
+ return getCommentPattern(["html", "jsx", "bash"], magicCommentDirectives);
100
+
101
+ case "tex":
102
+ case "latex":
103
+ case "matlab":
104
+ return getCommentPattern(["tex"], magicCommentDirectives);
105
+
106
+ case "lua":
107
+ case "haskell":
108
+ return getCommentPattern(["lua"], magicCommentDirectives);
109
+
110
+ case "sql":
111
+ return getCommentPattern(["lua", "jsBlock"], magicCommentDirectives);
112
+
113
+ case "wasm":
114
+ return getCommentPattern(["wasm"], magicCommentDirectives);
115
+
116
+ case "vb":
117
+ case "vba":
118
+ case "visual-basic":
119
+ return getCommentPattern(["vb", "rem"], magicCommentDirectives);
120
+ case "vbnet":
121
+ return getCommentPattern(["vbnet", "rem"], magicCommentDirectives);
122
+
123
+ case "batch":
124
+ return getCommentPattern(["rem"], magicCommentDirectives);
125
+
126
+ case "basic":
127
+ return getCommentPattern(["rem", "f90"], magicCommentDirectives);
128
+
129
+ case "fsharp":
130
+ return getCommentPattern(["js", "ml"], magicCommentDirectives);
131
+
132
+ case "ocaml":
133
+ case "sml":
134
+ return getCommentPattern(["ml"], magicCommentDirectives);
135
+
136
+ case "fortran":
137
+ return getCommentPattern(["f90"], magicCommentDirectives);
138
+
139
+ case "cobol":
140
+ return getCommentPattern(["cobol"], magicCommentDirectives);
141
+
142
+ default:
143
+ return getCommentPattern(popularCommentTypes, magicCommentDirectives);
144
+ }
145
+ }
146
+
147
+ export function parseCodeBlockTitle(metastring?: string): string {
148
+ return metastring?.match(codeBlockTitleRegex)?.groups!.title ?? "";
149
+ }
150
+
151
+ export function containsLineNumbers(metastring?: string): boolean {
152
+ return Boolean(metastring?.includes("showLineNumbers"));
153
+ }
154
+
155
+ type ParseCodeLinesParam = {
156
+ metastring: string | undefined;
157
+ language: string | undefined;
158
+ magicComments: MagicCommentConfig[];
159
+ };
160
+
161
+ type CodeLineClassNames = { [lineIndex: number]: string[] };
162
+
163
+ type ParsedCodeLines = {
164
+ code: string;
165
+ lineClassNames: CodeLineClassNames;
166
+ };
167
+
168
+ function parseCodeLinesFromMetastring(
169
+ code: string,
170
+ { metastring, magicComments }: ParseCodeLinesParam
171
+ ): ParsedCodeLines | null {
172
+ if (metastring && metastringLinesRangeRegex.test(metastring)) {
173
+ const linesRange = metastring.match(metastringLinesRangeRegex)!.groups!
174
+ .range!;
175
+ if (magicComments.length === 0) {
176
+ throw new Error(
177
+ `A highlight range has been given in code block's metastring (\`\`\` ${metastring}), but no magic comment config is available. Docusaurus applies the first magic comment entry's className for metastring ranges.`
178
+ );
179
+ }
180
+ const metastringRangeClassName = magicComments[0]!.className;
181
+ const lines = rangeParser(linesRange)
182
+ .filter((n) => n > 0)
183
+ .map((n) => [n - 1, [metastringRangeClassName]] as [number, string[]]);
184
+ return { lineClassNames: Object.fromEntries(lines), code };
185
+ }
186
+ return null;
187
+ }
188
+
189
+ function parseCodeLinesFromContent(
190
+ code: string,
191
+ params: ParseCodeLinesParam
192
+ ): ParsedCodeLines {
193
+ const { language, magicComments } = params;
194
+ if (language === undefined) {
195
+ return { lineClassNames: {}, code };
196
+ }
197
+ const directiveRegex = getAllMagicCommentDirectiveStyles(
198
+ language,
199
+ magicComments
200
+ );
201
+ const lines = code.split(/\r?\n/);
202
+ const blocks = Object.fromEntries(
203
+ magicComments.map((d) => [d.className, { start: 0, range: "" }])
204
+ );
205
+ const lineToClassName: { [comment: string]: string } = Object.fromEntries(
206
+ magicComments
207
+ .filter((d) => d.line)
208
+ .map(({ className, line }) => [line!, className] as [string, string])
209
+ );
210
+ const blockStartToClassName: { [comment: string]: string } =
211
+ Object.fromEntries(
212
+ magicComments
213
+ .filter((d) => d.block)
214
+ .map(({ className, block }) => [block!.start, className])
215
+ );
216
+ const blockEndToClassName: { [comment: string]: string } = Object.fromEntries(
217
+ magicComments
218
+ .filter((d) => d.block)
219
+ .map(({ className, block }) => [block!.end, className])
220
+ );
221
+ for (let lineNumber = 0; lineNumber < lines.length; ) {
222
+ const line = lines[lineNumber]!;
223
+ const match = line.match(directiveRegex);
224
+ if (!match) {
225
+ lineNumber += 1;
226
+ continue;
227
+ }
228
+ const directive = match
229
+ .slice(1)
230
+ .find((item: string | undefined) => item !== undefined)!;
231
+ if (lineToClassName[directive]) {
232
+ blocks[lineToClassName[directive]!]!.range += `${lineNumber},`;
233
+ } else if (blockStartToClassName[directive]) {
234
+ blocks[blockStartToClassName[directive]!]!.start = lineNumber;
235
+ } else if (blockEndToClassName[directive]) {
236
+ blocks[blockEndToClassName[directive]!]!.range += `${
237
+ blocks[blockEndToClassName[directive]!]!.start
238
+ }-${lineNumber - 1},`;
239
+ }
240
+ lines.splice(lineNumber, 1);
241
+ }
242
+
243
+ const lineClassNames: { [lineIndex: number]: string[] } = {};
244
+ Object.entries(blocks).forEach(([className, { range }]) => {
245
+ rangeParser(range).forEach((l) => {
246
+ lineClassNames[l] ??= [];
247
+ lineClassNames[l]!.push(className);
248
+ });
249
+ });
250
+
251
+ return { code: lines.join("\n"), lineClassNames };
252
+ }
253
+
254
+ export function parseLines(
255
+ code: string,
256
+ params: ParseCodeLinesParam
257
+ ): ParsedCodeLines {
258
+ const newCode = code.replace(/\r?\n$/, "");
259
+ return (
260
+ parseCodeLinesFromMetastring(newCode, { ...params }) ??
261
+ parseCodeLinesFromContent(newCode, { ...params })
262
+ );
263
+ }
264
+
265
+ function parseClassNameLanguage(
266
+ className: string | undefined
267
+ ): string | undefined {
268
+ if (!className) {
269
+ return undefined;
270
+ }
271
+ const languageClassName = className
272
+ .split(" ")
273
+ .find((str) => str.startsWith("language-"));
274
+ return languageClassName?.replace(/language-/, "");
275
+ }
276
+
277
+ // Upstream renamed `parseLanguage` to `parseClassNameLanguage`; the back-compat
278
+ // shim in @docusaurus/theme-common/internal re-exports it under the old name.
279
+ // We keep the old name here since our call sites still use it.
280
+ export { parseClassNameLanguage as parseLanguage };
281
+
282
+ export function getPrismCssVariables(prismTheme: PrismTheme): CSSProperties {
283
+ const mapping: PrismThemeEntry = {
284
+ color: "--prism-color",
285
+ backgroundColor: "--prism-background-color",
286
+ };
287
+
288
+ const properties: { [key: string]: string } = {};
289
+ Object.entries(prismTheme.plain).forEach(([key, value]) => {
290
+ const varName = mapping[key as keyof PrismThemeEntry];
291
+ if (varName && typeof value === "string") {
292
+ properties[varName] = value;
293
+ }
294
+ });
295
+ return properties;
296
+ }
@@ -0,0 +1,22 @@
1
+ /* ============================================================================
2
+ * Portions Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ * Portions Copyright (c) Palo Alto Networks
4
+ *
5
+ * Vendored from @docusaurus/theme-common/src/utils/reactUtils.tsx (MIT).
6
+ * Only useShallowMemoObject is kept here — it is not re-exported by
7
+ * @docusaurus/theme-common (public or /internal). useEvent and
8
+ * ReactContextError are public APIs imported directly from the package.
9
+ * See: https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1140
10
+ *
11
+ * This source code is licensed under the MIT license found in the
12
+ * LICENSE file in the root directory of this source tree.
13
+ * ========================================================================== */
14
+
15
+ import { useMemo } from "react";
16
+
17
+ export function useShallowMemoObject<O extends object>(obj: O): O {
18
+ const deps = Object.entries(obj);
19
+ deps.sort((a, b) => a[0].localeCompare(b[0]));
20
+ // eslint-disable-next-line react-hooks/exhaustive-deps
21
+ return useMemo(() => obj, deps.flat());
22
+ }
@@ -0,0 +1,153 @@
1
+ /* ============================================================================
2
+ * Portions Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ * Portions Copyright (c) Palo Alto Networks
4
+ *
5
+ * Vendored subset of @docusaurus/theme-common/src/utils/scrollUtils.tsx (MIT)
6
+ * to remove the dependency on @docusaurus/theme-common/internal. Only the
7
+ * ScrollControllerProvider + useScrollPositionBlocker surface is kept, since
8
+ * that's all our tab renderers need. The ScrollControllerProvider must be
9
+ * mounted in the React tree above any consumer of useScrollPositionBlocker
10
+ * (see ApiItem/index.tsx).
11
+ * See: https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1140
12
+ *
13
+ * This source code is licensed under the MIT license found in the
14
+ * LICENSE file in the root directory of this source tree.
15
+ * ========================================================================== */
16
+
17
+ import React, {
18
+ useCallback,
19
+ useContext,
20
+ useMemo,
21
+ useRef,
22
+ type ReactNode,
23
+ } from "react";
24
+
25
+ import useIsomorphicLayoutEffect from "@docusaurus/useIsomorphicLayoutEffect";
26
+ import { ReactContextError } from "@docusaurus/theme-common";
27
+
28
+ type ScrollController = {
29
+ scrollEventsEnabledRef: React.RefObject<boolean>;
30
+ enableScrollEvents: () => void;
31
+ disableScrollEvents: () => void;
32
+ };
33
+
34
+ function useScrollControllerContextValue(): ScrollController {
35
+ const scrollEventsEnabledRef = useRef(true);
36
+
37
+ return useMemo(
38
+ () => ({
39
+ scrollEventsEnabledRef,
40
+ enableScrollEvents: () => {
41
+ scrollEventsEnabledRef.current = true;
42
+ },
43
+ disableScrollEvents: () => {
44
+ scrollEventsEnabledRef.current = false;
45
+ },
46
+ }),
47
+ []
48
+ );
49
+ }
50
+
51
+ const ScrollMonitorContext = React.createContext<ScrollController | undefined>(
52
+ undefined
53
+ );
54
+
55
+ export function ScrollControllerProvider({
56
+ children,
57
+ }: {
58
+ children: ReactNode;
59
+ }): ReactNode {
60
+ const value = useScrollControllerContextValue();
61
+ return (
62
+ <ScrollMonitorContext.Provider value={value}>
63
+ {children}
64
+ </ScrollMonitorContext.Provider>
65
+ );
66
+ }
67
+
68
+ function useScrollController(): ScrollController {
69
+ const context = useContext(ScrollMonitorContext);
70
+ if (context == null) {
71
+ throw new ReactContextError("ScrollControllerProvider");
72
+ }
73
+ return context;
74
+ }
75
+
76
+ type UseScrollPositionSaver = {
77
+ save: (elem: HTMLElement) => void;
78
+ restore: () => { restored: boolean };
79
+ };
80
+
81
+ function useScrollPositionSaver(): UseScrollPositionSaver {
82
+ const lastElementRef = useRef<{ elem: HTMLElement | null; top: number }>({
83
+ elem: null,
84
+ top: 0,
85
+ });
86
+
87
+ const save = useCallback((elem: HTMLElement) => {
88
+ lastElementRef.current = {
89
+ elem,
90
+ top: elem.getBoundingClientRect().top,
91
+ };
92
+ }, []);
93
+
94
+ const restore = useCallback(() => {
95
+ const {
96
+ current: { elem, top },
97
+ } = lastElementRef;
98
+ if (!elem) {
99
+ return { restored: false };
100
+ }
101
+ const newTop = elem.getBoundingClientRect().top;
102
+ const heightDiff = newTop - top;
103
+ if (heightDiff) {
104
+ window.scrollBy({ left: 0, top: heightDiff });
105
+ }
106
+ lastElementRef.current = { elem: null, top: 0 };
107
+
108
+ return { restored: heightDiff !== 0 };
109
+ }, []);
110
+
111
+ return useMemo(() => ({ save, restore }), [restore, save]);
112
+ }
113
+
114
+ export function useScrollPositionBlocker(): {
115
+ blockElementScrollPositionUntilNextRender: (el: HTMLElement) => void;
116
+ } {
117
+ const scrollController = useScrollController();
118
+ const scrollPositionSaver = useScrollPositionSaver();
119
+
120
+ const nextLayoutEffectCallbackRef = useRef<(() => void) | undefined>(
121
+ undefined
122
+ );
123
+
124
+ const blockElementScrollPositionUntilNextRender = useCallback(
125
+ (el: HTMLElement) => {
126
+ scrollPositionSaver.save(el);
127
+ scrollController.disableScrollEvents();
128
+ nextLayoutEffectCallbackRef.current = () => {
129
+ const { restored } = scrollPositionSaver.restore();
130
+ nextLayoutEffectCallbackRef.current = undefined;
131
+
132
+ if (restored) {
133
+ const handleScrollRestoreEvent = () => {
134
+ scrollController.enableScrollEvents();
135
+ window.removeEventListener("scroll", handleScrollRestoreEvent);
136
+ };
137
+ window.addEventListener("scroll", handleScrollRestoreEvent);
138
+ } else {
139
+ scrollController.enableScrollEvents();
140
+ }
141
+ };
142
+ },
143
+ [scrollController, scrollPositionSaver]
144
+ );
145
+
146
+ useIsomorphicLayoutEffect(() => {
147
+ queueMicrotask(() => nextLayoutEffectCallbackRef.current?.());
148
+ });
149
+
150
+ return {
151
+ blockElementScrollPositionUntilNextRender,
152
+ };
153
+ }