@libraz/mejiro-react 0.9.0 → 0.9.2

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/dist/index.d.ts CHANGED
@@ -4,7 +4,7 @@ import * as react from 'react';
4
4
  import { ReactNode, CSSProperties, PointerEvent, TextareaHTMLAttributes, MutableRefObject, RefObject } from 'react';
5
5
  import { EpubBook, AssetResolver, EpubParseLimits, EditableEpub, ManuscriptDialect, EpubChapter, EditableEpubBook, EditableEpubSelection, AnnotatedParagraph, AddImageInput, EditableEpubImage, EpubExportOptions, EpubProjectMetadata, EpubProjectAsset, EpubProject } from '@libraz/mejiro/epub';
6
6
  export { EditableEpubSelection } from '@libraz/mejiro/epub';
7
- import { ChapterLayout, SpreadResult, BookOptions, ComputePageSizeOptions, InChapterAnchor, ReadingAnchor, ManuscriptChapter, PageResult, AnchorRect, AnchorRange, MejiroBook } from '@libraz/mejiro/book';
7
+ import { ChapterLayout, SpreadResult, BookOptions, MejiroBookOptions, ComputePageSizeOptions, InChapterAnchor, ReadingAnchor, ManuscriptChapter, PageResult, AnchorRect, AnchorRange, MejiroBook } from '@libraz/mejiro/book';
8
8
  import { RenderPage } from '@libraz/mejiro/render';
9
9
  import { FontFamily, InlineAnnotation } from '@libraz/mejiro/browser';
10
10
 
@@ -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
  /**
@@ -342,29 +360,33 @@ interface MejiroReaderHandle {
342
360
  next(): void;
343
361
  /** Go back one spread. */
344
362
  prev(): void;
345
- /** Jump to a chapter (resets spread index to 0). */
363
+ /** Jump to a chapter (clamped to the book's chapters; resets spread index to 0). */
346
364
  goToChapter(index: number): void;
347
365
  /** Read the current reading position. */
348
366
  getReadingPosition(): ReadingPosition;
349
367
  /**
350
368
  * Navigate to a {@link ReadingAnchor}. If the chapter differs from the
351
369
  * current one, the chapter is switched first; once the new layout is ready
352
- * the anchor is resolved and the matching spread is opened.
370
+ * the anchor is resolved and the matching spread is opened. A call made
371
+ * before the book has loaded is applied once it has.
353
372
  *
354
- * Returns a promise that resolves once the spread has been applied. If
355
- * another `goToAnchor` is invoked before the previous one settles, the
356
- * earlier promise resolves immediately (superseded). Resolves on unmount.
373
+ * Returns a promise that resolves once the spread has been applied, or
374
+ * without moving when the anchor's chapter or position does not exist in the
375
+ * book. If another `goToAnchor` is invoked before the previous one settles,
376
+ * the earlier promise resolves immediately (superseded). Resolves on unmount.
357
377
  */
358
378
  goToAnchor(anchor: ReadingAnchor): Promise<void>;
359
379
  /**
360
- * Returns the {@link ReadingAnchor} at the start of the current spread,
361
- * or `null` if the layout is not ready.
380
+ * Returns the {@link ReadingAnchor} at the start of the text on the current
381
+ * spread, or `null` if the layout is not ready. When an image blocks every
382
+ * page of the spread, the anchor is where the text resumes after it; `null`
383
+ * again if none follows.
362
384
  */
363
385
  getAnchor(): ReadingAnchor | null;
364
386
  /**
365
387
  * 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).
388
+ * current spread. `end` points at the start of the text on the next spread
389
+ * that has any (or the end of the chapter when none does).
368
390
  */
369
391
  getVisibleRange(): {
370
392
  start: ReadingAnchor;
@@ -376,8 +398,9 @@ interface MejiroReaderHandle {
376
398
  *
377
399
  * Successive calls are coalesced into a single application; the returned
378
400
  * 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.
401
+ * (such as a font that measures as a fallback under `strictFontCheck`) is
402
+ * reported through {@link MejiroReaderProps.onError} rather than rejecting
403
+ * the promise.
381
404
  */
382
405
  setOptions(partial: Partial<BookOptions>): Promise<void>;
383
406
  /**
@@ -413,8 +436,10 @@ interface MejiroReaderCommonProps {
413
436
  * ```ts
414
437
  * { ...DEFAULT_BOOK_OPTIONS, fontFamily: '"Noto Serif JP"', fontSize: 18 }
415
438
  * ```
439
+ *
440
+ * `strictFontCheck` is read once, when the reader mounts.
416
441
  */
417
- options?: Partial<BookOptions>;
442
+ options?: Partial<MejiroBookOptions>;
418
443
  /**
419
444
  * Page-geometry overrides forwarded to `MejiroBook.computePageSize`. Use to
420
445
  * tune how the spread is sized inside the surface — most usefully to shrink
@@ -434,7 +459,9 @@ interface MejiroReaderCommonProps {
434
459
  /**
435
460
  * Controlled spread index. When supplied, the reader is driven by this
436
461
  * value and emits {@link MejiroReaderProps.onSpreadIdxChange} on user
437
- * navigation. Combine with `useReadingPosition` for save/restore.
462
+ * navigation. A spread index does not survive reflow, so persist the
463
+ * reading position with `useReadingPosition` and the handle's
464
+ * `getAnchor` / `goToAnchor` instead.
438
465
  */
439
466
  spreadIdx?: number;
440
467
  /**
@@ -451,8 +478,10 @@ interface MejiroReaderCommonProps {
451
478
  */
452
479
  mode?: MejiroReaderMode;
453
480
  /**
454
- * Spread layout. `double` (default) renders two pages; `single` renders
455
- * only the right page; `auto` flips to `single` for portrait viewports.
481
+ * Spread layout. `double` (default) renders two pages; `single` renders one
482
+ * page at a time, and `spreadIdx`, `goToSpread`, `onSpreadIdxChange` and
483
+ * `spreadChanged` then count pages; `auto` flips to `single` for portrait
484
+ * viewports, keeping the page on screen visible.
456
485
  */
457
486
  spreadMode?: MejiroSpreadMode;
458
487
  /**
@@ -489,7 +518,7 @@ interface MejiroReaderCommonProps {
489
518
  * component directly, so hosts that accept them should tighten the
490
519
  * defaults here.
491
520
  */
492
- limits?: EpubParseLimits;
521
+ limits?: Partial<EpubParseLimits>;
493
522
  /**
494
523
  * Custom EPUB fetcher used in place of the global `fetch`. Overrides
495
524
  * {@link fetchOptions} when set.
@@ -585,7 +614,8 @@ interface MejiroReaderCommonProps {
585
614
  onLoad?: (book: EpubBook) => void;
586
615
  /**
587
616
  * Called when loading or parsing an EPUB fails, and when applying an option
588
- * change fails (typically a font that could not be loaded).
617
+ * change fails (such as a font that measures as a fallback under
618
+ * `strictFontCheck`; without it, a font that fails to load is not an error).
589
619
  */
590
620
  onError?: (error: Error) => void;
591
621
  /** Called when the chapter index changes. */
@@ -655,18 +685,31 @@ interface MejiroReaderManuscriptProps extends MejiroReaderCommonProps {
655
685
  * modes — TypeScript prevents passing more than one source at once.
656
686
  */
657
687
  type MejiroReaderProps = MejiroReaderControlledProps | MejiroReaderUrlProps | MejiroReaderFileProps | MejiroReaderManuscriptProps;
688
+ /**
689
+ * Full-page EPUB reader. Composes the package's primitives into a working
690
+ * reader; features opt out via the `enableX` props or all chrome via `bare`.
691
+ * The `ref` exposes {@link MejiroReaderHandle} for imperative navigation.
692
+ */
658
693
  declare const MejiroReader: react.ForwardRefExoticComponent<MejiroReaderProps & react.RefAttributes<MejiroReaderHandle>>;
659
694
 
695
+ /** One chapter of the manuscript being edited. */
660
696
  interface ManuscriptEditorChapter {
697
+ /** Stable identifier; survives reordering and title edits. */
661
698
  id: string;
699
+ /** Chapter title; an empty title falls back to the localized "untitled" label. */
662
700
  title: string;
701
+ /** Chapter body in the editor's manuscript notation. */
663
702
  body: string;
664
703
  }
665
704
  /** Autosave payload emitted by {@link MejiroManuscriptEditor}. */
666
705
  interface ManuscriptAutosaveDraft {
706
+ /** Book title as currently entered. */
667
707
  title: string;
708
+ /** Author name as currently entered. */
668
709
  author: string;
710
+ /** Selected cover image, or `null` when none is set. */
669
711
  cover: File | null;
712
+ /** Chapters in reading order, as plain (non-reactive) copies. */
670
713
  chapters: ManuscriptEditorChapter[];
671
714
  }
672
715
  /**
@@ -676,14 +719,23 @@ interface ManuscriptAutosaveDraft {
676
719
  * supplied here.
677
720
  */
678
721
  interface ManuscriptPreviewProps {
722
+ /** Header subtitle of the preview reader. */
679
723
  subtitle?: string;
724
+ /** Header title of the preview reader. */
680
725
  title?: string;
726
+ /** Where the preview places its chapter navigation. @defaultValue 'panel' */
681
727
  chapterNavMode?: 'select' | 'panel' | 'both' | 'none';
728
+ /** Show the preview header. */
682
729
  enableHeader?: boolean;
730
+ /** Show the preview chapter navigation. */
683
731
  enableChapterNav?: boolean;
732
+ /** Show the preview settings panel toggle. */
684
733
  enableSettings?: boolean;
734
+ /** Show the preview layout statistics. */
685
735
  enableStats?: boolean;
736
+ /** Enable arrow-key page turns in the preview. */
686
737
  enableKeyboard?: boolean;
738
+ /** Show the preview page indicator. */
687
739
  enablePageIndicator?: boolean;
688
740
  /** Book options forwarded to the embedded reader preview. */
689
741
  options?: Partial<BookOptions>;
@@ -698,6 +750,7 @@ interface ManuscriptPreviewProps {
698
750
  * to `true` to demo the fullscreen-reader behavior.
699
751
  */
700
752
  enableSurfaceTap?: boolean;
753
+ /** Render the preview without any reader chrome. */
701
754
  bare?: boolean;
702
755
  }
703
756
  /** Props for {@link MejiroManuscriptEditor}. */
@@ -861,6 +914,60 @@ interface MejiroPageViewProps {
861
914
  */
862
915
  declare function MejiroPageView({ result, fontFamily, lineSpacing, slotMode, className, style, }: MejiroPageViewProps): ReactNode;
863
916
 
917
+ /** A single image overlay placed on a specific spread. */
918
+ interface MultiImageItem {
919
+ /** Stable ID for the overlay (auto-assigned on add). */
920
+ id: string;
921
+ /** Position and size in pixels, relative to the right page. */
922
+ rect: ImageRect;
923
+ }
924
+ /** Options for {@link useMultiImageOverlay}. */
925
+ interface UseMultiImageOverlayOptions {
926
+ /** Default image width. @defaultValue 120 */
927
+ defaultWidth?: number;
928
+ /** Default image height. @defaultValue 160 */
929
+ defaultHeight?: number;
930
+ /** Default x. @defaultValue 80 */
931
+ defaultX?: number;
932
+ /** Default y. @defaultValue 100 */
933
+ defaultY?: number;
934
+ /** Margin around each image, in pixels. Defaults to the book's font size. */
935
+ margin?: number;
936
+ /** Called after every reflow caused by adding/removing/moving an image. */
937
+ onUpdate?: (spread: SpreadResult) => void;
938
+ }
939
+ /** Return value of {@link useMultiImageOverlay}. */
940
+ interface UseMultiImageOverlayReturn {
941
+ /**
942
+ * Map of all images keyed by spread index. A new map whenever the images or
943
+ * the margin they are applied with change, and a replacement layout has them
944
+ * before any view renders from it, so this map and the layout together key
945
+ * anything derived from the layout.
946
+ */
947
+ imagesBySpread: Map<number, MultiImageItem[]>;
948
+ /** Images on the current spread; a new array whenever `imagesBySpread` changes. */
949
+ currentImages: MultiImageItem[];
950
+ /** Whether any spread has at least one image. */
951
+ hasImages: boolean;
952
+ /** Add a new image on the current spread. */
953
+ addImage: (overrides?: Partial<ImageRect>) => MultiImageItem;
954
+ /** Remove the image with the given id. */
955
+ removeImage: (id: string) => void;
956
+ /** Replace an image's rectangle. */
957
+ updateImage: (id: string, rect: Partial<ImageRect>) => void;
958
+ /** Remove every image (or only those on `spreadIdx` if given). */
959
+ clearImages: (spreadIdx?: number) => void;
960
+ /** Pointer-down handler for the overlay body (drag). */
961
+ onOverlayPointerDown: (id: string, e: PointerEvent) => void;
962
+ /** Pointer-down handler for the resize handle. */
963
+ onResizePointerDown: (id: string, e: PointerEvent) => void;
964
+ }
965
+ /**
966
+ * React hook that manages multiple draggable/resizable image overlays
967
+ * per spread with automatic reflow via {@link ChapterLayout.setImages}.
968
+ */
969
+ declare function useMultiImageOverlay(layout: ChapterLayout | null, spreadIdx: number, options?: UseMultiImageOverlayOptions): UseMultiImageOverlayReturn;
970
+
864
971
  /** Props for {@link MejiroScrollView}. */
865
972
  interface MejiroScrollViewProps {
866
973
  /** Layout containing the pages to render. */
@@ -891,6 +998,31 @@ interface MejiroScrollViewProps {
891
998
  scrollToPage?: number;
892
999
  /** Vertical gap between pages (px). @defaultValue 24 */
893
1000
  pageGap?: number;
1001
+ /**
1002
+ * Selection rectangles to highlight. Compute via
1003
+ * {@link ChapterLayout.selectionRects}; each entry is painted on the page its
1004
+ * `pageIdx` addresses. An entry may carry a `color` fill.
1005
+ */
1006
+ selectionRects?: readonly (AnchorRect & {
1007
+ color?: string;
1008
+ })[];
1009
+ /** Zero-based spread whose pages carry {@link MejiroScrollViewProps.images}. */
1010
+ spreadIdx?: number;
1011
+ /**
1012
+ * Image overlays on the spread at {@link MejiroScrollViewProps.spreadIdx}.
1013
+ * Each renders on the page whose area contains its centre (see
1014
+ * `overlayPageSide`), matching {@link MejiroSpread}'s single-page routing.
1015
+ * The layout reflows in place when its images change, so a new array here is
1016
+ * also what re-reads the pages; `useMultiImageOverlay`'s `currentImages`
1017
+ * provides one on every change.
1018
+ */
1019
+ images?: MultiImageItem[];
1020
+ /** Pointer-down on an image overlay. */
1021
+ onImagePointerDown?: (id: string, e: PointerEvent) => void;
1022
+ /** Pointer-down on an image resize handle. */
1023
+ onImageResizePointerDown?: (id: string, e: PointerEvent) => void;
1024
+ /** Image overlay close button. */
1025
+ onImageClose?: (id: string) => void;
894
1026
  }
895
1027
  /**
896
1028
  * Continuous-scroll variant of {@link MejiroSpread}. Stacks every page in
@@ -898,7 +1030,7 @@ interface MejiroScrollViewProps {
898
1030
  * detected via `IntersectionObserver` and reported through
899
1031
  * {@link MejiroScrollViewProps.onVisiblePageChange}.
900
1032
  */
901
- declare function MejiroScrollView({ layout, pageWidth, pageHeight, contentHeight, fontFamily, fontSize, lineSpacing, slotMode, onVisiblePageChange, scrollToPage, pageGap, }: MejiroScrollViewProps): ReactNode;
1033
+ declare function MejiroScrollView({ layout, pageWidth, pageHeight, contentHeight, fontFamily, fontSize, lineSpacing, slotMode, onVisiblePageChange, scrollToPage, pageGap, selectionRects, spreadIdx, images, onImagePointerDown, onImageResizePointerDown, onImageClose, }: MejiroScrollViewProps): ReactNode;
902
1034
 
903
1035
  /** Props for {@link MejiroSelectionLayer}. */
904
1036
  interface MejiroSelectionLayerProps {
@@ -986,7 +1118,7 @@ interface MejiroShelfProps<T = unknown> {
986
1118
  currentId?: string;
987
1119
  /** Called when the user picks a volume. */
988
1120
  onSelect?: (volume: VolumeInfo<T>) => void;
989
- /** Heading shown above the grid. @defaultValue 'Library' */
1121
+ /** Heading shown above the grid. Defaults to the active message catalog's `shelfTitle`. */
990
1122
  title?: string;
991
1123
  }
992
1124
  /**
@@ -995,55 +1127,6 @@ interface MejiroShelfProps<T = unknown> {
995
1127
  */
996
1128
  declare function MejiroShelf<T = unknown>({ volumes, currentId, onSelect, title, }: MejiroShelfProps<T>): ReactNode;
997
1129
 
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
1130
  /** Props for {@link MejiroSpread}. */
1048
1131
  interface MejiroSpreadProps {
1049
1132
  /** Spread data to render. */
@@ -1066,7 +1149,11 @@ interface MejiroSpreadProps {
1066
1149
  rightHeader?: PageHeaderData;
1067
1150
  /** Header data for the left page. */
1068
1151
  leftHeader?: PageHeaderData;
1069
- /** Image overlays on the current spread. */
1152
+ /**
1153
+ * Image overlays on the current spread, relative to the right page. With
1154
+ * {@link MejiroSpreadProps.singlePage}, only those whose centre lies on the
1155
+ * page shown are drawn.
1156
+ */
1070
1157
  images?: MultiImageItem[];
1071
1158
  /**
1072
1159
  * Force slot-mode rendering on both pages. When omitted, each page falls
@@ -1117,11 +1204,16 @@ interface MejiroSpreadProps {
1117
1204
  */
1118
1205
  onSelectionChange?: (range: AnchorRange | null) => void;
1119
1206
  /**
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.
1207
+ * Render only one page of the spread (centered), chosen by
1208
+ * {@link MejiroSpreadProps.singleSide}. Use this for portrait viewports or
1209
+ * when a reader explicitly opts into single-page mode.
1123
1210
  */
1124
1211
  singlePage?: boolean;
1212
+ /**
1213
+ * Page of the spread shown when {@link MejiroSpreadProps.singlePage} is set
1214
+ * (`useSpread` reports it as `singleSide`). @defaultValue 'right'
1215
+ */
1216
+ singleSide?: 'right' | 'left';
1125
1217
  /**
1126
1218
  * Called when the user makes a quick swipe gesture on the spread.
1127
1219
  * `direction === 'next'` means the user swiped from right to left (the
@@ -1132,10 +1224,11 @@ interface MejiroSpreadProps {
1132
1224
  onSurfaceTap?: () => void;
1133
1225
  }
1134
1226
  /**
1135
- * Renders a two-page spread with the book frame, page chrome, navigation
1136
- * zones, and image overlays. Used inside `mejiro-reader-surface`.
1227
+ * Renders a two-page spread (or one page of it in single-page mode) with the
1228
+ * book frame, page chrome, navigation zones, and image overlays. Used inside
1229
+ * `mejiro-reader-surface`.
1137
1230
  */
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;
1231
+ 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
1232
 
1140
1233
  /** Props for {@link MejiroStats}. */
1141
1234
  interface MejiroStatsProps {
@@ -1165,9 +1258,9 @@ interface MejiroTocProps {
1165
1258
  currentAnchor?: ReadingAnchor | null;
1166
1259
  /** Show a search input that filters chapter titles. @defaultValue false */
1167
1260
  searchable?: boolean;
1168
- /** Heading displayed above the list. @defaultValue 'Contents' */
1261
+ /** Heading displayed above the list. Defaults to the active message catalog's `tocTitle`. */
1169
1262
  title?: string;
1170
- /** Placeholder shown in the search input. @defaultValue 'Search chapters…' */
1263
+ /** Placeholder shown in the search input. Defaults to the active message catalog's `tocSearchPlaceholder`. */
1171
1264
  searchPlaceholder?: string;
1172
1265
  /** Called when the user picks a chapter. */
1173
1266
  onSelect?: (chapter: number) => void;
@@ -1247,17 +1340,26 @@ interface UseChapterLayoutOptions {
1247
1340
  */
1248
1341
  pageGeometry?: () => ComputePageSizeOptions | undefined;
1249
1342
  /**
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.
1343
+ * Capture the reading position from the outgoing layout, just before a
1344
+ * **reflow** (non-blank) re-layout replaces it. The anchor is held in
1345
+ * `pendingRestore` until the new layout is in place, then handed to
1346
+ * `restorePosition` when one is given. Return `null` to skip preservation.
1347
+ * Never called for blank (content-change) re-layouts, which start at spread 0.
1259
1348
  */
1260
1349
  capturePosition?: (layout: ChapterLayout) => InChapterAnchor | null;
1350
+ /**
1351
+ * Apply an anchor captured by `capturePosition` to the new layout, e.g. by
1352
+ * locating it and jumping to its spread. Called once the new layout is in
1353
+ * place — after it commits, before paint — and `pendingRestore` is cleared
1354
+ * first. Without it, consume `pendingRestore` yourself.
1355
+ */
1356
+ restorePosition?: (layout: ChapterLayout, position: InChapterAnchor) => void;
1357
+ /**
1358
+ * Called once when a layout of the current source fails, whichever path
1359
+ * started it (source change, resize or `recompute`); a superseded layout
1360
+ * reports nothing. When supplied, `recompute` resolves instead of rejecting.
1361
+ */
1362
+ onError?: (error: Error) => void;
1261
1363
  }
1262
1364
  /** Options for {@link UseChapterLayoutReturn.recompute}. */
1263
1365
  interface RecomputeOptions {
@@ -1271,8 +1373,11 @@ interface RecomputeOptions {
1271
1373
  }
1272
1374
  /** Page dimensions returned by {@link MejiroBook.computePageSize}. */
1273
1375
  interface PageDimensions {
1376
+ /** Width of one page, in CSS pixels. */
1274
1377
  pageWidth: number;
1378
+ /** Height of one page, in CSS pixels. */
1275
1379
  pageHeight: number;
1380
+ /** Height of the text area inside a page, in CSS pixels. */
1276
1381
  contentHeight: number;
1277
1382
  }
1278
1383
  /** Return value of {@link useChapterLayout}. */
@@ -1287,12 +1392,16 @@ interface UseChapterLayoutReturn {
1287
1392
  contentHeight: number;
1288
1393
  /** Most recent layout time (ms). */
1289
1394
  elapsedMs: number;
1290
- /** Force a fresh layout computation. */
1395
+ /**
1396
+ * Force a fresh layout computation. A failure goes to `onError` when one is
1397
+ * supplied; otherwise the returned promise rejects.
1398
+ */
1291
1399
  recompute: (opts?: RecomputeOptions) => Promise<void>;
1292
1400
  /**
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`.
1401
+ * Anchor captured by `capturePosition` before the most recent reflow,
1402
+ * awaiting restoration into the new layout. `restorePosition`, when given,
1403
+ * consumes it; otherwise read it once the new layout is in place and set it
1404
+ * back to `null`.
1296
1405
  *
1297
1406
  * Declared as a `MutableRefObject` because consumers are expected to write to
1298
1407
  * it — `@types/react@18` models `RefObject.current` as read-only, so a
@@ -1311,13 +1420,16 @@ interface UseChapterLayoutReturn {
1311
1420
  * mounted before its container had a final box is still sized correctly on
1312
1421
  * first paint), and later size changes trigger a debounced **full re-layout**.
1313
1422
  *
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}.
1423
+ * Size and option changes lay the chapter out again rather than calling
1424
+ * `ChapterLayout.resize()`, so page geometry is re-derived from the surface and
1425
+ * the book's current options. The book replays the line-breaking hints it
1426
+ * already derived for the chapter, so a reflow never re-runs the analyzer.
1427
+ * Because each re-layout yields a new {@link ChapterLayout} object (resetting
1428
+ * any downstream spread index to 0), the reading position is carried across
1429
+ * reflows by {@link UseChapterLayoutOptions.capturePosition},
1430
+ * {@link UseChapterLayoutOptions.restorePosition} and
1431
+ * {@link UseChapterLayoutReturn.pendingRestore}, identically in the Vue
1432
+ * composable.
1321
1433
  *
1322
1434
  * @param book - The book instance to lay out with.
1323
1435
  * @param epub - The current parsed EPUB.
@@ -1327,6 +1439,7 @@ interface UseChapterLayoutReturn {
1327
1439
  */
1328
1440
  declare function useChapterLayout(book: MejiroBook, epub: EpubBook | null, chapterIndex: number, surface: RefObject<HTMLElement | null>, options?: UseChapterLayoutOptions): UseChapterLayoutReturn;
1329
1441
 
1442
+ /** Options for {@link useEditableEpub}. */
1330
1443
  interface UseEditableEpubOptions {
1331
1444
  /** URL fetched and loaded on mount. */
1332
1445
  defaultUrl?: string;
@@ -1342,32 +1455,58 @@ interface UseEditableEpubOptions {
1342
1455
  * fields keep their `DEFAULT_EPUB_PARSE_LIMITS` value.
1343
1456
  */
1344
1457
  limits?: Partial<EpubParseLimits>;
1458
+ /**
1459
+ * Resolves URL-only images into bytes when {@link UseEditableEpubReturn.exportEpub}
1460
+ * packages the book. An `assetResolver` passed to `exportEpub` itself wins.
1461
+ */
1462
+ assetResolver?: AssetResolver;
1345
1463
  }
1464
+ /** Return value of {@link useEditableEpub}. */
1346
1465
  interface UseEditableEpubReturn {
1466
+ /** Loaded editor, or `null` before any load. */
1347
1467
  editor: EditableEpub | null;
1468
+ /** The editor's live document, or `null` before any load. */
1348
1469
  book: EditableEpubBook | null;
1470
+ /** Snapshot of `book` re-cloned on every edit, for feeding a preview reader. */
1349
1471
  previewBook: EditableEpubBook | null;
1472
+ /** Whether a load is in progress. */
1350
1473
  loading: boolean;
1474
+ /** Whether an export is in progress. */
1351
1475
  exporting: boolean;
1476
+ /** Last load error, if any. */
1352
1477
  error: Error | null;
1478
+ /** Counter bumped on every load, edit, undo and redo. */
1353
1479
  revision: number;
1480
+ /** Undo / redo availability, or `null` before any load. */
1354
1481
  history: {
1355
1482
  canUndo: boolean;
1356
1483
  canRedo: boolean;
1357
1484
  depth: number;
1358
1485
  redoDepth: number;
1359
1486
  } | null;
1487
+ /** Paragraph targeted by the editing commands. */
1360
1488
  selection: EditableEpubSelection;
1489
+ /** The paragraph at `selection`, or `null` when there is none. */
1361
1490
  selectedParagraph: AnnotatedParagraph | null;
1491
+ /** Moves the selection, clamped to the loaded book. */
1362
1492
  setSelection: (selection: EditableEpubSelection) => void;
1493
+ /** Loads an EPUB from an in-memory buffer. */
1363
1494
  loadBuffer: (buffer: ArrayBuffer) => Promise<EditableEpub | null>;
1495
+ /** Loads an EPUB from a {@link File}. */
1364
1496
  loadFile: (file: File) => Promise<EditableEpub | null>;
1497
+ /** Fetches and loads an EPUB; a non-2xx response is reported as an error. */
1365
1498
  loadUrl: (url: string) => Promise<EditableEpub | null>;
1499
+ /** Replaces the selected paragraph's text and, optionally, its inline annotations. */
1366
1500
  updateParagraph: (text: string, inlineAnnotations?: readonly InlineAnnotation[]) => void;
1501
+ /** Replaces the selected paragraph's inline annotations. */
1367
1502
  setInlineAnnotations: (inlineAnnotations: readonly InlineAnnotation[]) => void;
1503
+ /** Inserts an image into the selected chapter. */
1368
1504
  addImage: (image: AddImageInput | EditableEpubImage) => void;
1505
+ /** Reverts the last edit. Returns `false` when there is nothing to undo. */
1369
1506
  undo: () => boolean;
1507
+ /** Re-applies the last undone edit. Returns `false` when there is nothing to redo. */
1370
1508
  redo: () => boolean;
1509
+ /** Packages the edited document as an EPUB buffer. */
1371
1510
  exportEpub: (options?: EpubExportOptions) => Promise<ArrayBuffer | null>;
1372
1511
  }
1373
1512
  /** Headless editable-EPUB state for custom proofreading/editor UIs. */
@@ -1375,11 +1514,11 @@ declare function useEditableEpub(options?: UseEditableEpubOptions): UseEditableE
1375
1514
 
1376
1515
  /** Options for {@link useEpub}. */
1377
1516
  interface UseEpubOptions {
1378
- /** URL fetched on mount. A non-OK response is treated as "no default". */
1517
+ /** URL fetched on mount. */
1379
1518
  defaultUrl?: string;
1380
1519
  /** Called after a successful load. */
1381
1520
  onLoad?: (book: EpubBook) => void;
1382
- /** Called when a load fails. Non-OK URL responses are still treated as "no default". */
1521
+ /** Called when a load fails, including a non-2xx URL response. */
1383
1522
  onError?: (error: Error) => void;
1384
1523
  /**
1385
1524
  * Extra options merged into the `fetch` call when loading by URL. Useful
@@ -1424,8 +1563,11 @@ declare function useEpub(options?: UseEpubOptions): UseEpubReturn;
1424
1563
 
1425
1564
  /** One chapter of the manuscript draft the hook keeps in React state. */
1426
1565
  interface EpubProjectChapterDraft {
1566
+ /** Stable chapter identifier. */
1427
1567
  id: string;
1568
+ /** Chapter title. */
1428
1569
  title: string;
1570
+ /** Chapter body in the manuscript notation. */
1429
1571
  body: string;
1430
1572
  }
1431
1573
  /** Options for {@link useEpubProject}. */
@@ -1465,19 +1607,32 @@ interface UseEpubProjectOptions {
1465
1607
  }
1466
1608
  /** State and actions returned by {@link useEpubProject}. */
1467
1609
  interface UseEpubProjectReturn {
1610
+ /** Current book metadata. */
1468
1611
  metadata: EpubProjectMetadata;
1612
+ /** Current chapter drafts, in reading order. */
1469
1613
  chapters: EpubProjectChapterDraft[];
1614
+ /** Index of the chapter being edited. */
1470
1615
  selectedChapter: number;
1616
+ /** The chapter at `selectedChapter`, or `null` when there is none. */
1471
1617
  currentChapter: EpubProjectChapterDraft | null;
1472
1618
  /** Current cover asset, or `null` when the project has no cover. */
1473
1619
  cover: EpubProjectAsset | null;
1474
1620
  /** Current non-cover assets, in registration order. */
1475
1621
  assets: EpubProjectAsset[];
1622
+ /** Parsed EPUB built from the current project, refreshed after each debounced change. */
1476
1623
  previewBook: EpubBook | null;
1624
+ /** Last preview build failure, if any. */
1477
1625
  previewError: Error | null;
1626
+ /** Whether a preview build is pending or running. */
1478
1627
  previewing: boolean;
1628
+ /**
1629
+ * Merges `patch` into the metadata. A missing or blank `identifier` keeps the
1630
+ * current one.
1631
+ */
1479
1632
  setMetadata: (patch: Partial<EpubProjectMetadata>) => void;
1633
+ /** Replaces every chapter; an empty list becomes one generated chapter. */
1480
1634
  setChapters: (chapters: EpubProjectChapterDraft[]) => void;
1635
+ /** Selects a chapter, clamped to the chapter range. */
1481
1636
  setSelectedChapter: (index: number) => void;
1482
1637
  /**
1483
1638
  * Replaces the cover asset, or drops it when passed `null`. The new cover is
@@ -1489,8 +1644,14 @@ interface UseEpubProjectReturn {
1489
1644
  * project, so URL-only entries reach `assetResolver` at export time.
1490
1645
  */
1491
1646
  setAssets: (assets: EpubProjectAsset[]) => void;
1647
+ /** Merges `patch` into the chapter at `index`. */
1492
1648
  patchChapter: (index: number, patch: Partial<EpubProjectChapterDraft>) => void;
1649
+ /** Appends a chapter (generated defaults fill omitted fields) and selects it. */
1493
1650
  addChapter: (chapter?: Partial<EpubProjectChapterDraft>) => void;
1651
+ /**
1652
+ * Removes the chapter at `index` (the selected one by default); the last chapter is never
1653
+ * removed.
1654
+ */
1494
1655
  removeChapter: (index?: number) => void;
1495
1656
  /**
1496
1657
  * Moves a chapter from `from` to `to`. An out-of-range `from` selects no
@@ -1498,7 +1659,9 @@ interface UseEpubProjectReturn {
1498
1659
  * the same contract as `EpubProject.reorderChapters()`.
1499
1660
  */
1500
1661
  reorderChapters: (from: number, to: number) => void;
1662
+ /** Builds an `EpubProject` from the current state. */
1501
1663
  buildProject: () => EpubProject;
1664
+ /** Packages the current project as an EPUB buffer. */
1502
1665
  exportEpub: () => Promise<ArrayBuffer>;
1503
1666
  }
1504
1667
  /** Headless manuscript-to-EPUB project state for custom authoring UIs. */
@@ -1526,14 +1689,24 @@ interface UseManuscriptDraftOptions<TAutosave = ManuscriptEditorChapter[]> {
1526
1689
  }
1527
1690
  /** Return value of {@link useManuscriptDraft}. */
1528
1691
  interface UseManuscriptDraftReturn {
1692
+ /** Current chapters, in reading order. */
1529
1693
  chapters: ManuscriptEditorChapter[];
1530
1694
  /** Index of the chapter currently being edited. */
1531
1695
  selected: number;
1696
+ /** Selects a chapter, clamped to the chapter range. */
1532
1697
  setSelected(index: number): void;
1698
+ /**
1699
+ * Replaces every chapter; an empty list becomes one generated chapter. The
1700
+ * selected chapter is kept by id when it survives.
1701
+ */
1533
1702
  setChapters(chapters: ManuscriptEditorChapter[]): void;
1703
+ /** Merges `patch` into the chapter at `index`. */
1534
1704
  patchChapter(index: number, patch: Partial<ManuscriptEditorChapter>): void;
1705
+ /** Appends a chapter (generated defaults fill omitted fields) and selects it. */
1535
1706
  addChapter(chapter?: Partial<ManuscriptEditorChapter>): void;
1707
+ /** Removes the chapter at `index`; the last remaining chapter is never removed. */
1536
1708
  removeChapter(index: number): void;
1709
+ /** Moves the chapter at `from` to `to`, keeping the same chapter selected. */
1537
1710
  reorderChapters(from: number, to: number): void;
1538
1711
  /** Last autosave failure, if any. */
1539
1712
  autosaveError: Error | null;
@@ -1558,13 +1731,26 @@ interface UseManuscriptLayoutOptions {
1558
1731
  /** Debounce window (ms) applied to size-triggered re-flows. @defaultValue 120 */
1559
1732
  resizeDebounce?: number;
1560
1733
  /**
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.
1734
+ * Capture the reading position from the outgoing layout, just before a
1735
+ * **reflow** (non-blank) re-layout replaces it. The anchor is held in
1736
+ * `pendingRestore` until the new layout is in place, then handed to
1737
+ * `restorePosition` when one is given. Return `null` to skip preservation.
1738
+ * Never called for blank (content-change) re-layouts, which start at spread 0.
1566
1739
  */
1567
1740
  capturePosition?: (layout: ChapterLayout) => InChapterAnchor | null;
1741
+ /**
1742
+ * Apply an anchor captured by `capturePosition` to the new layout, e.g. by
1743
+ * locating it and jumping to its spread. Called once the new layout is in
1744
+ * place — after it commits, before paint — and `pendingRestore` is cleared
1745
+ * first. Without it, consume `pendingRestore` yourself.
1746
+ */
1747
+ restorePosition?: (layout: ChapterLayout, position: InChapterAnchor) => void;
1748
+ /**
1749
+ * Called once when a layout of the current source fails, whichever path
1750
+ * started it (source change, resize or `recompute`); a superseded layout
1751
+ * reports nothing. When supplied, `recompute` resolves instead of rejecting.
1752
+ */
1753
+ onError?: (error: Error) => void;
1568
1754
  }
1569
1755
  /** Options for {@link UseManuscriptLayoutReturn.recompute}. */
1570
1756
  interface ManuscriptRecomputeOptions {
@@ -1578,8 +1764,11 @@ interface ManuscriptRecomputeOptions {
1578
1764
  }
1579
1765
  /** Page dimensions returned by {@link MejiroBook.computePageSize}. */
1580
1766
  interface ManuscriptPageDimensions {
1767
+ /** Width of one page, in CSS pixels. */
1581
1768
  pageWidth: number;
1769
+ /** Height of one page, in CSS pixels. */
1582
1770
  pageHeight: number;
1771
+ /** Height of the text area inside a page, in CSS pixels. */
1583
1772
  contentHeight: number;
1584
1773
  }
1585
1774
  /** Return value of {@link useManuscriptLayout}. */
@@ -1594,12 +1783,16 @@ interface UseManuscriptLayoutReturn {
1594
1783
  contentHeight: number;
1595
1784
  /** Most recent layout time (ms). */
1596
1785
  elapsedMs: number;
1597
- /** Force a fresh layout computation. */
1786
+ /**
1787
+ * Force a fresh layout computation. A failure goes to `onError` when one is
1788
+ * supplied; otherwise the returned promise rejects.
1789
+ */
1598
1790
  recompute: (opts?: ManuscriptRecomputeOptions) => Promise<void>;
1599
1791
  /**
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`.
1792
+ * Anchor captured by `capturePosition` before the most recent reflow,
1793
+ * awaiting restoration into the new layout. `restorePosition`, when given,
1794
+ * consumes it; otherwise read it once the new layout is in place and set it
1795
+ * back to `null`.
1603
1796
  */
1604
1797
  pendingRestore: MutableRefObject<InChapterAnchor | null>;
1605
1798
  }
@@ -1619,8 +1812,10 @@ interface UseManuscriptLayoutReturn {
1619
1812
  * is computed (no blank flash) and yields a new {@link ChapterLayout} object,
1620
1813
  * which resets any downstream spread index to 0; the reading position is
1621
1814
  * preserved across such reflows via
1622
- * {@link UseManuscriptLayoutOptions.capturePosition} and
1623
- * {@link UseManuscriptLayoutReturn.pendingRestore}.
1815
+ * {@link UseManuscriptLayoutOptions.capturePosition},
1816
+ * {@link UseManuscriptLayoutOptions.restorePosition} and
1817
+ * {@link UseManuscriptLayoutReturn.pendingRestore}, identically in the Vue
1818
+ * composable.
1624
1819
  */
1625
1820
  declare function useManuscriptLayout(book: MejiroBook, chapter: ManuscriptChapter | null, surface: RefObject<HTMLElement | null>, options?: UseManuscriptLayoutOptions): UseManuscriptLayoutReturn;
1626
1821
 
@@ -1636,11 +1831,10 @@ interface UseMejiroBookOptions {
1636
1831
  */
1637
1832
  debounceMs?: number;
1638
1833
  /**
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.
1834
+ * Called when applying options to the book fails. When supplied, failures are
1835
+ * reported here and the promise returned by
1836
+ * {@link UseMejiroBookReturn.setOptions} resolves instead of rejecting, so
1837
+ * fire-and-forget callers cannot leave an unhandled rejection behind.
1644
1838
  */
1645
1839
  onError?: (error: Error) => void;
1646
1840
  }
@@ -1648,7 +1842,10 @@ interface UseMejiroBookOptions {
1648
1842
  interface UseMejiroBookReturn {
1649
1843
  /** The managed {@link MejiroBook} instance. Stable across renders. */
1650
1844
  book: MejiroBook;
1651
- /** Reactive snapshot of the current options. */
1845
+ /**
1846
+ * Reactive snapshot of the current options. Once the latest application
1847
+ * settles it holds what the book applied, so a rejected change rolls back.
1848
+ */
1652
1849
  options: Readonly<BookOptions>;
1653
1850
  /**
1654
1851
  * Update options on the underlying book and the snapshot. The snapshot is
@@ -1667,7 +1864,7 @@ interface UseMejiroBookReturn {
1667
1864
  * @param initial - Initial book options (passed to the {@link MejiroBook} constructor).
1668
1865
  * @param options - Behavior overrides (debouncing, error reporting).
1669
1866
  */
1670
- declare function useMejiroBook(initial: BookOptions, options?: UseMejiroBookOptions): UseMejiroBookReturn;
1867
+ declare function useMejiroBook(initial: MejiroBookOptions, options?: UseMejiroBookOptions): UseMejiroBookReturn;
1671
1868
 
1672
1869
  /** Minimal storage interface required by {@link useReadingPosition}. */
1673
1870
  type ReadingPositionStorage = MejiroStorage;
@@ -1707,9 +1904,10 @@ interface UseReadingPositionReturn {
1707
1904
  * const { position, save } = useReadingPosition({ key: `mejiro:${bookId}` });
1708
1905
  * const reader = useRef<MejiroReaderHandle>(null);
1709
1906
  *
1907
+ * // Restore once per mount: depending on `position` would re-run after every save().
1710
1908
  * useEffect(() => {
1711
1909
  * if (position) reader.current?.goToAnchor(position);
1712
- * }, [position]);
1910
+ * }, []);
1713
1911
  *
1714
1912
  * useEffect(() => {
1715
1913
  * const off = reader.current?.subscribe('spreadChanged', () => {
@@ -1732,33 +1930,53 @@ interface UseSpreadOptions {
1732
1930
  enableKeyboard?: boolean;
1733
1931
  /** Page-turn animation duration in ms. @defaultValue 180 */
1734
1932
  turnDuration?: number;
1933
+ /**
1934
+ * Show one page at a time: the navigation index then counts pages instead
1935
+ * of two-page spreads. Toggling it converts the index so the page on
1936
+ * screen stays visible. @defaultValue false
1937
+ */
1938
+ single?: boolean;
1735
1939
  /** Called when the spread index changes (after the turn animation midpoint). */
1736
1940
  onChange?: (spreadIdx: number) => void;
1737
1941
  }
1738
1942
  /** Return value of {@link useSpread}. */
1739
1943
  interface UseSpreadReturn {
1740
- /** Current spread index. */
1944
+ /** Current navigation index: a page index in single mode, a spread index otherwise. */
1741
1945
  spreadIdx: number;
1742
- /** Current spread result, or `null` until the layout is ready. */
1946
+ /** Layout spread holding the visible page(s), or `null` until the layout is ready. */
1743
1947
  spread: SpreadResult | null;
1744
- /** Total number of pages. */
1948
+ /** First page shown, in reading order. */
1949
+ firstPage: number;
1950
+ /** Index of {@link UseSpreadReturn.spread} in the layout (`ChapterLayout.getSpread`). */
1951
+ layoutSpreadIdx: number;
1952
+ /** Side of {@link UseSpreadReturn.spread} shown in single mode; `null` in double mode. */
1953
+ singleSide: 'right' | 'left' | null;
1954
+ /** Total number of pages, as of {@link UseSpreadReturn.spread}. */
1745
1955
  totalPages: number;
1746
- /** Total number of two-page spreads. */
1956
+ /** Number of navigation positions: pages in single mode, two-page spreads otherwise. */
1747
1957
  totalSpreads: number;
1748
1958
  /** Whether the turn animation is currently in flight. */
1749
1959
  turning: boolean;
1750
- /** Advance one spread forward. */
1960
+ /** Advance one position (page or spread) forward. */
1751
1961
  next: () => void;
1752
- /** Go back one spread. */
1962
+ /** Go back one position. */
1753
1963
  prev: () => void;
1754
- /** Jump to an arbitrary spread index (clamped). */
1964
+ /** Jump to an arbitrary navigation index (clamped). */
1755
1965
  goTo: (index: number) => void;
1756
1966
  /**
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
1967
+ * Set the navigation index immediately, with no page-turn animation, clamped
1968
+ * to `[0, totalSpreads − 1]`. Use to restore a reading position after a reflow
1759
1969
  * re-layout (where an animated {@link goTo} would briefly flash spread 0).
1760
1970
  */
1761
1971
  setSpread: (index: number) => void;
1972
+ /** Navigation index that shows the given page index. */
1973
+ indexOfPage: (pageIdx: number) => number;
1974
+ /**
1975
+ * Anchor at the start of the text shown at navigation index `index`. A page an
1976
+ * image blocks entirely holds no text, so the anchor then comes from the next
1977
+ * page that does, even past `index`; `null` when no page from there on holds text.
1978
+ */
1979
+ anchorAt: (index: number) => InChapterAnchor | null;
1762
1980
  /** Manually refresh `spread` from `layout` at the current index. */
1763
1981
  refresh: () => void;
1764
1982
  }