@libraz/mejiro-react 0.9.0 → 0.9.1

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 (3) hide show
  1. package/dist/index.d.ts +313 -123
  2. package/dist/index.js +1556 -1201
  3. package/package.json +8 -8
package/dist/index.d.ts CHANGED
@@ -32,6 +32,10 @@ declare function resolveMessages(locale: MejiroLocale | undefined, overrides: Pa
32
32
  /** Replace `{name}` placeholders in a template. */
33
33
  declare function format(template: string, vars: Record<string, string | number>): string;
34
34
 
35
+ /**
36
+ * Layout of {@link MejiroChapterNav}: `'select'` is a compact dropdown,
37
+ * `'panel'` a chapter list with titles, headings and a text preview.
38
+ */
35
39
  type MejiroChapterNavVariant = 'select' | 'panel';
36
40
  /** Props for {@link MejiroChapterNav}. */
37
41
  interface MejiroChapterNavProps {
@@ -58,7 +62,7 @@ declare function MejiroChapterNav({ epub, chapter, onChange, variant, }: MejiroC
58
62
  interface MejiroDropZoneProps {
59
63
  /** File `accept` filter for the picker. @defaultValue '.epub' */
60
64
  accept?: string;
61
- /** Validator. Defaults to `name.endsWith('.epub')`. */
65
+ /** Validator. Defaults to the `accept` filter, with extensions matched case-insensitively. */
62
66
  validateFile?: (file: File) => boolean;
63
67
  /** Called with the selected/dropped file. */
64
68
  onFile: (file: File) => void;
@@ -96,6 +100,7 @@ interface MejiroSettingsPanelProps {
96
100
  */
97
101
  declare function MejiroSettingsPanel({ open, settings, onChange, fonts, minFontSize, maxFontSize, }: MejiroSettingsPanelProps): ReactNode;
98
102
 
103
+ /** Props for {@link MejiroEditor}. */
99
104
  interface MejiroEditorProps {
100
105
  /** URL fetched and loaded on mount. */
101
106
  epubUrl?: string;
@@ -152,7 +157,10 @@ interface MejiroEditorProps {
152
157
  onLoad?: (editor: EditableEpub) => void;
153
158
  /** Called after export completes. */
154
159
  onExport?: (buffer: ArrayBuffer) => void;
155
- /** Called when loading or parsing an EPUB fails. */
160
+ /**
161
+ * Called when loading, image insertion or export fails. The error is also
162
+ * shown in the editor.
163
+ */
156
164
  onError?: (error: Error) => void;
157
165
  }
158
166
  /**
@@ -227,8 +235,9 @@ interface UseImageOverlayReturn {
227
235
  * with automatic text reflow via {@link ChapterLayout.syncImages}.
228
236
  *
229
237
  * While an overlay is active the exclusion is re-issued whenever `layout` is
230
- * replaced or `spreadIdx` changes, so a re-layout or a page turn never leaves
231
- * the overlay drawn without its matching text reflow.
238
+ * replaced, `spreadIdx` changes or `options.margin` changes, so a re-layout, a
239
+ * page turn or a new margin never leaves the overlay drawn without its matching
240
+ * text reflow.
232
241
  *
233
242
  * @param layout - Current chapter layout (or `null` if not yet loaded).
234
243
  * @param spreadIdx - Current spread index.
@@ -262,6 +271,10 @@ interface MejiroImageOverlayProps {
262
271
  */
263
272
  declare function MejiroImageOverlay({ rect, label, onOverlayPointerDown, onResizePointerDown, onClose, }: MejiroImageOverlayProps): ReactNode;
264
273
 
274
+ /**
275
+ * Where {@link MejiroReader} places its chapter navigation: the header
276
+ * dropdown (`'select'`), the side chapter list (`'panel'`), both, or neither.
277
+ */
265
278
  type MejiroChapterNavMode = 'select' | 'panel' | 'both' | 'none';
266
279
  /**
267
280
  * Reading-flow mode for {@link MejiroReader}.
@@ -272,8 +285,9 @@ type MejiroReaderMode = 'paginated' | 'scroll';
272
285
  /**
273
286
  * Two-page vs single-page rendering of a spread.
274
287
  * - `double` — always render two pages (default).
275
- * - `single` — render only the right page, centered.
276
- * - `auto` — switch based on the surface aspect ratio (single when portrait).
288
+ * - `single` — render one page at a time, centered; spread indices count pages.
289
+ * - `auto` — switch based on the surface aspect ratio (single when portrait),
290
+ * converting the index so the page on screen stays visible.
277
291
  */
278
292
  type MejiroSpreadMode = 'double' | 'single' | 'auto';
279
293
  /**
@@ -305,9 +319,13 @@ type MejiroTheme = MejiroThemeName | {
305
319
  };
306
320
  /** Reading position exposed by {@link MejiroReaderHandle.getReadingPosition}. */
307
321
  interface ReadingPosition {
322
+ /** Zero-based index of the current chapter. */
308
323
  chapter: number;
324
+ /** Zero-based index of the current spread within the chapter. */
309
325
  spreadIdx: number;
326
+ /** Number of pages in the current chapter's layout. */
310
327
  totalPages: number;
328
+ /** Number of spreads in the current chapter's layout. */
311
329
  totalSpreads: number;
312
330
  }
313
331
  /**
@@ -357,14 +375,16 @@ interface MejiroReaderHandle {
357
375
  */
358
376
  goToAnchor(anchor: ReadingAnchor): Promise<void>;
359
377
  /**
360
- * Returns the {@link ReadingAnchor} at the start of the current spread,
361
- * or `null` if the layout is not ready.
378
+ * Returns the {@link ReadingAnchor} at the start of the text on the current
379
+ * spread, or `null` if the layout is not ready. When an image blocks every
380
+ * page of the spread, the anchor is where the text resumes after it; `null`
381
+ * again if none follows.
362
382
  */
363
383
  getAnchor(): ReadingAnchor | null;
364
384
  /**
365
385
  * Returns the half-open range of {@link ReadingAnchor}s visible on the
366
- * current spread. `end` points at the start of the next spread (or the end
367
- * of the chapter for the last spread).
386
+ * current spread. `end` points at the start of the text on the next spread
387
+ * that has any (or the end of the chapter when none does).
368
388
  */
369
389
  getVisibleRange(): {
370
390
  start: ReadingAnchor;
@@ -376,8 +396,9 @@ interface MejiroReaderHandle {
376
396
  *
377
397
  * Successive calls are coalesced into a single application; the returned
378
398
  * promise resolves once that application has settled. A failed application
379
- * (typically a font that could not be loaded) is reported through
380
- * {@link MejiroReaderProps.onError} rather than rejecting the promise.
399
+ * (such as a font that measures as a fallback under `strictFontCheck`) is
400
+ * reported through {@link MejiroReaderProps.onError} rather than rejecting
401
+ * the promise.
381
402
  */
382
403
  setOptions(partial: Partial<BookOptions>): Promise<void>;
383
404
  /**
@@ -434,7 +455,9 @@ interface MejiroReaderCommonProps {
434
455
  /**
435
456
  * Controlled spread index. When supplied, the reader is driven by this
436
457
  * value and emits {@link MejiroReaderProps.onSpreadIdxChange} on user
437
- * navigation. Combine with `useReadingPosition` for save/restore.
458
+ * navigation. A spread index does not survive reflow, so persist the
459
+ * reading position with `useReadingPosition` and the handle's
460
+ * `getAnchor` / `goToAnchor` instead.
438
461
  */
439
462
  spreadIdx?: number;
440
463
  /**
@@ -451,8 +474,10 @@ interface MejiroReaderCommonProps {
451
474
  */
452
475
  mode?: MejiroReaderMode;
453
476
  /**
454
- * Spread layout. `double` (default) renders two pages; `single` renders
455
- * only the right page; `auto` flips to `single` for portrait viewports.
477
+ * Spread layout. `double` (default) renders two pages; `single` renders one
478
+ * page at a time, and `spreadIdx`, `goToSpread`, `onSpreadIdxChange` and
479
+ * `spreadChanged` then count pages; `auto` flips to `single` for portrait
480
+ * viewports, keeping the page on screen visible.
456
481
  */
457
482
  spreadMode?: MejiroSpreadMode;
458
483
  /**
@@ -489,7 +514,7 @@ interface MejiroReaderCommonProps {
489
514
  * component directly, so hosts that accept them should tighten the
490
515
  * defaults here.
491
516
  */
492
- limits?: EpubParseLimits;
517
+ limits?: Partial<EpubParseLimits>;
493
518
  /**
494
519
  * Custom EPUB fetcher used in place of the global `fetch`. Overrides
495
520
  * {@link fetchOptions} when set.
@@ -585,7 +610,8 @@ interface MejiroReaderCommonProps {
585
610
  onLoad?: (book: EpubBook) => void;
586
611
  /**
587
612
  * Called when loading or parsing an EPUB fails, and when applying an option
588
- * change fails (typically a font that could not be loaded).
613
+ * change fails (such as a font that measures as a fallback under
614
+ * `strictFontCheck`; without it, a font that fails to load is not an error).
589
615
  */
590
616
  onError?: (error: Error) => void;
591
617
  /** Called when the chapter index changes. */
@@ -655,18 +681,31 @@ interface MejiroReaderManuscriptProps extends MejiroReaderCommonProps {
655
681
  * modes — TypeScript prevents passing more than one source at once.
656
682
  */
657
683
  type MejiroReaderProps = MejiroReaderControlledProps | MejiroReaderUrlProps | MejiroReaderFileProps | MejiroReaderManuscriptProps;
684
+ /**
685
+ * Full-page EPUB reader. Composes the package's primitives into a working
686
+ * reader; features opt out via the `enableX` props or all chrome via `bare`.
687
+ * The `ref` exposes {@link MejiroReaderHandle} for imperative navigation.
688
+ */
658
689
  declare const MejiroReader: react.ForwardRefExoticComponent<MejiroReaderProps & react.RefAttributes<MejiroReaderHandle>>;
659
690
 
691
+ /** One chapter of the manuscript being edited. */
660
692
  interface ManuscriptEditorChapter {
693
+ /** Stable identifier; survives reordering and title edits. */
661
694
  id: string;
695
+ /** Chapter title; an empty title falls back to the localized "untitled" label. */
662
696
  title: string;
697
+ /** Chapter body in the editor's manuscript notation. */
663
698
  body: string;
664
699
  }
665
700
  /** Autosave payload emitted by {@link MejiroManuscriptEditor}. */
666
701
  interface ManuscriptAutosaveDraft {
702
+ /** Book title as currently entered. */
667
703
  title: string;
704
+ /** Author name as currently entered. */
668
705
  author: string;
706
+ /** Selected cover image, or `null` when none is set. */
669
707
  cover: File | null;
708
+ /** Chapters in reading order, as plain (non-reactive) copies. */
670
709
  chapters: ManuscriptEditorChapter[];
671
710
  }
672
711
  /**
@@ -676,14 +715,23 @@ interface ManuscriptAutosaveDraft {
676
715
  * supplied here.
677
716
  */
678
717
  interface ManuscriptPreviewProps {
718
+ /** Header subtitle of the preview reader. */
679
719
  subtitle?: string;
720
+ /** Header title of the preview reader. */
680
721
  title?: string;
722
+ /** Where the preview places its chapter navigation. @defaultValue 'panel' */
681
723
  chapterNavMode?: 'select' | 'panel' | 'both' | 'none';
724
+ /** Show the preview header. */
682
725
  enableHeader?: boolean;
726
+ /** Show the preview chapter navigation. */
683
727
  enableChapterNav?: boolean;
728
+ /** Show the preview settings panel toggle. */
684
729
  enableSettings?: boolean;
730
+ /** Show the preview layout statistics. */
685
731
  enableStats?: boolean;
732
+ /** Enable arrow-key page turns in the preview. */
686
733
  enableKeyboard?: boolean;
734
+ /** Show the preview page indicator. */
687
735
  enablePageIndicator?: boolean;
688
736
  /** Book options forwarded to the embedded reader preview. */
689
737
  options?: Partial<BookOptions>;
@@ -698,6 +746,7 @@ interface ManuscriptPreviewProps {
698
746
  * to `true` to demo the fullscreen-reader behavior.
699
747
  */
700
748
  enableSurfaceTap?: boolean;
749
+ /** Render the preview without any reader chrome. */
701
750
  bare?: boolean;
702
751
  }
703
752
  /** Props for {@link MejiroManuscriptEditor}. */
@@ -861,6 +910,60 @@ interface MejiroPageViewProps {
861
910
  */
862
911
  declare function MejiroPageView({ result, fontFamily, lineSpacing, slotMode, className, style, }: MejiroPageViewProps): ReactNode;
863
912
 
913
+ /** A single image overlay placed on a specific spread. */
914
+ interface MultiImageItem {
915
+ /** Stable ID for the overlay (auto-assigned on add). */
916
+ id: string;
917
+ /** Position and size in pixels, relative to the right page. */
918
+ rect: ImageRect;
919
+ }
920
+ /** Options for {@link useMultiImageOverlay}. */
921
+ interface UseMultiImageOverlayOptions {
922
+ /** Default image width. @defaultValue 120 */
923
+ defaultWidth?: number;
924
+ /** Default image height. @defaultValue 160 */
925
+ defaultHeight?: number;
926
+ /** Default x. @defaultValue 80 */
927
+ defaultX?: number;
928
+ /** Default y. @defaultValue 100 */
929
+ defaultY?: number;
930
+ /** Margin around each image, in pixels. Defaults to the book's font size. */
931
+ margin?: number;
932
+ /** Called after every reflow caused by adding/removing/moving an image. */
933
+ onUpdate?: (spread: SpreadResult) => void;
934
+ }
935
+ /** Return value of {@link useMultiImageOverlay}. */
936
+ interface UseMultiImageOverlayReturn {
937
+ /**
938
+ * Map of all images keyed by spread index. A new map whenever the images or
939
+ * the margin they are applied with change, and a replacement layout has them
940
+ * before any view renders from it, so this map and the layout together key
941
+ * anything derived from the layout.
942
+ */
943
+ imagesBySpread: Map<number, MultiImageItem[]>;
944
+ /** Images on the current spread; a new array whenever `imagesBySpread` changes. */
945
+ currentImages: MultiImageItem[];
946
+ /** Whether any spread has at least one image. */
947
+ hasImages: boolean;
948
+ /** Add a new image on the current spread. */
949
+ addImage: (overrides?: Partial<ImageRect>) => MultiImageItem;
950
+ /** Remove the image with the given id. */
951
+ removeImage: (id: string) => void;
952
+ /** Replace an image's rectangle. */
953
+ updateImage: (id: string, rect: Partial<ImageRect>) => void;
954
+ /** Remove every image (or only those on `spreadIdx` if given). */
955
+ clearImages: (spreadIdx?: number) => void;
956
+ /** Pointer-down handler for the overlay body (drag). */
957
+ onOverlayPointerDown: (id: string, e: PointerEvent) => void;
958
+ /** Pointer-down handler for the resize handle. */
959
+ onResizePointerDown: (id: string, e: PointerEvent) => void;
960
+ }
961
+ /**
962
+ * React hook that manages multiple draggable/resizable image overlays
963
+ * per spread with automatic reflow via {@link ChapterLayout.setImages}.
964
+ */
965
+ declare function useMultiImageOverlay(layout: ChapterLayout | null, spreadIdx: number, options?: UseMultiImageOverlayOptions): UseMultiImageOverlayReturn;
966
+
864
967
  /** Props for {@link MejiroScrollView}. */
865
968
  interface MejiroScrollViewProps {
866
969
  /** Layout containing the pages to render. */
@@ -891,6 +994,31 @@ interface MejiroScrollViewProps {
891
994
  scrollToPage?: number;
892
995
  /** Vertical gap between pages (px). @defaultValue 24 */
893
996
  pageGap?: number;
997
+ /**
998
+ * Selection rectangles to highlight. Compute via
999
+ * {@link ChapterLayout.selectionRects}; each entry is painted on the page its
1000
+ * `pageIdx` addresses. An entry may carry a `color` fill.
1001
+ */
1002
+ selectionRects?: readonly (AnchorRect & {
1003
+ color?: string;
1004
+ })[];
1005
+ /** Zero-based spread whose pages carry {@link MejiroScrollViewProps.images}. */
1006
+ spreadIdx?: number;
1007
+ /**
1008
+ * Image overlays on the spread at {@link MejiroScrollViewProps.spreadIdx}.
1009
+ * Each renders on the page whose area contains its centre (see
1010
+ * `overlayPageSide`), matching {@link MejiroSpread}'s single-page routing.
1011
+ * The layout reflows in place when its images change, so a new array here is
1012
+ * also what re-reads the pages; `useMultiImageOverlay`'s `currentImages`
1013
+ * provides one on every change.
1014
+ */
1015
+ images?: MultiImageItem[];
1016
+ /** Pointer-down on an image overlay. */
1017
+ onImagePointerDown?: (id: string, e: PointerEvent) => void;
1018
+ /** Pointer-down on an image resize handle. */
1019
+ onImageResizePointerDown?: (id: string, e: PointerEvent) => void;
1020
+ /** Image overlay close button. */
1021
+ onImageClose?: (id: string) => void;
894
1022
  }
895
1023
  /**
896
1024
  * Continuous-scroll variant of {@link MejiroSpread}. Stacks every page in
@@ -898,7 +1026,7 @@ interface MejiroScrollViewProps {
898
1026
  * detected via `IntersectionObserver` and reported through
899
1027
  * {@link MejiroScrollViewProps.onVisiblePageChange}.
900
1028
  */
901
- declare function MejiroScrollView({ layout, pageWidth, pageHeight, contentHeight, fontFamily, fontSize, lineSpacing, slotMode, onVisiblePageChange, scrollToPage, pageGap, }: MejiroScrollViewProps): ReactNode;
1029
+ declare function MejiroScrollView({ layout, pageWidth, pageHeight, contentHeight, fontFamily, fontSize, lineSpacing, slotMode, onVisiblePageChange, scrollToPage, pageGap, selectionRects, spreadIdx, images, onImagePointerDown, onImageResizePointerDown, onImageClose, }: MejiroScrollViewProps): ReactNode;
902
1030
 
903
1031
  /** Props for {@link MejiroSelectionLayer}. */
904
1032
  interface MejiroSelectionLayerProps {
@@ -986,7 +1114,7 @@ interface MejiroShelfProps<T = unknown> {
986
1114
  currentId?: string;
987
1115
  /** Called when the user picks a volume. */
988
1116
  onSelect?: (volume: VolumeInfo<T>) => void;
989
- /** Heading shown above the grid. @defaultValue 'Library' */
1117
+ /** Heading shown above the grid. Defaults to the active message catalog's `shelfTitle`. */
990
1118
  title?: string;
991
1119
  }
992
1120
  /**
@@ -995,55 +1123,6 @@ interface MejiroShelfProps<T = unknown> {
995
1123
  */
996
1124
  declare function MejiroShelf<T = unknown>({ volumes, currentId, onSelect, title, }: MejiroShelfProps<T>): ReactNode;
997
1125
 
998
- /** A single image overlay placed on a specific spread. */
999
- interface MultiImageItem {
1000
- /** Stable ID for the overlay (auto-assigned on add). */
1001
- id: string;
1002
- /** Position and size in pixels, relative to the right page. */
1003
- rect: ImageRect;
1004
- }
1005
- /** Options for {@link useMultiImageOverlay}. */
1006
- interface UseMultiImageOverlayOptions {
1007
- /** Default image width. @defaultValue 120 */
1008
- defaultWidth?: number;
1009
- /** Default image height. @defaultValue 160 */
1010
- defaultHeight?: number;
1011
- /** Default x. @defaultValue 80 */
1012
- defaultX?: number;
1013
- /** Default y. @defaultValue 100 */
1014
- defaultY?: number;
1015
- /** Margin around each image, in pixels. Defaults to the book's font size. */
1016
- margin?: number;
1017
- /** Called after every reflow caused by adding/removing/moving an image. */
1018
- onUpdate?: (spread: SpreadResult) => void;
1019
- }
1020
- /** Return value of {@link useMultiImageOverlay}. */
1021
- interface UseMultiImageOverlayReturn {
1022
- /** Map of all images keyed by spread index. */
1023
- imagesBySpread: Map<number, MultiImageItem[]>;
1024
- /** Images on the current spread. */
1025
- currentImages: MultiImageItem[];
1026
- /** Whether any spread has at least one image. */
1027
- hasImages: boolean;
1028
- /** Add a new image on the current spread. */
1029
- addImage: (overrides?: Partial<ImageRect>) => MultiImageItem;
1030
- /** Remove the image with the given id. */
1031
- removeImage: (id: string) => void;
1032
- /** Replace an image's rectangle. */
1033
- updateImage: (id: string, rect: Partial<ImageRect>) => void;
1034
- /** Remove every image (or only those on `spreadIdx` if given). */
1035
- clearImages: (spreadIdx?: number) => void;
1036
- /** Pointer-down handler for the overlay body (drag). */
1037
- onOverlayPointerDown: (id: string, e: PointerEvent) => void;
1038
- /** Pointer-down handler for the resize handle. */
1039
- onResizePointerDown: (id: string, e: PointerEvent) => void;
1040
- }
1041
- /**
1042
- * React hook that manages multiple draggable/resizable image overlays
1043
- * per spread with automatic reflow via {@link ChapterLayout.setImages}.
1044
- */
1045
- declare function useMultiImageOverlay(layout: ChapterLayout | null, spreadIdx: number, options?: UseMultiImageOverlayOptions): UseMultiImageOverlayReturn;
1046
-
1047
1126
  /** Props for {@link MejiroSpread}. */
1048
1127
  interface MejiroSpreadProps {
1049
1128
  /** Spread data to render. */
@@ -1066,7 +1145,11 @@ interface MejiroSpreadProps {
1066
1145
  rightHeader?: PageHeaderData;
1067
1146
  /** Header data for the left page. */
1068
1147
  leftHeader?: PageHeaderData;
1069
- /** Image overlays on the current spread. */
1148
+ /**
1149
+ * Image overlays on the current spread, relative to the right page. With
1150
+ * {@link MejiroSpreadProps.singlePage}, only those whose centre lies on the
1151
+ * page shown are drawn.
1152
+ */
1070
1153
  images?: MultiImageItem[];
1071
1154
  /**
1072
1155
  * Force slot-mode rendering on both pages. When omitted, each page falls
@@ -1117,11 +1200,16 @@ interface MejiroSpreadProps {
1117
1200
  */
1118
1201
  onSelectionChange?: (range: AnchorRange | null) => void;
1119
1202
  /**
1120
- * Hide the left page and render only the right page (centered). Use this
1121
- * for portrait viewports or when a reader explicitly opts into single-page
1122
- * mode.
1203
+ * Render only one page of the spread (centered), chosen by
1204
+ * {@link MejiroSpreadProps.singleSide}. Use this for portrait viewports or
1205
+ * when a reader explicitly opts into single-page mode.
1123
1206
  */
1124
1207
  singlePage?: boolean;
1208
+ /**
1209
+ * Page of the spread shown when {@link MejiroSpreadProps.singlePage} is set
1210
+ * (`useSpread` reports it as `singleSide`). @defaultValue 'right'
1211
+ */
1212
+ singleSide?: 'right' | 'left';
1125
1213
  /**
1126
1214
  * Called when the user makes a quick swipe gesture on the spread.
1127
1215
  * `direction === 'next'` means the user swiped from right to left (the
@@ -1132,10 +1220,11 @@ interface MejiroSpreadProps {
1132
1220
  onSurfaceTap?: () => void;
1133
1221
  }
1134
1222
  /**
1135
- * Renders a two-page spread with the book frame, page chrome, navigation
1136
- * zones, and image overlays. Used inside `mejiro-reader-surface`.
1223
+ * Renders a two-page spread (or one page of it in single-page mode) with the
1224
+ * book frame, page chrome, navigation zones, and image overlays. Used inside
1225
+ * `mejiro-reader-surface`.
1137
1226
  */
1138
- declare function MejiroSpread({ spread, pageWidth, pageHeight, contentHeight, fontFamily, fontSize, lineSpacing, turning, rightHeader, leftHeader, images, slotMode, renderPageHeader, indicator, onPrev, onNext, onImagePointerDown, onImageResizePointerDown, onImageClose, anchorAtCoord, spreadIdx, selectionRects, onSelectionChange, singlePage, onSwipe, onSurfaceTap, }: MejiroSpreadProps): ReactNode;
1227
+ declare function MejiroSpread({ spread, pageWidth, pageHeight, contentHeight, fontFamily, fontSize, lineSpacing, turning, rightHeader, leftHeader, images, slotMode, renderPageHeader, indicator, onPrev, onNext, onImagePointerDown, onImageResizePointerDown, onImageClose, anchorAtCoord, spreadIdx, selectionRects, onSelectionChange, singlePage, singleSide, onSwipe, onSurfaceTap, }: MejiroSpreadProps): ReactNode;
1139
1228
 
1140
1229
  /** Props for {@link MejiroStats}. */
1141
1230
  interface MejiroStatsProps {
@@ -1165,9 +1254,9 @@ interface MejiroTocProps {
1165
1254
  currentAnchor?: ReadingAnchor | null;
1166
1255
  /** Show a search input that filters chapter titles. @defaultValue false */
1167
1256
  searchable?: boolean;
1168
- /** Heading displayed above the list. @defaultValue 'Contents' */
1257
+ /** Heading displayed above the list. Defaults to the active message catalog's `tocTitle`. */
1169
1258
  title?: string;
1170
- /** Placeholder shown in the search input. @defaultValue 'Search chapters…' */
1259
+ /** Placeholder shown in the search input. Defaults to the active message catalog's `tocSearchPlaceholder`. */
1171
1260
  searchPlaceholder?: string;
1172
1261
  /** Called when the user picks a chapter. */
1173
1262
  onSelect?: (chapter: number) => void;
@@ -1247,17 +1336,20 @@ interface UseChapterLayoutOptions {
1247
1336
  */
1248
1337
  pageGeometry?: () => ComputePageSizeOptions | undefined;
1249
1338
  /**
1250
- * Capture the current reading position from the outgoing layout, just before
1251
- * a **reflow** (non-blank) re-layout replaces it. The returned anchor is
1252
- * exposed via {@link UseChapterLayoutReturn.pendingRestore} and should be
1253
- * applied once the new layout commits. Return `null` to skip preservation.
1254
- * Never called for blank (content-change) re-layouts.
1255
- *
1256
- * A reflow yields a brand-new {@link ChapterLayout} object, which resets any
1257
- * downstream spread index to 0; capturing here and restoring after keeps the
1258
- * reader on the same passage across resizes and font / option changes.
1339
+ * Capture the reading position from the outgoing layout, just before a
1340
+ * **reflow** (non-blank) re-layout replaces it. The anchor is held in
1341
+ * `pendingRestore` until the new layout is in place, then handed to
1342
+ * `restorePosition` when one is given. Return `null` to skip preservation.
1343
+ * Never called for blank (content-change) re-layouts, which start at spread 0.
1259
1344
  */
1260
1345
  capturePosition?: (layout: ChapterLayout) => InChapterAnchor | null;
1346
+ /**
1347
+ * Apply an anchor captured by `capturePosition` to the new layout, e.g. by
1348
+ * locating it and jumping to its spread. Called once the new layout is in
1349
+ * place — after it commits, before paint — and `pendingRestore` is cleared
1350
+ * first. Without it, consume `pendingRestore` yourself.
1351
+ */
1352
+ restorePosition?: (layout: ChapterLayout, position: InChapterAnchor) => void;
1261
1353
  }
1262
1354
  /** Options for {@link UseChapterLayoutReturn.recompute}. */
1263
1355
  interface RecomputeOptions {
@@ -1271,8 +1363,11 @@ interface RecomputeOptions {
1271
1363
  }
1272
1364
  /** Page dimensions returned by {@link MejiroBook.computePageSize}. */
1273
1365
  interface PageDimensions {
1366
+ /** Width of one page, in CSS pixels. */
1274
1367
  pageWidth: number;
1368
+ /** Height of one page, in CSS pixels. */
1275
1369
  pageHeight: number;
1370
+ /** Height of the text area inside a page, in CSS pixels. */
1276
1371
  contentHeight: number;
1277
1372
  }
1278
1373
  /** Return value of {@link useChapterLayout}. */
@@ -1290,9 +1385,10 @@ interface UseChapterLayoutReturn {
1290
1385
  /** Force a fresh layout computation. */
1291
1386
  recompute: (opts?: RecomputeOptions) => Promise<void>;
1292
1387
  /**
1293
- * Anchor captured before the most recent reflow re-layout, awaiting
1294
- * restoration into the new {@link layout}. Consume it in a layout effect keyed
1295
- * on `layout` (after any index reset) and clear it back to `null`.
1388
+ * Anchor captured by `capturePosition` before the most recent reflow,
1389
+ * awaiting restoration into the new layout. `restorePosition`, when given,
1390
+ * consumes it; otherwise read it once the new layout is in place and set it
1391
+ * back to `null`.
1296
1392
  *
1297
1393
  * Declared as a `MutableRefObject` because consumers are expected to write to
1298
1394
  * it — `@types/react@18` models `RefObject.current` as read-only, so a
@@ -1311,13 +1407,16 @@ interface UseChapterLayoutReturn {
1311
1407
  * mounted before its container had a final box is still sized correctly on
1312
1408
  * first paint), and later size changes trigger a debounced **full re-layout**.
1313
1409
  *
1314
- * A full re-layout — rather than a `ChapterLayout.resize()` fast-path — is used
1315
- * for size changes on purpose: the fast-path only stretches `pageWidth` and does
1316
- * not re-paginate, leaving sparse, half-empty pages after a non-trivial size
1317
- * delta. Because a full re-layout yields a new {@link ChapterLayout} object
1318
- * (resetting any downstream spread index to 0), the reading position is
1319
- * preserved across reflows via {@link UseChapterLayoutOptions.capturePosition}
1320
- * and {@link UseChapterLayoutReturn.pendingRestore}.
1410
+ * Size and option changes lay the chapter out again rather than calling
1411
+ * `ChapterLayout.resize()`, so page geometry is re-derived from the surface and
1412
+ * the book's current options. The book replays the line-breaking hints it
1413
+ * already derived for the chapter, so a reflow never re-runs the analyzer.
1414
+ * Because each re-layout yields a new {@link ChapterLayout} object (resetting
1415
+ * any downstream spread index to 0), the reading position is carried across
1416
+ * reflows by {@link UseChapterLayoutOptions.capturePosition},
1417
+ * {@link UseChapterLayoutOptions.restorePosition} and
1418
+ * {@link UseChapterLayoutReturn.pendingRestore}, identically in the Vue
1419
+ * composable.
1321
1420
  *
1322
1421
  * @param book - The book instance to lay out with.
1323
1422
  * @param epub - The current parsed EPUB.
@@ -1327,6 +1426,7 @@ interface UseChapterLayoutReturn {
1327
1426
  */
1328
1427
  declare function useChapterLayout(book: MejiroBook, epub: EpubBook | null, chapterIndex: number, surface: RefObject<HTMLElement | null>, options?: UseChapterLayoutOptions): UseChapterLayoutReturn;
1329
1428
 
1429
+ /** Options for {@link useEditableEpub}. */
1330
1430
  interface UseEditableEpubOptions {
1331
1431
  /** URL fetched and loaded on mount. */
1332
1432
  defaultUrl?: string;
@@ -1342,32 +1442,58 @@ interface UseEditableEpubOptions {
1342
1442
  * fields keep their `DEFAULT_EPUB_PARSE_LIMITS` value.
1343
1443
  */
1344
1444
  limits?: Partial<EpubParseLimits>;
1445
+ /**
1446
+ * Resolves URL-only images into bytes when {@link UseEditableEpubReturn.exportEpub}
1447
+ * packages the book. An `assetResolver` passed to `exportEpub` itself wins.
1448
+ */
1449
+ assetResolver?: AssetResolver;
1345
1450
  }
1451
+ /** Return value of {@link useEditableEpub}. */
1346
1452
  interface UseEditableEpubReturn {
1453
+ /** Loaded editor, or `null` before any load. */
1347
1454
  editor: EditableEpub | null;
1455
+ /** The editor's live document, or `null` before any load. */
1348
1456
  book: EditableEpubBook | null;
1457
+ /** Snapshot of `book` re-cloned on every edit, for feeding a preview reader. */
1349
1458
  previewBook: EditableEpubBook | null;
1459
+ /** Whether a load is in progress. */
1350
1460
  loading: boolean;
1461
+ /** Whether an export is in progress. */
1351
1462
  exporting: boolean;
1463
+ /** Last load error, if any. */
1352
1464
  error: Error | null;
1465
+ /** Counter bumped on every load, edit, undo and redo. */
1353
1466
  revision: number;
1467
+ /** Undo / redo availability, or `null` before any load. */
1354
1468
  history: {
1355
1469
  canUndo: boolean;
1356
1470
  canRedo: boolean;
1357
1471
  depth: number;
1358
1472
  redoDepth: number;
1359
1473
  } | null;
1474
+ /** Paragraph targeted by the editing commands. */
1360
1475
  selection: EditableEpubSelection;
1476
+ /** The paragraph at `selection`, or `null` when there is none. */
1361
1477
  selectedParagraph: AnnotatedParagraph | null;
1478
+ /** Moves the selection, clamped to the loaded book. */
1362
1479
  setSelection: (selection: EditableEpubSelection) => void;
1480
+ /** Loads an EPUB from an in-memory buffer. */
1363
1481
  loadBuffer: (buffer: ArrayBuffer) => Promise<EditableEpub | null>;
1482
+ /** Loads an EPUB from a {@link File}. */
1364
1483
  loadFile: (file: File) => Promise<EditableEpub | null>;
1484
+ /** Fetches and loads an EPUB; a non-2xx response is reported as an error. */
1365
1485
  loadUrl: (url: string) => Promise<EditableEpub | null>;
1486
+ /** Replaces the selected paragraph's text and, optionally, its inline annotations. */
1366
1487
  updateParagraph: (text: string, inlineAnnotations?: readonly InlineAnnotation[]) => void;
1488
+ /** Replaces the selected paragraph's inline annotations. */
1367
1489
  setInlineAnnotations: (inlineAnnotations: readonly InlineAnnotation[]) => void;
1490
+ /** Inserts an image into the selected chapter. */
1368
1491
  addImage: (image: AddImageInput | EditableEpubImage) => void;
1492
+ /** Reverts the last edit. Returns `false` when there is nothing to undo. */
1369
1493
  undo: () => boolean;
1494
+ /** Re-applies the last undone edit. Returns `false` when there is nothing to redo. */
1370
1495
  redo: () => boolean;
1496
+ /** Packages the edited document as an EPUB buffer. */
1371
1497
  exportEpub: (options?: EpubExportOptions) => Promise<ArrayBuffer | null>;
1372
1498
  }
1373
1499
  /** Headless editable-EPUB state for custom proofreading/editor UIs. */
@@ -1375,11 +1501,11 @@ declare function useEditableEpub(options?: UseEditableEpubOptions): UseEditableE
1375
1501
 
1376
1502
  /** Options for {@link useEpub}. */
1377
1503
  interface UseEpubOptions {
1378
- /** URL fetched on mount. A non-OK response is treated as "no default". */
1504
+ /** URL fetched on mount. */
1379
1505
  defaultUrl?: string;
1380
1506
  /** Called after a successful load. */
1381
1507
  onLoad?: (book: EpubBook) => void;
1382
- /** Called when a load fails. Non-OK URL responses are still treated as "no default". */
1508
+ /** Called when a load fails, including a non-2xx URL response. */
1383
1509
  onError?: (error: Error) => void;
1384
1510
  /**
1385
1511
  * Extra options merged into the `fetch` call when loading by URL. Useful
@@ -1424,8 +1550,11 @@ declare function useEpub(options?: UseEpubOptions): UseEpubReturn;
1424
1550
 
1425
1551
  /** One chapter of the manuscript draft the hook keeps in React state. */
1426
1552
  interface EpubProjectChapterDraft {
1553
+ /** Stable chapter identifier. */
1427
1554
  id: string;
1555
+ /** Chapter title. */
1428
1556
  title: string;
1557
+ /** Chapter body in the manuscript notation. */
1429
1558
  body: string;
1430
1559
  }
1431
1560
  /** Options for {@link useEpubProject}. */
@@ -1465,19 +1594,29 @@ interface UseEpubProjectOptions {
1465
1594
  }
1466
1595
  /** State and actions returned by {@link useEpubProject}. */
1467
1596
  interface UseEpubProjectReturn {
1597
+ /** Current book metadata. */
1468
1598
  metadata: EpubProjectMetadata;
1599
+ /** Current chapter drafts, in reading order. */
1469
1600
  chapters: EpubProjectChapterDraft[];
1601
+ /** Index of the chapter being edited. */
1470
1602
  selectedChapter: number;
1603
+ /** The chapter at `selectedChapter`, or `null` when there is none. */
1471
1604
  currentChapter: EpubProjectChapterDraft | null;
1472
1605
  /** Current cover asset, or `null` when the project has no cover. */
1473
1606
  cover: EpubProjectAsset | null;
1474
1607
  /** Current non-cover assets, in registration order. */
1475
1608
  assets: EpubProjectAsset[];
1609
+ /** Parsed EPUB built from the current project, refreshed after each debounced change. */
1476
1610
  previewBook: EpubBook | null;
1611
+ /** Last preview build failure, if any. */
1477
1612
  previewError: Error | null;
1613
+ /** Whether a preview build is pending or running. */
1478
1614
  previewing: boolean;
1615
+ /** Merges `patch` into the metadata. */
1479
1616
  setMetadata: (patch: Partial<EpubProjectMetadata>) => void;
1617
+ /** Replaces every chapter; an empty list becomes one generated chapter. */
1480
1618
  setChapters: (chapters: EpubProjectChapterDraft[]) => void;
1619
+ /** Selects a chapter, clamped to the chapter range. */
1481
1620
  setSelectedChapter: (index: number) => void;
1482
1621
  /**
1483
1622
  * Replaces the cover asset, or drops it when passed `null`. The new cover is
@@ -1489,8 +1628,14 @@ interface UseEpubProjectReturn {
1489
1628
  * project, so URL-only entries reach `assetResolver` at export time.
1490
1629
  */
1491
1630
  setAssets: (assets: EpubProjectAsset[]) => void;
1631
+ /** Merges `patch` into the chapter at `index`. */
1492
1632
  patchChapter: (index: number, patch: Partial<EpubProjectChapterDraft>) => void;
1633
+ /** Appends a chapter (generated defaults fill omitted fields) and selects it. */
1493
1634
  addChapter: (chapter?: Partial<EpubProjectChapterDraft>) => void;
1635
+ /**
1636
+ * Removes the chapter at `index` (the selected one by default); the last chapter is never
1637
+ * removed.
1638
+ */
1494
1639
  removeChapter: (index?: number) => void;
1495
1640
  /**
1496
1641
  * Moves a chapter from `from` to `to`. An out-of-range `from` selects no
@@ -1498,7 +1643,9 @@ interface UseEpubProjectReturn {
1498
1643
  * the same contract as `EpubProject.reorderChapters()`.
1499
1644
  */
1500
1645
  reorderChapters: (from: number, to: number) => void;
1646
+ /** Builds an `EpubProject` from the current state. */
1501
1647
  buildProject: () => EpubProject;
1648
+ /** Packages the current project as an EPUB buffer. */
1502
1649
  exportEpub: () => Promise<ArrayBuffer>;
1503
1650
  }
1504
1651
  /** Headless manuscript-to-EPUB project state for custom authoring UIs. */
@@ -1526,14 +1673,24 @@ interface UseManuscriptDraftOptions<TAutosave = ManuscriptEditorChapter[]> {
1526
1673
  }
1527
1674
  /** Return value of {@link useManuscriptDraft}. */
1528
1675
  interface UseManuscriptDraftReturn {
1676
+ /** Current chapters, in reading order. */
1529
1677
  chapters: ManuscriptEditorChapter[];
1530
1678
  /** Index of the chapter currently being edited. */
1531
1679
  selected: number;
1680
+ /** Selects a chapter, clamped to the chapter range. */
1532
1681
  setSelected(index: number): void;
1682
+ /**
1683
+ * Replaces every chapter; an empty list becomes one generated chapter. The
1684
+ * selected chapter is kept by id when it survives.
1685
+ */
1533
1686
  setChapters(chapters: ManuscriptEditorChapter[]): void;
1687
+ /** Merges `patch` into the chapter at `index`. */
1534
1688
  patchChapter(index: number, patch: Partial<ManuscriptEditorChapter>): void;
1689
+ /** Appends a chapter (generated defaults fill omitted fields) and selects it. */
1535
1690
  addChapter(chapter?: Partial<ManuscriptEditorChapter>): void;
1691
+ /** Removes the chapter at `index`; the last remaining chapter is never removed. */
1536
1692
  removeChapter(index: number): void;
1693
+ /** Moves the chapter at `from` to `to`, keeping the same chapter selected. */
1537
1694
  reorderChapters(from: number, to: number): void;
1538
1695
  /** Last autosave failure, if any. */
1539
1696
  autosaveError: Error | null;
@@ -1558,13 +1715,20 @@ interface UseManuscriptLayoutOptions {
1558
1715
  /** Debounce window (ms) applied to size-triggered re-flows. @defaultValue 120 */
1559
1716
  resizeDebounce?: number;
1560
1717
  /**
1561
- * Capture the current reading position from the outgoing layout, just before
1562
- * a **reflow** (non-blank) re-layout replaces it. The returned anchor is
1563
- * exposed via {@link UseManuscriptLayoutReturn.pendingRestore} and should be
1564
- * applied once the new layout commits. Return `null` to skip preservation.
1565
- * Never called for blank (content-change) re-layouts.
1718
+ * Capture the reading position from the outgoing layout, just before a
1719
+ * **reflow** (non-blank) re-layout replaces it. The anchor is held in
1720
+ * `pendingRestore` until the new layout is in place, then handed to
1721
+ * `restorePosition` when one is given. Return `null` to skip preservation.
1722
+ * Never called for blank (content-change) re-layouts, which start at spread 0.
1566
1723
  */
1567
1724
  capturePosition?: (layout: ChapterLayout) => InChapterAnchor | null;
1725
+ /**
1726
+ * Apply an anchor captured by `capturePosition` to the new layout, e.g. by
1727
+ * locating it and jumping to its spread. Called once the new layout is in
1728
+ * place — after it commits, before paint — and `pendingRestore` is cleared
1729
+ * first. Without it, consume `pendingRestore` yourself.
1730
+ */
1731
+ restorePosition?: (layout: ChapterLayout, position: InChapterAnchor) => void;
1568
1732
  }
1569
1733
  /** Options for {@link UseManuscriptLayoutReturn.recompute}. */
1570
1734
  interface ManuscriptRecomputeOptions {
@@ -1578,8 +1742,11 @@ interface ManuscriptRecomputeOptions {
1578
1742
  }
1579
1743
  /** Page dimensions returned by {@link MejiroBook.computePageSize}. */
1580
1744
  interface ManuscriptPageDimensions {
1745
+ /** Width of one page, in CSS pixels. */
1581
1746
  pageWidth: number;
1747
+ /** Height of one page, in CSS pixels. */
1582
1748
  pageHeight: number;
1749
+ /** Height of the text area inside a page, in CSS pixels. */
1583
1750
  contentHeight: number;
1584
1751
  }
1585
1752
  /** Return value of {@link useManuscriptLayout}. */
@@ -1597,9 +1764,10 @@ interface UseManuscriptLayoutReturn {
1597
1764
  /** Force a fresh layout computation. */
1598
1765
  recompute: (opts?: ManuscriptRecomputeOptions) => Promise<void>;
1599
1766
  /**
1600
- * Anchor captured before the most recent reflow re-layout, awaiting
1601
- * restoration into the new {@link layout}. Consume it in a layout effect keyed
1602
- * on `layout` (after any index reset) and clear it back to `null`.
1767
+ * Anchor captured by `capturePosition` before the most recent reflow,
1768
+ * awaiting restoration into the new layout. `restorePosition`, when given,
1769
+ * consumes it; otherwise read it once the new layout is in place and set it
1770
+ * back to `null`.
1603
1771
  */
1604
1772
  pendingRestore: MutableRefObject<InChapterAnchor | null>;
1605
1773
  }
@@ -1619,8 +1787,10 @@ interface UseManuscriptLayoutReturn {
1619
1787
  * is computed (no blank flash) and yields a new {@link ChapterLayout} object,
1620
1788
  * which resets any downstream spread index to 0; the reading position is
1621
1789
  * preserved across such reflows via
1622
- * {@link UseManuscriptLayoutOptions.capturePosition} and
1623
- * {@link UseManuscriptLayoutReturn.pendingRestore}.
1790
+ * {@link UseManuscriptLayoutOptions.capturePosition},
1791
+ * {@link UseManuscriptLayoutOptions.restorePosition} and
1792
+ * {@link UseManuscriptLayoutReturn.pendingRestore}, identically in the Vue
1793
+ * composable.
1624
1794
  */
1625
1795
  declare function useManuscriptLayout(book: MejiroBook, chapter: ManuscriptChapter | null, surface: RefObject<HTMLElement | null>, options?: UseManuscriptLayoutOptions): UseManuscriptLayoutReturn;
1626
1796
 
@@ -1636,11 +1806,10 @@ interface UseMejiroBookOptions {
1636
1806
  */
1637
1807
  debounceMs?: number;
1638
1808
  /**
1639
- * Called when applying options to the book fails — typically a font that
1640
- * could not be loaded. When supplied, failures are reported here and the
1641
- * promise returned by {@link UseMejiroBookReturn.setOptions} resolves instead
1642
- * of rejecting, so fire-and-forget callers cannot leave an unhandled
1643
- * rejection behind.
1809
+ * Called when applying options to the book fails. When supplied, failures are
1810
+ * reported here and the promise returned by
1811
+ * {@link UseMejiroBookReturn.setOptions} resolves instead of rejecting, so
1812
+ * fire-and-forget callers cannot leave an unhandled rejection behind.
1644
1813
  */
1645
1814
  onError?: (error: Error) => void;
1646
1815
  }
@@ -1707,9 +1876,10 @@ interface UseReadingPositionReturn {
1707
1876
  * const { position, save } = useReadingPosition({ key: `mejiro:${bookId}` });
1708
1877
  * const reader = useRef<MejiroReaderHandle>(null);
1709
1878
  *
1879
+ * // Restore once per mount: depending on `position` would re-run after every save().
1710
1880
  * useEffect(() => {
1711
1881
  * if (position) reader.current?.goToAnchor(position);
1712
- * }, [position]);
1882
+ * }, []);
1713
1883
  *
1714
1884
  * useEffect(() => {
1715
1885
  * const off = reader.current?.subscribe('spreadChanged', () => {
@@ -1732,33 +1902,53 @@ interface UseSpreadOptions {
1732
1902
  enableKeyboard?: boolean;
1733
1903
  /** Page-turn animation duration in ms. @defaultValue 180 */
1734
1904
  turnDuration?: number;
1905
+ /**
1906
+ * Show one page at a time: the navigation index then counts pages instead
1907
+ * of two-page spreads. Toggling it converts the index so the page on
1908
+ * screen stays visible. @defaultValue false
1909
+ */
1910
+ single?: boolean;
1735
1911
  /** Called when the spread index changes (after the turn animation midpoint). */
1736
1912
  onChange?: (spreadIdx: number) => void;
1737
1913
  }
1738
1914
  /** Return value of {@link useSpread}. */
1739
1915
  interface UseSpreadReturn {
1740
- /** Current spread index. */
1916
+ /** Current navigation index: a page index in single mode, a spread index otherwise. */
1741
1917
  spreadIdx: number;
1742
- /** Current spread result, or `null` until the layout is ready. */
1918
+ /** Layout spread holding the visible page(s), or `null` until the layout is ready. */
1743
1919
  spread: SpreadResult | null;
1744
- /** Total number of pages. */
1920
+ /** First page shown, in reading order. */
1921
+ firstPage: number;
1922
+ /** Index of {@link UseSpreadReturn.spread} in the layout (`ChapterLayout.getSpread`). */
1923
+ layoutSpreadIdx: number;
1924
+ /** Side of {@link UseSpreadReturn.spread} shown in single mode; `null` in double mode. */
1925
+ singleSide: 'right' | 'left' | null;
1926
+ /** Total number of pages, as of {@link UseSpreadReturn.spread}. */
1745
1927
  totalPages: number;
1746
- /** Total number of two-page spreads. */
1928
+ /** Number of navigation positions: pages in single mode, two-page spreads otherwise. */
1747
1929
  totalSpreads: number;
1748
1930
  /** Whether the turn animation is currently in flight. */
1749
1931
  turning: boolean;
1750
- /** Advance one spread forward. */
1932
+ /** Advance one position (page or spread) forward. */
1751
1933
  next: () => void;
1752
- /** Go back one spread. */
1934
+ /** Go back one position. */
1753
1935
  prev: () => void;
1754
- /** Jump to an arbitrary spread index (clamped). */
1936
+ /** Jump to an arbitrary navigation index (clamped). */
1755
1937
  goTo: (index: number) => void;
1756
1938
  /**
1757
- * Set the spread index immediately, with no page-turn animation, clamped to
1758
- * `[0, totalSpreads − 1]`. Use to restore a reading position after a reflow
1939
+ * Set the navigation index immediately, with no page-turn animation, clamped
1940
+ * to `[0, totalSpreads − 1]`. Use to restore a reading position after a reflow
1759
1941
  * re-layout (where an animated {@link goTo} would briefly flash spread 0).
1760
1942
  */
1761
1943
  setSpread: (index: number) => void;
1944
+ /** Navigation index that shows the given page index. */
1945
+ indexOfPage: (pageIdx: number) => number;
1946
+ /**
1947
+ * Anchor at the start of the text shown at navigation index `index`. A page an
1948
+ * image blocks entirely holds no text, so the anchor then comes from the next
1949
+ * page that does, even past `index`; `null` when no page from there on holds text.
1950
+ */
1951
+ anchorAt: (index: number) => InChapterAnchor | null;
1762
1952
  /** Manually refresh `spread` from `layout` at the current index. */
1763
1953
  refresh: () => void;
1764
1954
  }