@open-press/cli 0.8.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.
Files changed (250) hide show
  1. package/README.md +33 -23
  2. package/dist/cli.js +320 -252
  3. package/package.json +9 -8
  4. package/template/core/AGENTS.md +0 -126
  5. package/template/core/CHANGELOG.md +0 -215
  6. package/template/core/README.md +0 -40
  7. package/template/core/engine/cli.mjs +0 -96
  8. package/template/core/engine/commands/_shared.mjs +0 -177
  9. package/template/core/engine/commands/deploy.mjs +0 -31
  10. package/template/core/engine/commands/dev.mjs +0 -49
  11. package/template/core/engine/commands/doctor.mjs +0 -229
  12. package/template/core/engine/commands/export.mjs +0 -8
  13. package/template/core/engine/commands/init.mjs +0 -24
  14. package/template/core/engine/commands/inspect.mjs +0 -35
  15. package/template/core/engine/commands/pdf.mjs +0 -26
  16. package/template/core/engine/commands/preview.mjs +0 -26
  17. package/template/core/engine/commands/render.mjs +0 -17
  18. package/template/core/engine/commands/replace.mjs +0 -41
  19. package/template/core/engine/commands/search.mjs +0 -33
  20. package/template/core/engine/commands/typecheck.mjs +0 -5
  21. package/template/core/engine/commands/upgrade.mjs +0 -159
  22. package/template/core/engine/commands/validate.mjs +0 -17
  23. package/template/core/engine/document-export.mjs +0 -15
  24. package/template/core/engine/init.mjs +0 -90
  25. package/template/core/engine/output/chrome-pdf.d.mts +0 -34
  26. package/template/core/engine/output/chrome-pdf.mjs +0 -358
  27. package/template/core/engine/output/deploy-sync.mjs +0 -15
  28. package/template/core/engine/output/fonts.mjs +0 -62
  29. package/template/core/engine/output/katex-assets.mjs +0 -45
  30. package/template/core/engine/output/page-block.mjs +0 -30
  31. package/template/core/engine/output/pdf-media.mjs +0 -45
  32. package/template/core/engine/output/public-assets.mjs +0 -19
  33. package/template/core/engine/output/static-server.mjs +0 -532
  34. package/template/core/engine/react/caption-numbering.mjs +0 -73
  35. package/template/core/engine/react/comment-endpoint.d.mts +0 -11
  36. package/template/core/engine/react/comment-endpoint.mjs +0 -102
  37. package/template/core/engine/react/comment-marker.mjs +0 -374
  38. package/template/core/engine/react/document-entry.mjs +0 -324
  39. package/template/core/engine/react/document-export.mjs +0 -373
  40. package/template/core/engine/react/http-json.mjs +0 -24
  41. package/template/core/engine/react/mdx-compile.mjs +0 -599
  42. package/template/core/engine/react/measurement-css.mjs +0 -136
  43. package/template/core/engine/react/object-entities.mjs +0 -119
  44. package/template/core/engine/react/pagination/allocator.mjs +0 -122
  45. package/template/core/engine/react/pagination/regions.mjs +0 -81
  46. package/template/core/engine/react/pagination-constants.mjs +0 -3
  47. package/template/core/engine/react/pagination.mjs +0 -9
  48. package/template/core/engine/react/pipeline/allocate.mjs +0 -251
  49. package/template/core/engine/react/pipeline/final-render.mjs +0 -94
  50. package/template/core/engine/react/pipeline/frame-measurement.mjs +0 -302
  51. package/template/core/engine/react/pipeline/press-tree.mjs +0 -135
  52. package/template/core/engine/react/project-asset-endpoint.d.mts +0 -10
  53. package/template/core/engine/react/project-asset-endpoint.mjs +0 -361
  54. package/template/core/engine/react/section-css.mjs +0 -56
  55. package/template/core/engine/react/source-edit-endpoint.d.mts +0 -10
  56. package/template/core/engine/react/source-edit-endpoint.mjs +0 -75
  57. package/template/core/engine/react/sources/heading-numbering.mjs +0 -132
  58. package/template/core/engine/react/sources/mdx-resolver.mjs +0 -439
  59. package/template/core/engine/react/style-discovery.mjs +0 -142
  60. package/template/core/engine/runtime/config.d.mts +0 -40
  61. package/template/core/engine/runtime/config.mjs +0 -175
  62. package/template/core/engine/runtime/file-utils.mjs +0 -106
  63. package/template/core/engine/runtime/file-walk.mjs +0 -22
  64. package/template/core/engine/runtime/inspection.mjs +0 -328
  65. package/template/core/engine/runtime/issue-report.mjs +0 -44
  66. package/template/core/engine/runtime/path-utils.mjs +0 -20
  67. package/template/core/engine/runtime/source-text-tools.d.mts +0 -102
  68. package/template/core/engine/runtime/source-text-tools.mjs +0 -832
  69. package/template/core/engine/runtime/source-workspace.mjs +0 -159
  70. package/template/core/engine/runtime/validation.mjs +0 -174
  71. package/template/core/index.html +0 -13
  72. package/template/core/openpress.config.mjs +0 -12
  73. package/template/core/package.json +0 -91
  74. package/template/core/src/main.tsx +0 -16
  75. package/template/core/src/openpress/app/OpenPressApp.tsx +0 -140
  76. package/template/core/src/openpress/app/OpenPressRuntime.tsx +0 -94
  77. package/template/core/src/openpress/app/index.ts +0 -2
  78. package/template/core/src/openpress/core/Frame.tsx +0 -78
  79. package/template/core/src/openpress/core/FrameContext.tsx +0 -24
  80. package/template/core/src/openpress/core/MdxArea.tsx +0 -34
  81. package/template/core/src/openpress/core/Press.tsx +0 -34
  82. package/template/core/src/openpress/core/cn.ts +0 -4
  83. package/template/core/src/openpress/core/index.tsx +0 -40
  84. package/template/core/src/openpress/core/primitives.tsx +0 -44
  85. package/template/core/src/openpress/core/types.ts +0 -191
  86. package/template/core/src/openpress/core/useSource.ts +0 -28
  87. package/template/core/src/openpress/document-model/anchorMapModel.ts +0 -27
  88. package/template/core/src/openpress/document-model/documentIndexes.ts +0 -329
  89. package/template/core/src/openpress/document-model/documentTypes.ts +0 -138
  90. package/template/core/src/openpress/document-model/index.ts +0 -6
  91. package/template/core/src/openpress/document-model/objectEntityModel.ts +0 -51
  92. package/template/core/src/openpress/document-model/projectIdentityModel.ts +0 -15
  93. package/template/core/src/openpress/document-model/reactDocumentMetadataModel.ts +0 -27
  94. package/template/core/src/openpress/manuscript/index.tsx +0 -238
  95. package/template/core/src/openpress/mdx/index.ts +0 -88
  96. package/template/core/src/openpress/numbering/index.ts +0 -294
  97. package/template/core/src/openpress/reader/PublicReaderPage.tsx +0 -267
  98. package/template/core/src/openpress/reader/ReaderNavigationPanel.tsx +0 -123
  99. package/template/core/src/openpress/reader/index.ts +0 -10
  100. package/template/core/src/openpress/reader/pageViewportScaleModel.ts +0 -73
  101. package/template/core/src/openpress/reader/readerPageRegistry.ts +0 -41
  102. package/template/core/src/openpress/reader/readerPageRoute.ts +0 -21
  103. package/template/core/src/openpress/reader/readerScroll.ts +0 -92
  104. package/template/core/src/openpress/reader/readerStateModel.ts +0 -15
  105. package/template/core/src/openpress/reader/readerTypes.ts +0 -4
  106. package/template/core/src/openpress/reader/usePageViewportScale.ts +0 -119
  107. package/template/core/src/openpress/reader/usePanelState.ts +0 -56
  108. package/template/core/src/openpress/reader/useReaderHashSync.ts +0 -61
  109. package/template/core/src/openpress/reader/useReaderKeyboardNav.ts +0 -48
  110. package/template/core/src/openpress/reader/useReaderRuntime.ts +0 -146
  111. package/template/core/src/openpress/reader/useReaderScrollAnchor.ts +0 -64
  112. package/template/core/src/openpress/shared/Panel.tsx +0 -77
  113. package/template/core/src/openpress/shared/frameScheduler.ts +0 -32
  114. package/template/core/src/openpress/shared/index.ts +0 -4
  115. package/template/core/src/openpress/shared/numberUtils.ts +0 -3
  116. package/template/core/src/openpress/shared/runtimeMode.ts +0 -11
  117. package/template/core/src/openpress/workbench/Workbench.tsx +0 -407
  118. package/template/core/src/openpress/workbench/actions/DeploymentControl.tsx +0 -157
  119. package/template/core/src/openpress/workbench/actions/PageZoomControl.tsx +0 -182
  120. package/template/core/src/openpress/workbench/actions/SearchControl.tsx +0 -345
  121. package/template/core/src/openpress/workbench/actions/deploymentStatusModel.ts +0 -112
  122. package/template/core/src/openpress/workbench/actions/index.ts +0 -5
  123. package/template/core/src/openpress/workbench/actions/useDeploymentWorkbench.ts +0 -136
  124. package/template/core/src/openpress/workbench/dialog/WorkbenchDialog.tsx +0 -72
  125. package/template/core/src/openpress/workbench/dialog/index.ts +0 -1
  126. package/template/core/src/openpress/workbench/document/components/DocumentPanel.tsx +0 -127
  127. package/template/core/src/openpress/workbench/document/components/InlineSourceEditorLayer.tsx +0 -207
  128. package/template/core/src/openpress/workbench/document/components/ReaderStage.tsx +0 -9
  129. package/template/core/src/openpress/workbench/document/hooks/useDocumentWorkbenchModel.ts +0 -34
  130. package/template/core/src/openpress/workbench/document/hooks/useInlineDocumentEditor.ts +0 -525
  131. package/template/core/src/openpress/workbench/document/index.ts +0 -10
  132. package/template/core/src/openpress/workbench/index.ts +0 -2
  133. package/template/core/src/openpress/workbench/inspector/InlineInspectorLayer.tsx +0 -459
  134. package/template/core/src/openpress/workbench/inspector/index.ts +0 -5
  135. package/template/core/src/openpress/workbench/inspector/inlineCommentModel.ts +0 -125
  136. package/template/core/src/openpress/workbench/inspector/inspectorGeometryModel.ts +0 -160
  137. package/template/core/src/openpress/workbench/inspector/inspectorModel.ts +0 -408
  138. package/template/core/src/openpress/workbench/inspector/useInspectorComments.ts +0 -248
  139. package/template/core/src/openpress/workbench/mentions/MentionSuggestionList.tsx +0 -41
  140. package/template/core/src/openpress/workbench/mentions/index.ts +0 -2
  141. package/template/core/src/openpress/workbench/mentions/useComposerMentions.ts +0 -185
  142. package/template/core/src/openpress/workbench/panels/Panel.tsx +0 -1
  143. package/template/core/src/openpress/workbench/panels/PendingCommentsPanel.tsx +0 -76
  144. package/template/core/src/openpress/workbench/panels/WorkbenchControlPanel.tsx +0 -29
  145. package/template/core/src/openpress/workbench/panels/index.ts +0 -3
  146. package/template/core/src/openpress/workbench/project/ProjectEntryPanel.tsx +0 -523
  147. package/template/core/src/openpress/workbench/project/ProjectPreviewDialog.tsx +0 -35
  148. package/template/core/src/openpress/workbench/project/index.ts +0 -2
  149. package/template/core/src/openpress/workbench/project/projectPreviewTypes.ts +0 -11
  150. package/template/core/src/openpress/workbench/project/projectSourceModel.ts +0 -24
  151. package/template/core/src/openpress/workbench/shell/WorkbenchShell.tsx +0 -167
  152. package/template/core/src/openpress/workbench/shell/index.ts +0 -1
  153. package/template/core/src/openpress/workbench/workbenchFormatters.ts +0 -120
  154. package/template/core/src/openpress/workbench/workbenchTypes.ts +0 -35
  155. package/template/core/src/styles/openpress/app-shell.css +0 -251
  156. package/template/core/src/styles/openpress/media-workspace.css +0 -230
  157. package/template/core/src/styles/openpress/print-route.css +0 -184
  158. package/template/core/src/styles/openpress/project-preview-panel.css +0 -924
  159. package/template/core/src/styles/openpress/public-viewer.css +0 -688
  160. package/template/core/src/styles/openpress/reader-runtime.css +0 -980
  161. package/template/core/src/styles/openpress/responsive.css +0 -245
  162. package/template/core/src/styles/openpress/workbench-panels.css +0 -594
  163. package/template/core/src/styles/openpress/workbench.css +0 -1255
  164. package/template/core/src/styles/openpress.css +0 -14
  165. package/template/core/src/vite-env.d.ts +0 -9
  166. package/template/core/tsconfig.json +0 -40
  167. package/template/core/vite.config.ts +0 -584
  168. package/template/packs/academic-paper/document/chapters/01-introduction/content/01-introduction.mdx +0 -35
  169. package/template/packs/academic-paper/document/chapters/02-methods/content/01-methods.mdx +0 -50
  170. package/template/packs/academic-paper/document/chapters/03-results-and-discussion/content/01-results.mdx +0 -47
  171. package/template/packs/academic-paper/document/chapters/04-acknowledgment/content/01-acknowledgment.mdx +0 -26
  172. package/template/packs/academic-paper/document/chapters/05-references/content/01-references.mdx +0 -32
  173. package/template/packs/academic-paper/document/components/ChapterOpenerVisual/index.tsx +0 -76
  174. package/template/packs/academic-paper/document/components/Page.tsx +0 -60
  175. package/template/packs/academic-paper/document/components/TokenSwatchGrid/index.tsx +0 -46
  176. package/template/packs/academic-paper/document/components/TokenSwatchGrid/style.css +0 -63
  177. package/template/packs/academic-paper/document/components/TypeSpecimen/index.tsx +0 -38
  178. package/template/packs/academic-paper/document/components/TypeSpecimen/style.css +0 -111
  179. package/template/packs/academic-paper/document/design.md +0 -279
  180. package/template/packs/academic-paper/document/index.tsx +0 -123
  181. package/template/packs/academic-paper/document/media/README.md +0 -13
  182. package/template/packs/academic-paper/document/media/figure-placeholder.svg +0 -9
  183. package/template/packs/academic-paper/document/openpress.config.mjs +0 -26
  184. package/template/packs/academic-paper/document/theme/README.md +0 -11
  185. package/template/packs/academic-paper/document/theme/base/page-contract.css +0 -522
  186. package/template/packs/academic-paper/document/theme/base/print.css +0 -93
  187. package/template/packs/academic-paper/document/theme/base/typography.css +0 -333
  188. package/template/packs/academic-paper/document/theme/fonts.css +0 -3
  189. package/template/packs/academic-paper/document/theme/page-surfaces/back-cover.css +0 -43
  190. package/template/packs/academic-paper/document/theme/page-surfaces/chapter-opener.css +0 -205
  191. package/template/packs/academic-paper/document/theme/page-surfaces/cover.css +0 -294
  192. package/template/packs/academic-paper/document/theme/page-surfaces/toc.css +0 -149
  193. package/template/packs/academic-paper/document/theme/patterns/_chart-frame.css +0 -49
  194. package/template/packs/academic-paper/document/theme/patterns/figure-grid.css +0 -68
  195. package/template/packs/academic-paper/document/theme/patterns/table-utilities.css +0 -66
  196. package/template/packs/academic-paper/document/theme/shell/reader-controls.css +0 -761
  197. package/template/packs/academic-paper/document/theme/tokens.css +0 -80
  198. package/template/packs/academic-paper/openpress.config.mjs +0 -5
  199. package/template/packs/claude-document/document/chapters/01-document-shape/content/01-document-shape.mdx +0 -51
  200. package/template/packs/claude-document/document/chapters/02-review-loop/content/01-review-loop.mdx +0 -31
  201. package/template/packs/claude-document/document/components/ChapterOpenerVisual.tsx +0 -96
  202. package/template/packs/claude-document/document/components/Page.tsx +0 -37
  203. package/template/packs/claude-document/document/design.md +0 -142
  204. package/template/packs/claude-document/document/index.tsx +0 -94
  205. package/template/packs/claude-document/document/media/README.md +0 -13
  206. package/template/packs/claude-document/document/openpress.config.mjs +0 -26
  207. package/template/packs/claude-document/document/theme/README.md +0 -15
  208. package/template/packs/claude-document/document/theme/base/page-contract.css +0 -525
  209. package/template/packs/claude-document/document/theme/base/print.css +0 -93
  210. package/template/packs/claude-document/document/theme/base/typography.css +0 -612
  211. package/template/packs/claude-document/document/theme/fonts.css +0 -4
  212. package/template/packs/claude-document/document/theme/page-surfaces/back-cover.css +0 -72
  213. package/template/packs/claude-document/document/theme/page-surfaces/chapter-opener.css +0 -236
  214. package/template/packs/claude-document/document/theme/page-surfaces/cover.css +0 -309
  215. package/template/packs/claude-document/document/theme/page-surfaces/toc.css +0 -225
  216. package/template/packs/claude-document/document/theme/patterns/_chart-frame.css +0 -53
  217. package/template/packs/claude-document/document/theme/patterns/figure-grid.css +0 -68
  218. package/template/packs/claude-document/document/theme/patterns/table-utilities.css +0 -66
  219. package/template/packs/claude-document/document/theme/shell/reader-controls.css +0 -789
  220. package/template/packs/claude-document/document/theme/tokens.css +0 -89
  221. package/template/packs/claude-document/openpress.config.mjs +0 -5
  222. package/template/packs/editorial-monograph/document/chapters/01-product-and-use-cases/content/01-product-and-use-cases.mdx +0 -31
  223. package/template/packs/editorial-monograph/document/chapters/02-workflow/content/01-workflow.mdx +0 -89
  224. package/template/packs/editorial-monograph/document/chapters/03-agent-skills-contributors/content/01-agent-skills-contributors.mdx +0 -51
  225. package/template/packs/editorial-monograph/document/chapters/04-validation-deploy/content/01-validation-deploy.mdx +0 -39
  226. package/template/packs/editorial-monograph/document/components/ChapterOpenerVisual/index.tsx +0 -76
  227. package/template/packs/editorial-monograph/document/components/Page.tsx +0 -37
  228. package/template/packs/editorial-monograph/document/components/TokenSwatchGrid/index.tsx +0 -46
  229. package/template/packs/editorial-monograph/document/components/TokenSwatchGrid/style.css +0 -63
  230. package/template/packs/editorial-monograph/document/components/TypeSpecimen/index.tsx +0 -38
  231. package/template/packs/editorial-monograph/document/components/TypeSpecimen/style.css +0 -111
  232. package/template/packs/editorial-monograph/document/design.md +0 -279
  233. package/template/packs/editorial-monograph/document/index.tsx +0 -97
  234. package/template/packs/editorial-monograph/document/media/README.md +0 -13
  235. package/template/packs/editorial-monograph/document/openpress.config.mjs +0 -26
  236. package/template/packs/editorial-monograph/document/theme/README.md +0 -11
  237. package/template/packs/editorial-monograph/document/theme/base/page-contract.css +0 -505
  238. package/template/packs/editorial-monograph/document/theme/base/print.css +0 -93
  239. package/template/packs/editorial-monograph/document/theme/base/typography.css +0 -336
  240. package/template/packs/editorial-monograph/document/theme/fonts.css +0 -3
  241. package/template/packs/editorial-monograph/document/theme/page-surfaces/back-cover.css +0 -43
  242. package/template/packs/editorial-monograph/document/theme/page-surfaces/chapter-opener.css +0 -205
  243. package/template/packs/editorial-monograph/document/theme/page-surfaces/cover.css +0 -147
  244. package/template/packs/editorial-monograph/document/theme/page-surfaces/toc.css +0 -149
  245. package/template/packs/editorial-monograph/document/theme/patterns/_chart-frame.css +0 -49
  246. package/template/packs/editorial-monograph/document/theme/patterns/figure-grid.css +0 -68
  247. package/template/packs/editorial-monograph/document/theme/patterns/table-utilities.css +0 -66
  248. package/template/packs/editorial-monograph/document/theme/shell/reader-controls.css +0 -761
  249. package/template/packs/editorial-monograph/document/theme/tokens.css +0 -80
  250. package/template/packs/editorial-monograph/openpress.config.mjs +0 -5
@@ -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
- }
@@ -1,77 +0,0 @@
1
- import { type ComponentPropsWithoutRef } from "react";
2
- import { cn } from "../core/cn";
3
-
4
- type PanelProps = ComponentPropsWithoutRef<"section">;
5
- type PanelHeaderProps = ComponentPropsWithoutRef<"header">;
6
- type PanelDivProps = ComponentPropsWithoutRef<"div">;
7
- type PanelTextProps = ComponentPropsWithoutRef<"p">;
8
- type PanelTitleProps = ComponentPropsWithoutRef<"h2">;
9
- type PanelSectionTitleProps = ComponentPropsWithoutRef<"h3">;
10
- type PanelButtonProps = ComponentPropsWithoutRef<"button">;
11
-
12
- function PanelRoot({ className, ...props }: PanelProps) {
13
- return <section {...props} className={cn("openpress-panel", className)} />;
14
- }
15
-
16
- function PanelHeader({ className, ...props }: PanelHeaderProps) {
17
- return <header {...props} className={cn("openpress-panel-header", className)} />;
18
- }
19
-
20
- function PanelKicker({ className, ...props }: ComponentPropsWithoutRef<"span">) {
21
- return <span {...props} className={cn("openpress-panel-kicker", className)} />;
22
- }
23
-
24
- function PanelTitle({ className, ...props }: PanelTitleProps) {
25
- return <h2 {...props} className={cn("openpress-panel-title", className)} />;
26
- }
27
-
28
- function PanelDescription({ className, ...props }: PanelTextProps) {
29
- return <p {...props} className={cn("openpress-panel-description", className)} />;
30
- }
31
-
32
- function PanelActions({ className, ...props }: PanelDivProps) {
33
- return <div {...props} className={cn("openpress-panel-actions", className)} />;
34
- }
35
-
36
- function PanelActionButton({ className, ...props }: PanelButtonProps) {
37
- return <button {...props} className={cn("openpress-panel-action-button", className)} />;
38
- }
39
-
40
- function PanelBody({ className, ...props }: PanelDivProps) {
41
- return <div {...props} className={cn("openpress-panel-body", className)} />;
42
- }
43
-
44
- function PanelSection({ className, ...props }: PanelProps) {
45
- return <section {...props} className={cn("openpress-panel-section", className)} />;
46
- }
47
-
48
- function PanelSectionTitle({ className, ...props }: PanelSectionTitleProps) {
49
- return <h3 {...props} className={cn("openpress-panel-section-title", className)} />;
50
- }
51
-
52
- function PanelSectionDescription({ className, ...props }: PanelTextProps) {
53
- return <p {...props} className={cn("openpress-panel-section-description", className)} />;
54
- }
55
-
56
- function PanelEmpty({ className, ...props }: PanelDivProps) {
57
- return <div {...props} className={cn("openpress-panel-empty", className)} />;
58
- }
59
-
60
- function PanelError({ className, role = "alert", ...props }: PanelTextProps) {
61
- return <p {...props} role={role} className={cn("openpress-panel-error", className)} />;
62
- }
63
-
64
- export const Panel = Object.assign(PanelRoot, {
65
- Header: PanelHeader,
66
- Kicker: PanelKicker,
67
- Title: PanelTitle,
68
- Description: PanelDescription,
69
- Actions: PanelActions,
70
- ActionButton: PanelActionButton,
71
- Body: PanelBody,
72
- Section: PanelSection,
73
- SectionTitle: PanelSectionTitle,
74
- SectionDescription: PanelSectionDescription,
75
- Empty: PanelEmpty,
76
- Error: PanelError,
77
- });
@@ -1,32 +0,0 @@
1
- type BrowserFrameCallback = (timestamp: number) => void;
2
-
3
- export function scheduleBrowserFrame(callback: BrowserFrameCallback) {
4
- if (canUseAnimationFrame()) {
5
- const frame = window.requestAnimationFrame(callback);
6
- return () => window.cancelAnimationFrame(frame);
7
- }
8
-
9
- const timer = window.setTimeout(() => callback(now()), 0);
10
- return () => window.clearTimeout(timer);
11
- }
12
-
13
- export function waitForBrowserFrame() {
14
- return new Promise<void>((resolve) => {
15
- scheduleBrowserFrame(() => resolve());
16
- });
17
- }
18
-
19
- function canUseAnimationFrame() {
20
- return (
21
- typeof window !== "undefined" &&
22
- typeof window.requestAnimationFrame === "function" &&
23
- typeof document !== "undefined" &&
24
- document.visibilityState !== "hidden"
25
- );
26
- }
27
-
28
- function now() {
29
- return typeof performance !== "undefined" && typeof performance.now === "function"
30
- ? performance.now()
31
- : Date.now();
32
- }
@@ -1,4 +0,0 @@
1
- export * from "./frameScheduler";
2
- export * from "./numberUtils";
3
- export * from "./Panel";
4
- export * from "./runtimeMode";
@@ -1,3 +0,0 @@
1
- export function clampNumber(value: number, min: number, max: number) {
2
- return Math.min(Math.max(value, min), Math.max(min, max));
3
- }
@@ -1,11 +0,0 @@
1
- export function isLocalWorkspaceHost(hostname: string) {
2
- return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
3
- }
4
-
5
- export function isWorkspaceModeLocation(location: Pick<Location, "hostname" | "search">) {
6
- return isLocalWorkspaceHost(location.hostname) && new URLSearchParams(location.search).has("dev");
7
- }
8
-
9
- export function isPrintModeLocation(location: Pick<Location, "search">) {
10
- return new URLSearchParams(location.search).has("print");
11
- }