@open-press/cli 1.0.0 → 1.1.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/README.md +11 -12
- package/dist/cli.js +298 -79
- package/package.json +9 -7
- package/template/core/AGENTS.md +0 -130
- package/template/core/CHANGELOG.md +0 -218
- package/template/core/README.md +0 -43
- package/template/core/engine/cli.mjs +0 -96
- package/template/core/engine/commands/_shared.mjs +0 -199
- package/template/core/engine/commands/deploy.mjs +0 -31
- package/template/core/engine/commands/dev.mjs +0 -49
- package/template/core/engine/commands/doctor.mjs +0 -229
- package/template/core/engine/commands/export.mjs +0 -8
- package/template/core/engine/commands/image.mjs +0 -29
- package/template/core/engine/commands/inspect.mjs +0 -35
- package/template/core/engine/commands/pdf.mjs +0 -26
- package/template/core/engine/commands/preview.mjs +0 -26
- package/template/core/engine/commands/render.mjs +0 -17
- package/template/core/engine/commands/replace.mjs +0 -41
- package/template/core/engine/commands/search.mjs +0 -33
- package/template/core/engine/commands/skills-sync.mjs +0 -71
- package/template/core/engine/commands/typecheck.mjs +0 -67
- package/template/core/engine/commands/upgrade.mjs +0 -159
- package/template/core/engine/commands/validate.mjs +0 -17
- package/template/core/engine/document-export.mjs +0 -15
- package/template/core/engine/output/chrome-pdf.d.mts +0 -34
- package/template/core/engine/output/chrome-pdf.mjs +0 -450
- package/template/core/engine/output/deploy-sync.mjs +0 -15
- package/template/core/engine/output/fonts.mjs +0 -62
- package/template/core/engine/output/katex-assets.mjs +0 -45
- package/template/core/engine/output/page-block.mjs +0 -30
- package/template/core/engine/output/pdf-media.mjs +0 -45
- package/template/core/engine/output/public-assets.mjs +0 -19
- package/template/core/engine/output/static-server.mjs +0 -571
- package/template/core/engine/react/caption-numbering.mjs +0 -73
- package/template/core/engine/react/comment-endpoint.d.mts +0 -11
- package/template/core/engine/react/comment-endpoint.mjs +0 -102
- package/template/core/engine/react/comment-marker.mjs +0 -374
- package/template/core/engine/react/document-entry.mjs +0 -331
- package/template/core/engine/react/document-export.mjs +0 -512
- package/template/core/engine/react/http-json.mjs +0 -24
- package/template/core/engine/react/mdx-compile.mjs +0 -629
- package/template/core/engine/react/measurement-css.mjs +0 -157
- package/template/core/engine/react/object-entities.mjs +0 -204
- package/template/core/engine/react/pagination/allocator.mjs +0 -167
- package/template/core/engine/react/pagination/regions.mjs +0 -81
- package/template/core/engine/react/pagination-constants.mjs +0 -3
- package/template/core/engine/react/pagination.mjs +0 -9
- package/template/core/engine/react/pipeline/allocate.mjs +0 -217
- package/template/core/engine/react/pipeline/final-render.mjs +0 -94
- package/template/core/engine/react/pipeline/frame-measurement.mjs +0 -306
- package/template/core/engine/react/pipeline/press-tree.mjs +0 -135
- package/template/core/engine/react/press-tree-inspection.mjs +0 -172
- package/template/core/engine/react/project-asset-endpoint.d.mts +0 -10
- package/template/core/engine/react/project-asset-endpoint.mjs +0 -361
- package/template/core/engine/react/section-css.mjs +0 -56
- package/template/core/engine/react/source-edit-endpoint.d.mts +0 -10
- package/template/core/engine/react/source-edit-endpoint.mjs +0 -75
- package/template/core/engine/react/sources/heading-numbering.mjs +0 -132
- package/template/core/engine/react/sources/mdx-resolver.mjs +0 -439
- package/template/core/engine/react/style-discovery.mjs +0 -160
- package/template/core/engine/runtime/config.d.mts +0 -48
- package/template/core/engine/runtime/config.mjs +0 -172
- package/template/core/engine/runtime/file-utils.mjs +0 -114
- package/template/core/engine/runtime/file-walk.mjs +0 -22
- package/template/core/engine/runtime/inspection.mjs +0 -328
- package/template/core/engine/runtime/issue-report.mjs +0 -44
- package/template/core/engine/runtime/page-geometry.mjs +0 -131
- package/template/core/engine/runtime/path-utils.mjs +0 -20
- package/template/core/engine/runtime/source-text-tools.d.mts +0 -102
- package/template/core/engine/runtime/source-text-tools.mjs +0 -832
- package/template/core/engine/runtime/source-workspace.mjs +0 -168
- package/template/core/engine/runtime/validation.mjs +0 -183
- package/template/core/index.html +0 -13
- package/template/core/openpress.config.mjs +0 -8
- package/template/core/package.json +0 -89
- package/template/core/src/main.tsx +0 -16
- package/template/core/src/openpress/app/OpenPressApp.tsx +0 -296
- package/template/core/src/openpress/app/OpenPressRuntime.tsx +0 -102
- package/template/core/src/openpress/app/WorkspaceGalleryPage.tsx +0 -219
- package/template/core/src/openpress/app/index.ts +0 -2
- package/template/core/src/openpress/core/Frame.tsx +0 -91
- package/template/core/src/openpress/core/FrameContext.tsx +0 -26
- package/template/core/src/openpress/core/MdxArea.tsx +0 -34
- package/template/core/src/openpress/core/Press.tsx +0 -55
- package/template/core/src/openpress/core/Workspace.tsx +0 -36
- package/template/core/src/openpress/core/cn.ts +0 -4
- package/template/core/src/openpress/core/index.tsx +0 -47
- package/template/core/src/openpress/core/primitives.tsx +0 -91
- package/template/core/src/openpress/core/types.ts +0 -236
- package/template/core/src/openpress/core/useSource.ts +0 -28
- package/template/core/src/openpress/document-model/anchorMapModel.ts +0 -27
- package/template/core/src/openpress/document-model/documentIndexes.ts +0 -329
- package/template/core/src/openpress/document-model/documentTypes.ts +0 -147
- package/template/core/src/openpress/document-model/index.ts +0 -7
- package/template/core/src/openpress/document-model/objectEntityModel.ts +0 -55
- package/template/core/src/openpress/document-model/projectIdentityModel.ts +0 -15
- package/template/core/src/openpress/document-model/reactDocumentMetadataModel.ts +0 -27
- package/template/core/src/openpress/document-model/workspaceManifestModel.ts +0 -57
- package/template/core/src/openpress/manuscript/index.tsx +0 -238
- package/template/core/src/openpress/mdx/index.ts +0 -96
- package/template/core/src/openpress/numbering/index.ts +0 -294
- package/template/core/src/openpress/reader/PageThumbnailsPanel.tsx +0 -168
- package/template/core/src/openpress/reader/PublicReaderPage.tsx +0 -267
- package/template/core/src/openpress/reader/ReaderNavigationPanel.tsx +0 -123
- package/template/core/src/openpress/reader/index.ts +0 -11
- package/template/core/src/openpress/reader/pageViewportScaleModel.ts +0 -73
- package/template/core/src/openpress/reader/readerPageRegistry.ts +0 -41
- package/template/core/src/openpress/reader/readerPageRoute.ts +0 -21
- package/template/core/src/openpress/reader/readerScroll.ts +0 -92
- package/template/core/src/openpress/reader/readerStateModel.ts +0 -15
- package/template/core/src/openpress/reader/readerTypes.ts +0 -4
- package/template/core/src/openpress/reader/usePageViewportScale.ts +0 -119
- package/template/core/src/openpress/reader/usePanelState.ts +0 -56
- package/template/core/src/openpress/reader/useReaderHashSync.ts +0 -61
- package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +0 -48
- package/template/core/src/openpress/reader/useReaderRuntime.ts +0 -146
- package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +0 -64
- package/template/core/src/openpress/shared/Panel.tsx +0 -77
- package/template/core/src/openpress/shared/frameScheduler.ts +0 -32
- package/template/core/src/openpress/shared/index.ts +0 -4
- package/template/core/src/openpress/shared/numberUtils.ts +0 -3
- package/template/core/src/openpress/shared/runtimeMode.ts +0 -11
- package/template/core/src/openpress/workbench/Workbench.tsx +0 -506
- package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +0 -157
- package/template/core/src/openpress/workbench/actions/ExportImageControl.tsx +0 -96
- package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +0 -182
- package/template/core/src/openpress/workbench/actions/SearchControl.tsx +0 -345
- package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +0 -112
- package/template/core/src/openpress/workbench/actions/index.ts +0 -6
- package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +0 -136
- package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +0 -72
- package/template/core/src/openpress/workbench/dialog/index.ts +0 -1
- package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +0 -127
- package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +0 -207
- package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +0 -9
- package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +0 -34
- package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +0 -525
- package/template/core/src/openpress/workbench/document/index.ts +0 -10
- package/template/core/src/openpress/workbench/index.ts +0 -2
- package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +0 -459
- package/template/core/src/openpress/workbench/inspector/index.ts +0 -5
- package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +0 -125
- package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +0 -160
- package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +0 -408
- package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +0 -254
- package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +0 -41
- package/template/core/src/openpress/workbench/mentions/index.ts +0 -2
- package/template/core/src/openpress/workbench/mentions/useComposerMentions.ts +0 -185
- package/template/core/src/openpress/workbench/panels/Panel.tsx +0 -1
- package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +0 -80
- package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +0 -29
- package/template/core/src/openpress/workbench/panels/index.ts +0 -3
- package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +0 -525
- package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +0 -35
- package/template/core/src/openpress/workbench/project/index.ts +0 -2
- package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +0 -11
- package/template/core/src/openpress/workbench/project/projectSourceModel.ts +0 -24
- package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +0 -167
- package/template/core/src/openpress/workbench/shell/index.ts +0 -1
- package/template/core/src/openpress/workbench/workbenchFormatters.ts +0 -120
- package/template/core/src/openpress/workbench/workbenchTypes.ts +0 -35
- package/template/core/src/styles/openpress/app-shell.css +0 -251
- package/template/core/src/styles/openpress/media-workspace.css +0 -230
- package/template/core/src/styles/openpress/print-route.css +0 -184
- package/template/core/src/styles/openpress/project-preview-panel.css +0 -924
- package/template/core/src/styles/openpress/public-viewer.css +0 -688
- package/template/core/src/styles/openpress/reader-runtime.css +0 -989
- package/template/core/src/styles/openpress/responsive.css +0 -245
- package/template/core/src/styles/openpress/workbench-panels.css +0 -707
- package/template/core/src/styles/openpress/workbench.css +0 -1255
- package/template/core/src/styles/openpress/workspace-gallery.css +0 -300
- package/template/core/src/styles/openpress.css +0 -15
- package/template/core/src/vite-env.d.ts +0 -9
- package/template/core/tsconfig.json +0 -40
- package/template/core/vite.config.ts +0 -584
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
const PAGE_HASH_PATTERN = /^#page-(\d+)$/;
|
|
2
|
-
|
|
3
|
-
export function pageHashFromIndex(pageIndex: number) {
|
|
4
|
-
return `#page-${String(Math.max(1, pageIndex + 1)).padStart(2, "0")}`;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function pageIndexFromHash(hash: string, pageCount: number) {
|
|
8
|
-
const match = hash.match(PAGE_HASH_PATTERN);
|
|
9
|
-
if (!match) return null;
|
|
10
|
-
|
|
11
|
-
const pageNumber = Number.parseInt(match[1], 10);
|
|
12
|
-
if (!Number.isFinite(pageNumber) || pageNumber < 1 || pageNumber > pageCount) return null;
|
|
13
|
-
return pageNumber - 1;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function replacePageRoute(pageIndex: number) {
|
|
17
|
-
if (typeof window === "undefined") return;
|
|
18
|
-
const hash = pageHashFromIndex(pageIndex);
|
|
19
|
-
if (window.location.hash === hash) return;
|
|
20
|
-
window.history.replaceState(null, "", hash);
|
|
21
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
// Single place that touches scrollIntoView and IntersectionObserver. Keeping
|
|
2
|
-
// these together makes it obvious which DOM APIs the reader depends on and
|
|
3
|
-
// keeps the React runtime free of imperative scroll bookkeeping.
|
|
4
|
-
|
|
5
|
-
const DEBOUNCE_MS = 100;
|
|
6
|
-
|
|
7
|
-
const OBSERVER_THRESHOLDS = [0, 0.25, 0.5, 0.75, 1];
|
|
8
|
-
|
|
9
|
-
export function scrollToPage(
|
|
10
|
-
refs: Array<HTMLElement | null>,
|
|
11
|
-
pageIndex: number,
|
|
12
|
-
behavior: ScrollBehavior = "smooth",
|
|
13
|
-
root?: HTMLElement | null,
|
|
14
|
-
) {
|
|
15
|
-
const target = refs[pageIndex];
|
|
16
|
-
if (!target) return false;
|
|
17
|
-
|
|
18
|
-
if (root && root.contains(target) && typeof root.scrollTo === "function") {
|
|
19
|
-
const rootRect = root.getBoundingClientRect();
|
|
20
|
-
const targetRect = target.getBoundingClientRect();
|
|
21
|
-
const scrollMarginTop = readScrollMarginTop(target);
|
|
22
|
-
root.scrollTo({
|
|
23
|
-
top: Math.max(0, root.scrollTop + targetRect.top - rootRect.top - scrollMarginTop),
|
|
24
|
-
behavior,
|
|
25
|
-
});
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
target.scrollIntoView({ behavior, block: "start" });
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function readScrollMarginTop(target: HTMLElement) {
|
|
34
|
-
if (typeof window === "undefined") return 0;
|
|
35
|
-
const value = Number.parseFloat(window.getComputedStyle(target).scrollMarginTop);
|
|
36
|
-
return Number.isFinite(value) ? value : 0;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface PageVisibilityObserver {
|
|
40
|
-
observe: (element: Element) => void;
|
|
41
|
-
disconnect: () => void;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function createPageVisibilityObserver(
|
|
45
|
-
root: Element,
|
|
46
|
-
onVisiblePageChange: (pageIndex: number) => void,
|
|
47
|
-
): PageVisibilityObserver | null {
|
|
48
|
-
if (typeof IntersectionObserver === "undefined") return null;
|
|
49
|
-
|
|
50
|
-
const ratios = new Map<Element, number>();
|
|
51
|
-
let debounceTimer: ReturnType<typeof setTimeout> | null = null;
|
|
52
|
-
|
|
53
|
-
const flush = () => {
|
|
54
|
-
debounceTimer = null;
|
|
55
|
-
let bestEl: Element | null = null;
|
|
56
|
-
let bestRatio = -1;
|
|
57
|
-
for (const [el, ratio] of ratios) {
|
|
58
|
-
if (ratio > bestRatio) {
|
|
59
|
-
bestRatio = ratio;
|
|
60
|
-
bestEl = el;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (!bestEl || bestRatio <= 0) return;
|
|
64
|
-
const raw = bestEl.getAttribute("data-openpress-page-index");
|
|
65
|
-
if (raw === null) return;
|
|
66
|
-
const parsed = Number.parseInt(raw, 10);
|
|
67
|
-
if (Number.isFinite(parsed)) onVisiblePageChange(parsed);
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const observer = new IntersectionObserver(
|
|
71
|
-
(entries) => {
|
|
72
|
-
for (const entry of entries) {
|
|
73
|
-
ratios.set(entry.target, entry.isIntersecting ? entry.intersectionRatio : 0);
|
|
74
|
-
}
|
|
75
|
-
if (debounceTimer !== null) clearTimeout(debounceTimer);
|
|
76
|
-
debounceTimer = setTimeout(flush, DEBOUNCE_MS);
|
|
77
|
-
},
|
|
78
|
-
{ root, threshold: OBSERVER_THRESHOLDS },
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
observe: (element) => observer.observe(element),
|
|
83
|
-
disconnect: () => {
|
|
84
|
-
observer.disconnect();
|
|
85
|
-
if (debounceTimer !== null) {
|
|
86
|
-
clearTimeout(debounceTimer);
|
|
87
|
-
debounceTimer = null;
|
|
88
|
-
}
|
|
89
|
-
ratios.clear();
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
export function clampReaderPageIndex(value: number, pageCount: number) {
|
|
2
|
-
const normalizedPageCount = normalizeReaderPageCount(pageCount);
|
|
3
|
-
if (normalizedPageCount <= 0) return 0;
|
|
4
|
-
if (!Number.isFinite(value)) return 0;
|
|
5
|
-
return Math.min(Math.max(Math.trunc(value), 0), normalizedPageCount - 1);
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function formatReaderPageNumber(value: number) {
|
|
9
|
-
return String(Math.max(Math.trunc(value), 1)).padStart(2, "0");
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function normalizeReaderPageCount(value: number) {
|
|
13
|
-
if (!Number.isFinite(value)) return 0;
|
|
14
|
-
return Math.max(Math.trunc(value), 0);
|
|
15
|
-
}
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { useLayoutEffect, useMemo, useState, type RefObject } from "react";
|
|
2
|
-
import { scheduleBrowserFrame } from "../shared";
|
|
3
|
-
import {
|
|
4
|
-
formatPageViewportScaleLabel,
|
|
5
|
-
formatPageViewportScaleValue,
|
|
6
|
-
resolvePageViewportScale,
|
|
7
|
-
type PageLayoutMode,
|
|
8
|
-
type PageViewportScaleMode,
|
|
9
|
-
} from "./pageViewportScaleModel";
|
|
10
|
-
|
|
11
|
-
export function usePageViewportScale({
|
|
12
|
-
stageRef,
|
|
13
|
-
pageContainerRef,
|
|
14
|
-
pageCount,
|
|
15
|
-
layoutMode = "single",
|
|
16
|
-
}: {
|
|
17
|
-
stageRef: RefObject<HTMLElement | null>;
|
|
18
|
-
pageContainerRef: RefObject<HTMLElement | null>;
|
|
19
|
-
pageCount: number;
|
|
20
|
-
layoutMode?: PageLayoutMode;
|
|
21
|
-
}) {
|
|
22
|
-
const [scaleMode, setScaleMode] = useState<PageViewportScaleMode>("fit-width");
|
|
23
|
-
const [scale, setScale] = useState(1);
|
|
24
|
-
|
|
25
|
-
useLayoutEffect(() => {
|
|
26
|
-
if (typeof window === "undefined") return undefined;
|
|
27
|
-
|
|
28
|
-
let cancelFrame: (() => void) | null = null;
|
|
29
|
-
|
|
30
|
-
const syncScale = () => {
|
|
31
|
-
cancelFrame?.();
|
|
32
|
-
cancelFrame = scheduleBrowserFrame(() => {
|
|
33
|
-
cancelFrame = null;
|
|
34
|
-
const container = pageContainerRef.current;
|
|
35
|
-
if (!container) return;
|
|
36
|
-
|
|
37
|
-
const pageSurface = container.querySelector<HTMLElement>(".openpress-html-page__html");
|
|
38
|
-
if (!pageSurface) {
|
|
39
|
-
container.style.setProperty("--openpress-page-viewport-scale", "1");
|
|
40
|
-
container.dataset.openpressPageScaleMode = scaleMode;
|
|
41
|
-
container.dataset.openpressPageScale = "1";
|
|
42
|
-
setScale(1);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const stage = stageRef.current ?? container.parentElement;
|
|
47
|
-
const containerStyle = window.getComputedStyle(container);
|
|
48
|
-
const paddingLeft = parseCssPixelValue(containerStyle.paddingLeft);
|
|
49
|
-
const paddingRight = parseCssPixelValue(containerStyle.paddingRight);
|
|
50
|
-
const paddingTop = parseCssPixelValue(containerStyle.paddingTop);
|
|
51
|
-
const paddingBottom = parseCssPixelValue(containerStyle.paddingBottom);
|
|
52
|
-
const columnGap = parseCssPixelValue(containerStyle.columnGap || containerStyle.gap);
|
|
53
|
-
const availableWidth = Math.max(
|
|
54
|
-
1,
|
|
55
|
-
(stage?.clientWidth || container.clientWidth || window.innerWidth) - paddingLeft - paddingRight,
|
|
56
|
-
);
|
|
57
|
-
const availableHeight = Math.max(
|
|
58
|
-
1,
|
|
59
|
-
(stage?.clientHeight || container.clientHeight || window.innerHeight) - paddingTop - paddingBottom,
|
|
60
|
-
);
|
|
61
|
-
const pageWidth = pageSurface.offsetWidth;
|
|
62
|
-
const pageHeight = pageSurface.offsetHeight;
|
|
63
|
-
const canonicalWidth = layoutMode === "spread" ? (pageWidth * 2) + columnGap : pageWidth;
|
|
64
|
-
const canonicalHeight = pageHeight;
|
|
65
|
-
const fitWidthScale = canonicalWidth > 0 ? availableWidth / canonicalWidth : 1;
|
|
66
|
-
const fitPageScale = canonicalWidth > 0 && canonicalHeight > 0
|
|
67
|
-
? Math.min(availableWidth / canonicalWidth, availableHeight / canonicalHeight)
|
|
68
|
-
: 1;
|
|
69
|
-
const nextScale = resolvePageViewportScale({ mode: scaleMode, fitWidthScale, fitPageScale });
|
|
70
|
-
const nextScaleValue = formatPageViewportScaleValue(nextScale);
|
|
71
|
-
|
|
72
|
-
container.style.setProperty("--openpress-page-viewport-scale", nextScaleValue);
|
|
73
|
-
container.dataset.openpressPageScaleMode = scaleMode;
|
|
74
|
-
container.dataset.openpressPageScale = nextScaleValue;
|
|
75
|
-
setScale((current) => (current === nextScale ? current : nextScale));
|
|
76
|
-
});
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
syncScale();
|
|
80
|
-
|
|
81
|
-
const ResizeObserverCtor = window.ResizeObserver;
|
|
82
|
-
const observer = ResizeObserverCtor ? new ResizeObserverCtor(syncScale) : null;
|
|
83
|
-
const stage = stageRef.current;
|
|
84
|
-
const container = pageContainerRef.current;
|
|
85
|
-
if (stage) observer?.observe(stage);
|
|
86
|
-
if (container) observer?.observe(container);
|
|
87
|
-
|
|
88
|
-
window.addEventListener("resize", syncScale);
|
|
89
|
-
window.visualViewport?.addEventListener("resize", syncScale);
|
|
90
|
-
return () => {
|
|
91
|
-
cancelFrame?.();
|
|
92
|
-
observer?.disconnect();
|
|
93
|
-
window.removeEventListener("resize", syncScale);
|
|
94
|
-
window.visualViewport?.removeEventListener("resize", syncScale);
|
|
95
|
-
};
|
|
96
|
-
}, [layoutMode, pageContainerRef, pageCount, scaleMode, stageRef]);
|
|
97
|
-
|
|
98
|
-
const scaleLabel = useMemo(
|
|
99
|
-
() => {
|
|
100
|
-
const labelScale = scaleMode.startsWith("scale-")
|
|
101
|
-
? resolvePageViewportScale({ mode: scaleMode, fitWidthScale: scale, fitPageScale: scale })
|
|
102
|
-
: scale;
|
|
103
|
-
return formatPageViewportScaleLabel(scaleMode, labelScale);
|
|
104
|
-
},
|
|
105
|
-
[scale, scaleMode],
|
|
106
|
-
);
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
scale,
|
|
110
|
-
scaleMode,
|
|
111
|
-
scaleLabel,
|
|
112
|
-
setScaleMode,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function parseCssPixelValue(value: string) {
|
|
117
|
-
const parsed = Number.parseFloat(value);
|
|
118
|
-
return Number.isFinite(parsed) ? parsed : 0;
|
|
119
|
-
}
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState } from "react";
|
|
2
|
-
|
|
3
|
-
export interface UsePanelStateOptions {
|
|
4
|
-
leftPanelBreakpoint?: number;
|
|
5
|
-
rightPanelBreakpoint?: number;
|
|
6
|
-
onAfterResize?: () => void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export interface PanelState {
|
|
10
|
-
leftPanelOpen: boolean;
|
|
11
|
-
rightPanelOpen: boolean;
|
|
12
|
-
toggleLeftPanel: () => void;
|
|
13
|
-
toggleRightPanel: () => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function usePanelState({
|
|
17
|
-
leftPanelBreakpoint,
|
|
18
|
-
rightPanelBreakpoint = 1000,
|
|
19
|
-
onAfterResize,
|
|
20
|
-
}: UsePanelStateOptions = {}): PanelState {
|
|
21
|
-
const shouldOpenLeftPanel = useCallback(
|
|
22
|
-
() =>
|
|
23
|
-
leftPanelBreakpoint === undefined || typeof window === "undefined" || window.innerWidth >= leftPanelBreakpoint,
|
|
24
|
-
[leftPanelBreakpoint],
|
|
25
|
-
);
|
|
26
|
-
const shouldOpenRightPanel = useCallback(
|
|
27
|
-
() => typeof window === "undefined" || window.innerWidth >= rightPanelBreakpoint,
|
|
28
|
-
[rightPanelBreakpoint],
|
|
29
|
-
);
|
|
30
|
-
|
|
31
|
-
const [rightPanelOpen, setRightPanelOpen] = useState(false);
|
|
32
|
-
const [leftPanelOpen, setLeftPanelOpen] = useState(false);
|
|
33
|
-
|
|
34
|
-
useEffect(() => {
|
|
35
|
-
if (typeof window === "undefined") return undefined;
|
|
36
|
-
|
|
37
|
-
const handleResize = () => {
|
|
38
|
-
setLeftPanelOpen((open) => (open && !shouldOpenLeftPanel() ? false : open));
|
|
39
|
-
setRightPanelOpen((open) => (open && !shouldOpenRightPanel() ? false : open));
|
|
40
|
-
onAfterResize?.();
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
handleResize();
|
|
44
|
-
window.addEventListener("resize", handleResize);
|
|
45
|
-
window.visualViewport?.addEventListener("resize", handleResize);
|
|
46
|
-
return () => {
|
|
47
|
-
window.removeEventListener("resize", handleResize);
|
|
48
|
-
window.visualViewport?.removeEventListener("resize", handleResize);
|
|
49
|
-
};
|
|
50
|
-
}, [shouldOpenLeftPanel, shouldOpenRightPanel, onAfterResize]);
|
|
51
|
-
|
|
52
|
-
const toggleLeftPanel = useCallback(() => setLeftPanelOpen((open) => !open), []);
|
|
53
|
-
const toggleRightPanel = useCallback(() => setRightPanelOpen((open) => !open), []);
|
|
54
|
-
|
|
55
|
-
return { leftPanelOpen, rightPanelOpen, toggleLeftPanel, toggleRightPanel };
|
|
56
|
-
}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { useEffect, type MutableRefObject } from "react";
|
|
2
|
-
import { pageIndexFromHash, replacePageRoute } from "./readerPageRoute";
|
|
3
|
-
import { scrollToPage } from "./readerScroll";
|
|
4
|
-
|
|
5
|
-
export interface UseReaderHashSyncOptions {
|
|
6
|
-
stageRef: MutableRefObject<HTMLElement | null>;
|
|
7
|
-
pageRefs: MutableRefObject<Array<HTMLElement | null>>;
|
|
8
|
-
currentPageIndex: number;
|
|
9
|
-
currentPageIndexRef: MutableRefObject<number>;
|
|
10
|
-
normalizedPageCount: number;
|
|
11
|
-
setCurrentPageIndex: (index: number) => void;
|
|
12
|
-
armPendingScrollTarget: (target: number) => void;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function useReaderHashSync({
|
|
16
|
-
stageRef,
|
|
17
|
-
pageRefs,
|
|
18
|
-
currentPageIndex,
|
|
19
|
-
currentPageIndexRef,
|
|
20
|
-
normalizedPageCount,
|
|
21
|
-
setCurrentPageIndex,
|
|
22
|
-
armPendingScrollTarget,
|
|
23
|
-
}: UseReaderHashSyncOptions) {
|
|
24
|
-
// Mirror currentPageIndex into the URL hash so deep links + history work.
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (typeof window === "undefined") return;
|
|
27
|
-
replacePageRoute(currentPageIndex);
|
|
28
|
-
}, [currentPageIndex]);
|
|
29
|
-
|
|
30
|
-
// Listen for hash/back/forward navigation and drive scroll to match.
|
|
31
|
-
useEffect(() => {
|
|
32
|
-
if (typeof window === "undefined") return undefined;
|
|
33
|
-
|
|
34
|
-
const syncFromHash = (behavior: ScrollBehavior) => {
|
|
35
|
-
const refs = pageRefs.current;
|
|
36
|
-
const hashPage = pageIndexFromHash(window.location.hash, normalizedPageCount);
|
|
37
|
-
if (hashPage === null) return;
|
|
38
|
-
// replacePageRoute writes the hash to mirror state; skip if it already
|
|
39
|
-
// matches so we don't fight ourselves.
|
|
40
|
-
if (hashPage === currentPageIndexRef.current) return;
|
|
41
|
-
armPendingScrollTarget(hashPage);
|
|
42
|
-
setCurrentPageIndex(hashPage);
|
|
43
|
-
scrollToPage(refs, hashPage, behavior, stageRef.current);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const onHashChange = () => syncFromHash("smooth");
|
|
47
|
-
window.addEventListener("hashchange", onHashChange);
|
|
48
|
-
window.addEventListener("popstate", onHashChange);
|
|
49
|
-
return () => {
|
|
50
|
-
window.removeEventListener("hashchange", onHashChange);
|
|
51
|
-
window.removeEventListener("popstate", onHashChange);
|
|
52
|
-
};
|
|
53
|
-
}, [
|
|
54
|
-
armPendingScrollTarget,
|
|
55
|
-
currentPageIndexRef,
|
|
56
|
-
normalizedPageCount,
|
|
57
|
-
pageRefs,
|
|
58
|
-
setCurrentPageIndex,
|
|
59
|
-
stageRef,
|
|
60
|
-
]);
|
|
61
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
2
|
-
|
|
3
|
-
export interface UseReaderKeyboardNavOptions {
|
|
4
|
-
nextPage: () => void;
|
|
5
|
-
prevPage: () => void;
|
|
6
|
-
setPage: (pageIndex: number) => void;
|
|
7
|
-
normalizedPageCount: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function useReaderKeyboardNav({
|
|
11
|
-
nextPage,
|
|
12
|
-
prevPage,
|
|
13
|
-
setPage,
|
|
14
|
-
normalizedPageCount,
|
|
15
|
-
}: UseReaderKeyboardNavOptions) {
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const handleKeyDown = (event: KeyboardEvent) => {
|
|
18
|
-
if (isEditableTarget(event.target)) return;
|
|
19
|
-
if (hasActiveTextSelection()) return;
|
|
20
|
-
if (event.key === "ArrowRight" || event.key === "PageDown") {
|
|
21
|
-
event.preventDefault();
|
|
22
|
-
nextPage();
|
|
23
|
-
} else if (event.key === "ArrowLeft" || event.key === "PageUp") {
|
|
24
|
-
event.preventDefault();
|
|
25
|
-
prevPage();
|
|
26
|
-
} else if (event.key === "Home") {
|
|
27
|
-
event.preventDefault();
|
|
28
|
-
setPage(0);
|
|
29
|
-
} else if (event.key === "End") {
|
|
30
|
-
event.preventDefault();
|
|
31
|
-
setPage(Math.max(0, normalizedPageCount - 1));
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
window.addEventListener("keydown", handleKeyDown);
|
|
35
|
-
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
36
|
-
}, [nextPage, prevPage, setPage, normalizedPageCount]);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function isEditableTarget(target: EventTarget | null) {
|
|
40
|
-
if (!(target instanceof HTMLElement)) return false;
|
|
41
|
-
return Boolean(target.closest("input, textarea, select, button, [contenteditable]"));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function hasActiveTextSelection() {
|
|
45
|
-
const selection = window.getSelection?.();
|
|
46
|
-
if (!selection || selection.isCollapsed) return false;
|
|
47
|
-
return Boolean(selection.toString().trim());
|
|
48
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useRef, useState, type RefCallback } from "react";
|
|
2
|
-
import { pageIndexFromHash } from "./readerPageRoute";
|
|
3
|
-
import { createReaderPageRegistry } from "./readerPageRegistry";
|
|
4
|
-
import { clampReaderPageIndex, formatReaderPageNumber, normalizeReaderPageCount } from "./readerStateModel";
|
|
5
|
-
import { createPageVisibilityObserver, scrollToPage } from "./readerScroll";
|
|
6
|
-
import { usePanelState } from "./usePanelState";
|
|
7
|
-
import { useReaderScrollAnchor } from "./useReaderScrollAnchor";
|
|
8
|
-
import { useReaderHashSync } from "./useReaderHashSync";
|
|
9
|
-
import { useReaderKeyboardNav } from "./useReaderKeyboardNav";
|
|
10
|
-
|
|
11
|
-
export interface SetPageOptions {
|
|
12
|
-
behavior?: ScrollBehavior;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface UseReaderRuntimeOptions {
|
|
16
|
-
pageCount: number;
|
|
17
|
-
leftPanelBreakpoint?: number;
|
|
18
|
-
rightPanelBreakpoint?: number;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function useReaderRuntime({
|
|
22
|
-
pageCount,
|
|
23
|
-
leftPanelBreakpoint,
|
|
24
|
-
rightPanelBreakpoint = 1000,
|
|
25
|
-
}: UseReaderRuntimeOptions) {
|
|
26
|
-
const normalizedPageCount = normalizeReaderPageCount(pageCount);
|
|
27
|
-
const stageRef = useRef<HTMLElement | null>(null);
|
|
28
|
-
const [pageRegistrationVersion, setPageRegistrationVersion] = useState(0);
|
|
29
|
-
const pageRegistry = useRef<ReturnType<typeof createReaderPageRegistry<HTMLElement>> | null>(null);
|
|
30
|
-
if (!pageRegistry.current) {
|
|
31
|
-
pageRegistry.current = createReaderPageRegistry<HTMLElement>(setPageRegistrationVersion);
|
|
32
|
-
}
|
|
33
|
-
const pageRefs = useMemo(() => ({
|
|
34
|
-
get current() {
|
|
35
|
-
return pageRegistry.current?.refs ?? [];
|
|
36
|
-
},
|
|
37
|
-
}), []) as { current: Array<HTMLElement | null> };
|
|
38
|
-
|
|
39
|
-
const [currentPageIndex, setCurrentPageIndex] = useState(() => {
|
|
40
|
-
if (typeof window === "undefined") return 0;
|
|
41
|
-
const fromHash = pageIndexFromHash(window.location.hash, normalizedPageCount);
|
|
42
|
-
return fromHash ?? 0;
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
const currentPageIndexRef = useRef(currentPageIndex);
|
|
46
|
-
currentPageIndexRef.current = currentPageIndex;
|
|
47
|
-
|
|
48
|
-
const { pendingScrollTargetRef, armPendingScrollTarget, clearPendingScrollTarget, reAnchorAfterPaint } =
|
|
49
|
-
useReaderScrollAnchor({ stageRef, pageRefs, currentPageIndexRef });
|
|
50
|
-
|
|
51
|
-
const { leftPanelOpen, rightPanelOpen, toggleLeftPanel, toggleRightPanel } = usePanelState({
|
|
52
|
-
leftPanelBreakpoint,
|
|
53
|
-
rightPanelBreakpoint,
|
|
54
|
-
// scroll-snap-type: y mandatory re-aligns to the closest snap point on
|
|
55
|
-
// viewport change, which can land one page off from where the reader was.
|
|
56
|
-
onAfterResize: reAnchorAfterPaint,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// Trim the registry + clamp current page when the page count shrinks.
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
pageRegistry.current?.trim(normalizedPageCount);
|
|
62
|
-
setCurrentPageIndex((idx) => clampReaderPageIndex(idx, normalizedPageCount));
|
|
63
|
-
}, [normalizedPageCount]);
|
|
64
|
-
|
|
65
|
-
// Drive currentPageIndex from visible pages. Suppress intermediates while a
|
|
66
|
-
// programmatic scroll is in flight.
|
|
67
|
-
useEffect(() => {
|
|
68
|
-
const stage = stageRef.current;
|
|
69
|
-
if (!stage) return undefined;
|
|
70
|
-
const observer = createPageVisibilityObserver(stage, (pageIndex) => {
|
|
71
|
-
if (pendingScrollTargetRef.current !== null) {
|
|
72
|
-
if (pageIndex !== pendingScrollTargetRef.current) return;
|
|
73
|
-
clearPendingScrollTarget();
|
|
74
|
-
}
|
|
75
|
-
setCurrentPageIndex((prev) => (prev === pageIndex ? prev : pageIndex));
|
|
76
|
-
});
|
|
77
|
-
if (!observer) return undefined;
|
|
78
|
-
pageRegistry.current?.refs.forEach((el) => el && observer.observe(el));
|
|
79
|
-
return () => observer.disconnect();
|
|
80
|
-
}, [clearPendingScrollTarget, normalizedPageCount, pageRegistrationVersion, pendingScrollTargetRef]);
|
|
81
|
-
|
|
82
|
-
// When refs change (initial mount, pagination kicks in), re-anchor the stage
|
|
83
|
-
// to the page we already believe we're on so scroll-snap mandatory doesn't
|
|
84
|
-
// pull us to whichever page is closest.
|
|
85
|
-
useEffect(() => {
|
|
86
|
-
const refs = pageRegistry.current?.refs ?? [];
|
|
87
|
-
const idx = currentPageIndexRef.current;
|
|
88
|
-
if (idx === 0) return;
|
|
89
|
-
if (!refs[idx]) return;
|
|
90
|
-
armPendingScrollTarget(idx);
|
|
91
|
-
scrollToPage(refs, idx, "instant", stageRef.current);
|
|
92
|
-
}, [armPendingScrollTarget, pageRegistrationVersion]);
|
|
93
|
-
|
|
94
|
-
const setPage = useCallback(
|
|
95
|
-
(pageIndex: number, options: SetPageOptions = {}) => {
|
|
96
|
-
const refs = pageRegistry.current?.refs ?? [];
|
|
97
|
-
const target = clampReaderPageIndex(pageIndex, normalizedPageCount);
|
|
98
|
-
armPendingScrollTarget(target);
|
|
99
|
-
setCurrentPageIndex(target);
|
|
100
|
-
scrollToPage(refs, target, options.behavior ?? "smooth", stageRef.current);
|
|
101
|
-
},
|
|
102
|
-
[armPendingScrollTarget, normalizedPageCount],
|
|
103
|
-
);
|
|
104
|
-
|
|
105
|
-
const nextPage = useCallback(() => {
|
|
106
|
-
setPage(currentPageIndexRef.current + 1);
|
|
107
|
-
}, [setPage]);
|
|
108
|
-
|
|
109
|
-
const prevPage = useCallback(() => {
|
|
110
|
-
setPage(currentPageIndexRef.current - 1);
|
|
111
|
-
}, [setPage]);
|
|
112
|
-
|
|
113
|
-
useReaderHashSync({
|
|
114
|
-
stageRef,
|
|
115
|
-
pageRefs,
|
|
116
|
-
currentPageIndex,
|
|
117
|
-
currentPageIndexRef,
|
|
118
|
-
normalizedPageCount,
|
|
119
|
-
setCurrentPageIndex,
|
|
120
|
-
armPendingScrollTarget,
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
useReaderKeyboardNav({ nextPage, prevPage, setPage, normalizedPageCount });
|
|
124
|
-
|
|
125
|
-
const registerPage = useCallback<(pageIndex: number) => RefCallback<HTMLElement>>(
|
|
126
|
-
(pageIndex) => pageRegistry.current?.registerPage(pageIndex) ?? (() => undefined),
|
|
127
|
-
[],
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
const progressPercent =
|
|
131
|
-
normalizedPageCount <= 1 ? 100 : ((currentPageIndex + 1) / normalizedPageCount) * 100;
|
|
132
|
-
|
|
133
|
-
return {
|
|
134
|
-
stageRef,
|
|
135
|
-
currentPageIndex,
|
|
136
|
-
currentPageLabel: formatReaderPageNumber(currentPageIndex + 1),
|
|
137
|
-
totalPageLabel: formatReaderPageNumber(normalizedPageCount),
|
|
138
|
-
progressPercent,
|
|
139
|
-
leftPanelOpen,
|
|
140
|
-
rightPanelOpen,
|
|
141
|
-
registerPage,
|
|
142
|
-
setPage,
|
|
143
|
-
toggleLeftPanel,
|
|
144
|
-
toggleRightPanel,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useRef, type MutableRefObject } from "react";
|
|
2
|
-
import { scrollToPage } from "./readerScroll";
|
|
3
|
-
|
|
4
|
-
// Generous upper bound on a smooth scrollIntoView. If the target ref is gone or
|
|
5
|
-
// the browser never settles on it, clear the guard so the IO observer regains
|
|
6
|
-
// authority over currentPageIndex.
|
|
7
|
-
const PROGRAMMATIC_SCROLL_FALLBACK_MS = 2500;
|
|
8
|
-
|
|
9
|
-
export interface UseReaderScrollAnchorOptions {
|
|
10
|
-
stageRef: MutableRefObject<HTMLElement | null>;
|
|
11
|
-
pageRefs: MutableRefObject<Array<HTMLElement | null>>;
|
|
12
|
-
currentPageIndexRef: MutableRefObject<number>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface ReaderScrollAnchor {
|
|
16
|
-
pendingScrollTargetRef: MutableRefObject<number | null>;
|
|
17
|
-
armPendingScrollTarget: (target: number) => void;
|
|
18
|
-
clearPendingScrollTarget: () => void;
|
|
19
|
-
reAnchorAfterPaint: () => void;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function useReaderScrollAnchor({
|
|
23
|
-
stageRef,
|
|
24
|
-
pageRefs,
|
|
25
|
-
currentPageIndexRef,
|
|
26
|
-
}: UseReaderScrollAnchorOptions): ReaderScrollAnchor {
|
|
27
|
-
// While a programmatic scroll is in flight, the IntersectionObserver should
|
|
28
|
-
// only accept the destination page (not the intermediates we sweep past).
|
|
29
|
-
const pendingScrollTargetRef = useRef<number | null>(null);
|
|
30
|
-
const pendingScrollClearTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
31
|
-
|
|
32
|
-
const armPendingScrollTarget = useCallback((target: number) => {
|
|
33
|
-
pendingScrollTargetRef.current = target;
|
|
34
|
-
if (pendingScrollClearTimerRef.current !== null) clearTimeout(pendingScrollClearTimerRef.current);
|
|
35
|
-
pendingScrollClearTimerRef.current = setTimeout(() => {
|
|
36
|
-
pendingScrollTargetRef.current = null;
|
|
37
|
-
pendingScrollClearTimerRef.current = null;
|
|
38
|
-
}, PROGRAMMATIC_SCROLL_FALLBACK_MS);
|
|
39
|
-
}, []);
|
|
40
|
-
|
|
41
|
-
const clearPendingScrollTarget = useCallback(() => {
|
|
42
|
-
pendingScrollTargetRef.current = null;
|
|
43
|
-
if (pendingScrollClearTimerRef.current !== null) {
|
|
44
|
-
clearTimeout(pendingScrollClearTimerRef.current);
|
|
45
|
-
pendingScrollClearTimerRef.current = null;
|
|
46
|
-
}
|
|
47
|
-
}, []);
|
|
48
|
-
|
|
49
|
-
useEffect(() => () => clearPendingScrollTarget(), [clearPendingScrollTarget]);
|
|
50
|
-
|
|
51
|
-
// Re-anchor the stage to the page we already believe we're on. scroll-snap
|
|
52
|
-
// mandatory would otherwise snap to whichever page is closest after a layout
|
|
53
|
-
// change. Pin to the active programmatic target if there is one.
|
|
54
|
-
const reAnchorAfterPaint = useCallback(() => {
|
|
55
|
-
if (typeof window === "undefined") return;
|
|
56
|
-
window.requestAnimationFrame(() => {
|
|
57
|
-
const refs = pageRefs.current;
|
|
58
|
-
const target = pendingScrollTargetRef.current ?? currentPageIndexRef.current;
|
|
59
|
-
scrollToPage(refs, target, "instant", stageRef.current);
|
|
60
|
-
});
|
|
61
|
-
}, [currentPageIndexRef, pageRefs, stageRef]);
|
|
62
|
-
|
|
63
|
-
return { pendingScrollTargetRef, armPendingScrollTarget, clearPendingScrollTarget, reAnchorAfterPaint };
|
|
64
|
-
}
|