@libraz/mejiro-react 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +34 -60
  2. package/dist/index.d.ts +1673 -38
  3. package/dist/index.js +3854 -37
  4. package/package.json +35 -8
package/dist/index.d.ts CHANGED
@@ -1,6 +1,496 @@
1
+ import * as react from 'react';
2
+ import { ReactNode, CSSProperties, PointerEvent, TextareaHTMLAttributes, RefObject } from 'react';
3
+ import { EpubBook, AssetResolver, EditableEpub, ManuscriptDialect, EpubChapter, EditableEpubBook, AnnotatedParagraph, AddImageInput, EditableEpubImage, EpubExportOptions, EpubProjectMetadata, EpubProject } from '@libraz/mejiro/epub';
4
+ import { BookOptions, ChapterLayout, SpreadResult, PageResult, ComputePageSizeOptions, InChapterAnchor, ReadingAnchor, ManuscriptChapter, AnchorRect, AnchorRange, MejiroBook } from '@libraz/mejiro/book';
1
5
  import { RenderPage } from '@libraz/mejiro/render';
2
- import { CSSProperties, ReactNode, PointerEvent } from 'react';
3
- import { PageResult, ChapterLayout, SpreadResult } from '@libraz/mejiro/book';
6
+ import { FontFamily, InlineAnnotation } from '@libraz/mejiro/browser';
7
+
8
+ /** All UI strings rendered by the bundled React components. */
9
+ interface MejiroMessages {
10
+ /** Logo subtitle. */
11
+ readonly logoSubtitle: string;
12
+ /** Header "Open" button label. */
13
+ readonly openButton: string;
14
+ /** Header "Settings" button label. */
15
+ readonly settingsButton: string;
16
+ /** Header "Image" overlay button label. */
17
+ readonly imageButton: string;
18
+ /** ARIA label for removing an image overlay. */
19
+ readonly imageRemoveButton: string;
20
+ /** Loading-state body text. */
21
+ readonly loading: string;
22
+ /** ARIA label for the previous-spread nav zone. */
23
+ readonly prevSpread: string;
24
+ /** ARIA label for the next-spread nav zone. */
25
+ readonly nextSpread: string;
26
+ /** Settings panel group title — Font. */
27
+ readonly settingsFont: string;
28
+ /** Settings panel font-size label. */
29
+ readonly settingsSize: string;
30
+ /** Settings panel "decrease font size" button. */
31
+ readonly settingsSizeDown: string;
32
+ /** Settings panel "increase font size" button. */
33
+ readonly settingsSizeUp: string;
34
+ /** Settings panel group title — Layout. */
35
+ readonly settingsLayout: string;
36
+ /** Settings panel kinsoku label. */
37
+ readonly settingsKinsoku: string;
38
+ /** Settings panel hanging-punctuation label. */
39
+ readonly settingsHanging: string;
40
+ /** Settings panel line-spacing label. */
41
+ readonly settingsLineSpacing: string;
42
+ /** Settings panel "strict" kinsoku option. */
43
+ readonly settingsStrict: string;
44
+ /** Settings panel "loose" kinsoku option. */
45
+ readonly settingsLoose: string;
46
+ /** Settings panel "on" toggle option. */
47
+ readonly toggleOn: string;
48
+ /** Settings panel "off" toggle option. */
49
+ readonly toggleOff: string;
50
+ /** Drop zone primary text. */
51
+ readonly dropZoneTitle: string;
52
+ /** Drop zone secondary hint. */
53
+ readonly dropZoneHint: string;
54
+ /** TOC heading. */
55
+ readonly tocTitle: string;
56
+ /** TOC search placeholder. */
57
+ readonly tocSearchPlaceholder: string;
58
+ /** TOC empty-results template. Placeholder `{query}` is replaced. */
59
+ readonly tocEmpty: string;
60
+ /** Shelf heading. */
61
+ readonly shelfTitle: string;
62
+ /** Generic chapter-N template. Placeholder `{n}` is replaced. */
63
+ readonly chapterN: string;
64
+ /** Aria-live announcement template. Placeholders: `{spread}`, `{total}`. */
65
+ readonly spreadAnnouncement: string;
66
+ /** Embedded EPUB editor preview subtitle. */
67
+ readonly editorPreviewSubtitle: string;
68
+ /** EPUB editor panel title. */
69
+ readonly editorTitle: string;
70
+ /** EPUB editor empty-state title. */
71
+ readonly editorNoBookLoaded: string;
72
+ /** EPUB editor paragraph list section label. */
73
+ readonly editorParagraphs: string;
74
+ /** EPUB editor proofread section label. */
75
+ readonly editorProofread: string;
76
+ /** EPUB editor apply-text button label. */
77
+ readonly editorApplyText: string;
78
+ /** EPUB editor ruby section label. */
79
+ readonly editorRuby: string;
80
+ /** EPUB editor ruby usage hint. */
81
+ readonly editorRubyHint: string;
82
+ /** EPUB editor ruby selected-range template. Placeholders: `{start}`, `{end}`, `{count}`. */
83
+ readonly editorRubyRange: string;
84
+ /** EPUB editor ruby input placeholder. */
85
+ readonly editorRubyPlaceholder: string;
86
+ /** EPUB editor apply-ruby button label. */
87
+ readonly editorApplyRuby: string;
88
+ /** EPUB editor image section label. */
89
+ readonly editorImages: string;
90
+ /** EPUB editor insert-image button label. */
91
+ readonly editorInsertImageAfterParagraph: string;
92
+ /** EPUB editor export button label. */
93
+ readonly editorExportEpub: string;
94
+ /** Manuscript editor preview subtitle. */
95
+ readonly manuscriptPreviewSubtitle: string;
96
+ /** Manuscript editor panel title. */
97
+ readonly manuscriptTitle: string;
98
+ /** Manuscript editor ruby notation hint. */
99
+ readonly manuscriptRubyHint: string;
100
+ /** Manuscript editor metadata section label. */
101
+ readonly manuscriptMetadata: string;
102
+ /** Manuscript editor chapters section label. */
103
+ readonly manuscriptChapters: string;
104
+ /** Manuscript editor draft section label. */
105
+ readonly manuscriptDraft: string;
106
+ /** Manuscript editor cover chooser label. */
107
+ readonly manuscriptChooseCoverImage: string;
108
+ /** Manuscript editor default book title. */
109
+ readonly manuscriptDefaultTitle: string;
110
+ /** Manuscript editor default chapter title template. Placeholder `{n}` is replaced. */
111
+ readonly manuscriptDefaultChapterTitle: string;
112
+ /** Manuscript editor default draft body. */
113
+ readonly manuscriptDefaultBody: string;
114
+ /** Generic untitled fallback. */
115
+ readonly untitled: string;
116
+ /** Manuscript editor add-chapter button label. */
117
+ readonly manuscriptAddChapter: string;
118
+ /** Manuscript editor remove-chapter button label. */
119
+ readonly manuscriptRemove: string;
120
+ /** Manuscript editor emphasis-dots button label. */
121
+ readonly manuscriptEmphasisDots: string;
122
+ /** Manuscript editor tate-chu-yoko button label. */
123
+ readonly manuscriptTcy: string;
124
+ /** Manuscript editor emphasis button label. */
125
+ readonly manuscriptEm: string;
126
+ /** Manuscript editor strong button label. */
127
+ readonly manuscriptStrong: string;
128
+ /** ARIA label for the chapter drag handle. Placeholders: `{title}`. */
129
+ readonly manuscriptReorderHandle: string;
130
+ }
131
+ /** Built-in English catalog (the default). */
132
+ declare const enMessages: MejiroMessages;
133
+ /** Built-in Japanese catalog. */
134
+ declare const jaMessages: MejiroMessages;
135
+ /** Built-in locale identifiers. */
136
+ type MejiroLocale = 'en' | 'ja';
137
+ /** Provider that scopes a message catalog to its descendants. */
138
+ declare function MejiroI18nProvider({ locale, messages, children, }: {
139
+ locale?: MejiroLocale;
140
+ messages?: Partial<MejiroMessages>;
141
+ children: ReactNode;
142
+ }): ReactNode;
143
+ /** Options for {@link useI18n}. */
144
+ interface UseI18nOptions {
145
+ /** Built-in locale to use as the base catalog. @defaultValue 'en' */
146
+ locale?: MejiroLocale;
147
+ /** Overrides merged on top of the built-in catalog. */
148
+ messages?: Partial<MejiroMessages>;
149
+ }
150
+ /**
151
+ * Returns the resolved `MejiroMessages` catalog. With no arguments the
152
+ * catalog provided by the nearest {@link MejiroI18nProvider} is returned;
153
+ * passing `locale` / `messages` resolves a catalog without context.
154
+ */
155
+ declare function useI18n(options?: UseI18nOptions): MejiroMessages;
156
+ /** Build a catalog without invoking React (useful in plain modules). */
157
+ declare function resolveMessages(locale: MejiroLocale | undefined, overrides: Partial<MejiroMessages> | undefined): MejiroMessages;
158
+ /** Replace `{name}` placeholders in a template. */
159
+ declare function format(template: string, vars: Record<string, string | number>): string;
160
+
161
+ type MejiroChapterNavVariant = 'select' | 'panel';
162
+ /** Props for {@link MejiroChapterNav}. */
163
+ interface MejiroChapterNavProps {
164
+ /** EPUB to list chapters from. */
165
+ epub: EpubBook;
166
+ /** Current chapter index. */
167
+ chapter: number;
168
+ /** Called when a new chapter is picked. */
169
+ onChange: (chapter: number) => void;
170
+ /** Visual treatment for the chapter list. @defaultValue 'select' */
171
+ variant?: MejiroChapterNavVariant;
172
+ }
173
+ /**
174
+ * Chapter selector for an {@link EpubBook}.
175
+ *
176
+ * @deprecated Prefer {@link MejiroToc} for new code. `MejiroToc` adds
177
+ * search and current-anchor highlighting and renders the same data with a
178
+ * more accessible markup. `MejiroChapterNav` remains supported for the
179
+ * `<select>` variant and existing `MejiroReader` integration.
180
+ */
181
+ declare function MejiroChapterNav({ epub, chapter, onChange, variant, }: MejiroChapterNavProps): ReactNode;
182
+
183
+ /** Props for {@link MejiroDropZone}. */
184
+ interface MejiroDropZoneProps {
185
+ /** File `accept` filter for the picker. @defaultValue '.epub' */
186
+ accept?: string;
187
+ /** Validator. Defaults to `name.endsWith('.epub')`. */
188
+ validateFile?: (file: File) => boolean;
189
+ /** Called with the selected/dropped file. */
190
+ onFile: (file: File) => void;
191
+ /** Custom body content (replaces default icon/text). */
192
+ children?: ReactNode;
193
+ /** Additional class name for the root element. */
194
+ className?: string;
195
+ /** Additional inline styles. */
196
+ style?: CSSProperties;
197
+ }
198
+ /**
199
+ * Drop zone for EPUB files. Combines drag-and-drop with a click-to-open file picker.
200
+ */
201
+ declare function MejiroDropZone({ accept, validateFile, onFile, children, className, style, }: MejiroDropZoneProps): ReactNode;
202
+
203
+ /** A font choice shown in the settings panel. */
204
+ interface FontChoice {
205
+ /** CSS `font-family` value applied to the book. */
206
+ value: string;
207
+ /** Human-readable label. */
208
+ label: string;
209
+ }
210
+ /** Subset of {@link BookOptions} editable from the panel. */
211
+ type EditableSettings = Pick<BookOptions, 'fontFamily' | 'fontSize' | 'lineSpacing' | 'mode' | 'enableHanging'>;
212
+ /** Props for {@link MejiroSettingsPanel}. */
213
+ interface MejiroSettingsPanelProps {
214
+ /** Whether the panel is expanded. */
215
+ open: boolean;
216
+ /** Current editable settings. */
217
+ settings: EditableSettings;
218
+ /** Called when a setting changes. */
219
+ onChange: (next: EditableSettings) => void;
220
+ /** Font choices. */
221
+ fonts?: FontChoice[];
222
+ /** Min font size. @defaultValue 10 */
223
+ minFontSize?: number;
224
+ /** Max font size. @defaultValue 48 */
225
+ maxFontSize?: number;
226
+ }
227
+ /**
228
+ * Reader settings panel: font, size, kinsoku mode, hanging punctuation, line spacing.
229
+ */
230
+ declare function MejiroSettingsPanel({ open, settings, onChange, fonts, minFontSize, maxFontSize, }: MejiroSettingsPanelProps): ReactNode;
231
+
232
+ interface MejiroEditorProps {
233
+ /** URL fetched and loaded on mount. */
234
+ epubUrl?: string;
235
+ /** Font choices passed to the preview reader. */
236
+ fonts?: FontChoice[];
237
+ /**
238
+ * Allow editing paragraph text (the "Proofread" section).
239
+ * @defaultValue true
240
+ */
241
+ enableProofread?: boolean;
242
+ /**
243
+ * Allow editing ruby annotations (the "Ruby" section).
244
+ * @defaultValue true
245
+ */
246
+ enableRuby?: boolean;
247
+ /**
248
+ * Allow inserting images into the EPUB (the "Images" section).
249
+ * @defaultValue true
250
+ */
251
+ enableImages?: boolean;
252
+ /**
253
+ * Allow exporting the edited EPUB. SaaS publishers can disable this to
254
+ * restrict downloads (e.g. server-side export only).
255
+ * @defaultValue true
256
+ */
257
+ enableExport?: boolean;
258
+ /**
259
+ * Called before the export buffer is offered as a download. Return `false`
260
+ * (or a `Promise<false>`) to suppress the browser download — useful for
261
+ * uploading the buffer to a backend instead.
262
+ */
263
+ onBeforeExport?: (buffer: ArrayBuffer) => boolean | undefined | Promise<boolean | undefined>;
264
+ /**
265
+ * Declarative export policy. When set, supersedes `onBeforeExport` for
266
+ * download control and threads watermark / encrypt transforms through the
267
+ * export pipeline.
268
+ */
269
+ exportPolicy?: MejiroExportPolicy;
270
+ /**
271
+ * Resolves URL-only image assets ({@link EditableImageAsset.url} set, `data`
272
+ * unset) into bytes at export time. Forwarded to `editor.export()`. Use this
273
+ * to keep large image bytes off the client during editing and only fetch
274
+ * them once when the EPUB is assembled — e.g. signed S3 URLs that require
275
+ * custom auth headers.
276
+ */
277
+ assetResolver?: AssetResolver;
278
+ /** Called after an EPUB is loaded into the editor. */
279
+ onLoad?: (editor: EditableEpub) => void;
280
+ /** Called after export completes. */
281
+ onExport?: (buffer: ArrayBuffer) => void;
282
+ /** Called when loading or parsing an EPUB fails. */
283
+ onError?: (error: Error) => void;
284
+ }
285
+ /**
286
+ * Declarative restrictions on the export pipeline. Mejiro applies the
287
+ * transforms in this order: `watermark` (modifies the EPUB in-place) →
288
+ * `encrypt` (replaces the buffer with the result) → `allowDownload` (skips
289
+ * the browser download when `false`).
290
+ */
291
+ interface MejiroExportPolicy {
292
+ /**
293
+ * If `false`, the EPUB buffer is still produced (and `onExport` still fires)
294
+ * but no browser download is triggered. Use when shipping the buffer
295
+ * elsewhere (e.g. uploading to a backend).
296
+ * @defaultValue true
297
+ */
298
+ allowDownload?: boolean;
299
+ /**
300
+ * Transforms the EPUB buffer before it is offered for download. Typically
301
+ * a server round-trip that returns a DRM-wrapped EPUB.
302
+ */
303
+ encrypt?: (buffer: ArrayBuffer) => ArrayBuffer | Promise<ArrayBuffer>;
304
+ /**
305
+ * Embeds a visible watermark string into the EPUB. Implemented by writing
306
+ * the text as XHTML metadata; renderers may surface it differently.
307
+ */
308
+ watermark?: {
309
+ text: string;
310
+ opacity?: number;
311
+ };
312
+ }
313
+ /** EPUB editor UI for proofreading, ruby edits, image insertion, and export. */
314
+ declare function MejiroEditor({ epubUrl, fonts, enableProofread, enableRuby, enableImages, enableExport, onBeforeExport, exportPolicy, assetResolver, onLoad, onExport, onError, }: MejiroEditorProps): ReactNode;
315
+
316
+ /** Rectangle describing an image overlay position and size. */
317
+ interface ImageRect {
318
+ x: number;
319
+ y: number;
320
+ w: number;
321
+ h: number;
322
+ }
323
+ /** Options for {@link useImageOverlay}. */
324
+ interface UseImageOverlayOptions {
325
+ /** Default image width in pixels. @defaultValue 120 */
326
+ defaultWidth?: number;
327
+ /** Default image height in pixels. @defaultValue 160 */
328
+ defaultHeight?: number;
329
+ /** Default x position. @defaultValue 80 */
330
+ defaultX?: number;
331
+ /** Default y position. @defaultValue 100 */
332
+ defaultY?: number;
333
+ /** Margin around the image in pixels. Defaults to the book's font size. */
334
+ margin?: number;
335
+ }
336
+ /** Return value of {@link useImageOverlay}. */
337
+ interface UseImageOverlayReturn {
338
+ /** Current image rectangle, or `null` if no overlay is active. */
339
+ imageRect: ImageRect | null;
340
+ /** Whether an image overlay is currently active. */
341
+ hasImage: boolean;
342
+ /** Toggle the image overlay on/off. */
343
+ toggleImage: () => void;
344
+ /** Pointer-down handler for the overlay body (initiates drag). */
345
+ onOverlayPointerDown: (e: PointerEvent) => void;
346
+ /** Pointer-down handler for the resize handle. */
347
+ onResizePointerDown: (e: PointerEvent) => void;
348
+ }
349
+ /**
350
+ * React hook that manages a draggable/resizable image overlay
351
+ * with automatic text reflow via {@link ChapterLayout.syncImages}.
352
+ *
353
+ * @param layout - Current chapter layout (or `null` if not yet loaded).
354
+ * @param spreadIdx - Current spread index.
355
+ * @param onUpdate - Called with the updated {@link SpreadResult} after every reflow.
356
+ * @param options - Default dimensions and position for the overlay.
357
+ *
358
+ * @example
359
+ * ```tsx
360
+ * const { imageRect, hasImage, toggleImage, onOverlayPointerDown, onResizePointerDown } =
361
+ * useImageOverlay(layout, spreadIdx, setSpread);
362
+ * ```
363
+ */
364
+ declare function useImageOverlay(layout: ChapterLayout | null, spreadIdx: number, onUpdate: (spread: SpreadResult) => void, options?: UseImageOverlayOptions): UseImageOverlayReturn;
365
+
366
+ /** Props for {@link MejiroImageOverlay}. */
367
+ interface MejiroImageOverlayProps {
368
+ /** Position and size of the overlay (px). */
369
+ rect: ImageRect;
370
+ /** Label inside the overlay. @defaultValue `messages.imageButton` */
371
+ label?: string;
372
+ /** Pointer-down on the body (drag). */
373
+ onOverlayPointerDown?: (e: PointerEvent) => void;
374
+ /** Pointer-down on the resize handle. */
375
+ onResizePointerDown?: (e: PointerEvent) => void;
376
+ /** Triggered when the close button is pressed. */
377
+ onClose?: () => void;
378
+ }
379
+ /**
380
+ * Decorative image placeholder overlay with drag/resize affordances.
381
+ * Pair the pointer handlers with {@link useImageOverlay} or {@link useMultiImageOverlay}.
382
+ */
383
+ declare function MejiroImageOverlay({ rect, label, onOverlayPointerDown, onResizePointerDown, onClose, }: MejiroImageOverlayProps): ReactNode;
384
+
385
+ interface ManuscriptEditorChapter {
386
+ id: string;
387
+ title: string;
388
+ body: string;
389
+ }
390
+ /**
391
+ * Subset of {@link MejiroReader} props that the manuscript editor passes
392
+ * through to the live preview. Properties driven by the editor itself
393
+ * (`manuscript`, `fonts`, `chapter`, `onChapterChange`) are ignored if
394
+ * supplied here.
395
+ */
396
+ interface ManuscriptPreviewProps {
397
+ subtitle?: string;
398
+ title?: string;
399
+ chapterNavMode?: 'select' | 'panel' | 'both' | 'none';
400
+ enableHeader?: boolean;
401
+ enableChapterNav?: boolean;
402
+ enableSettings?: boolean;
403
+ enableStats?: boolean;
404
+ enableKeyboard?: boolean;
405
+ enablePageIndicator?: boolean;
406
+ /**
407
+ * Toggle the surface-tap chrome-hide behavior of the embedded reader.
408
+ * Defaults to `false` here (the editor's preview is part of a side-by-side
409
+ * authoring workflow where tap-to-hide would surprise the author). Set
410
+ * to `true` to demo the fullscreen-reader behavior.
411
+ */
412
+ enableSurfaceTap?: boolean;
413
+ bare?: boolean;
414
+ }
415
+ interface MejiroManuscriptEditorProps {
416
+ /** Font choices passed to the preview reader. */
417
+ fonts?: FontChoice[];
418
+ /**
419
+ * Title. When `onTitleChange` is supplied, this is the controlled value
420
+ * (changes propagate from parent to the input). Otherwise it is the
421
+ * initial value and the editor manages its own title state.
422
+ */
423
+ title?: string;
424
+ /** Called whenever the title input changes. Enables controlled mode. */
425
+ onTitleChange?: (title: string) => void;
426
+ /**
427
+ * Author. Controlled when `onAuthorChange` is supplied; otherwise treated
428
+ * as the initial value.
429
+ */
430
+ author?: string;
431
+ /** Called whenever the author input changes. Enables controlled mode. */
432
+ onAuthorChange?: (author: string) => void;
433
+ /**
434
+ * Cover image. Controlled when `onCoverChange` is supplied; otherwise
435
+ * treated as the initial cover. Setting `null` clears the cover.
436
+ */
437
+ cover?: File | null;
438
+ /** Called whenever the cover changes. Enables controlled mode. */
439
+ onCoverChange?: (cover: File | null) => void;
440
+ /** Initial chapters. */
441
+ chapters?: ManuscriptEditorChapter[];
442
+ /**
443
+ * Called whenever the chapter draft settles (debounced by
444
+ * {@link MejiroManuscriptEditorProps.autosaveDelay}). Use to persist drafts
445
+ * to localStorage, IndexedDB, or upload to a server.
446
+ */
447
+ onAutosave?: (chapters: ManuscriptEditorChapter[]) => void | Promise<void>;
448
+ /** Autosave debounce in milliseconds. @defaultValue 800 */
449
+ autosaveDelay?: number;
450
+ /**
451
+ * Props forwarded to the embedded {@link MejiroReader} preview. Lets
452
+ * hosts customize subtitle / chapterNavMode / etc.; `manuscript`, `fonts`,
453
+ * `chapter`, and `onChapterChange` remain driven by the editor.
454
+ */
455
+ previewProps?: ManuscriptPreviewProps;
456
+ /**
457
+ * Resolves URL-only project assets (e.g. covers/illustrations registered as
458
+ * `{ url, ... }`) into bytes at export time. Forwarded to `project.export()`
459
+ * so authors can register signed-URL references without holding raw bytes
460
+ * client-side until publish.
461
+ */
462
+ assetResolver?: AssetResolver;
463
+ /** Called after export completes. */
464
+ onExport?: (buffer: ArrayBuffer) => void;
465
+ }
466
+ /** Manuscript-to-EPUB editor for author drafts from posting sites. */
467
+ declare function MejiroManuscriptEditor({ fonts, title: titleProp, onTitleChange, author: authorProp, onAuthorChange, cover: coverProp, onCoverChange, chapters: initialChapters, onAutosave, autosaveDelay, previewProps, assetResolver, onExport, }: MejiroManuscriptEditorProps): ReactNode;
468
+
469
+ /** Props for {@link MejiroNotationHighlighter}. */
470
+ interface MejiroNotationHighlighterProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'onChange'> {
471
+ /** Source manuscript text. */
472
+ value: string;
473
+ /** Called whenever the textarea content changes. */
474
+ onChange: (next: string) => void;
475
+ /** Manuscript notation dialect. @defaultValue `'mejiro'` */
476
+ dialect?: ManuscriptDialect;
477
+ /** Class applied to the wrapper element. */
478
+ wrapperClassName?: string;
479
+ /** Inline style on the wrapper element. */
480
+ wrapperStyle?: CSSProperties;
481
+ }
482
+ /**
483
+ * Textarea with a notation-highlight overlay. The overlay sits *behind* the
484
+ * textarea and tints the background of manuscript notation tokens (ruby,
485
+ * emphasis dots, tate-chu-yoko, em / strong, links, footnotes) so authors can
486
+ * see at a glance which characters carry notation.
487
+ *
488
+ * The textarea is fully interactive; selection, undo / redo, and the
489
+ * browser-native caret are unaffected. Notation kinds are exposed via
490
+ * `data-token` attributes on the overlay spans so hosts can restyle them via
491
+ * CSS (`.mejiro-notation-token[data-token="ruby"]` etc.).
492
+ */
493
+ declare const MejiroNotationHighlighter: react.ForwardRefExoticComponent<MejiroNotationHighlighterProps & react.RefAttributes<HTMLTextAreaElement>>;
4
494
 
5
495
  /** Props for the MejiroPage component. */
6
496
  interface MejiroPageProps {
@@ -19,12 +509,22 @@ interface MejiroPageProps {
19
509
  */
20
510
  declare function MejiroPage({ page, className, style }: MejiroPageProps): ReactNode;
21
511
 
512
+ /** Props for {@link MejiroPageIndicator}. */
513
+ interface MejiroPageIndicatorProps {
514
+ /** Current spread (1-based). */
515
+ current: number;
516
+ /** Total number of spreads. */
517
+ total: number;
518
+ }
519
+ /** Displays "current / total" spread position under the book. */
520
+ declare function MejiroPageIndicator({ current, total }: MejiroPageIndicatorProps): ReactNode;
521
+
22
522
  /** Props for the MejiroPageView component. */
23
523
  interface MejiroPageViewProps {
24
524
  /** Page result from {@link ChapterLayout.getSpread} or {@link ChapterLayout.getPage}. */
25
525
  result: PageResult;
26
526
  /** CSS font family for slot-based rendering (used when images are present). */
27
- fontFamily?: string;
527
+ fontFamily?: FontFamily;
28
528
  /** Line spacing multiplier for slot-based rendering (used when images are present). */
29
529
  lineSpacing?: number;
30
530
  /**
@@ -53,54 +553,1189 @@ interface MejiroPageViewProps {
53
553
  */
54
554
  declare function MejiroPageView({ result, fontFamily, lineSpacing, slotMode, className, style, }: MejiroPageViewProps): ReactNode;
55
555
 
56
- /** Rectangle describing an image overlay position and size. */
57
- interface ImageRect {
58
- x: number;
59
- y: number;
60
- w: number;
61
- h: number;
556
+ type MejiroChapterNavMode = 'select' | 'panel' | 'both' | 'none';
557
+ /**
558
+ * Reading-flow mode for {@link MejiroReader}.
559
+ * - `paginated` — two-page spread with page-turn animation (default).
560
+ * - `scroll` — every page in the chapter stacked in a vertical scroll view.
561
+ */
562
+ type MejiroReaderMode = 'paginated' | 'scroll';
563
+ /**
564
+ * Two-page vs single-page rendering of a spread.
565
+ * - `double` — always render two pages (default).
566
+ * - `single` — render only the right page, centered.
567
+ * - `auto` — switch based on the surface aspect ratio (single when portrait).
568
+ */
569
+ type MejiroSpreadMode = 'double' | 'single' | 'auto';
570
+ /**
571
+ * Which page of a spread prints its page number: `'both'` (right = odd,
572
+ * left = even), only `'right'`, only `'left'`, or `'none'`.
573
+ */
574
+ type PageNumberDisplay = 'both' | 'right' | 'left' | 'none';
575
+ /**
576
+ * How the reader sizes itself inside its container.
577
+ * - `fill` — fill the container's given height; the spread is fitted inside it,
578
+ * letterboxing if the box aspect doesn't match the spread (the default; the
579
+ * host must give the reader a height).
580
+ * - `width` — self-size from width: the reader derives its own height from its
581
+ * measured width and the page aspect ratio, so the spread fills exactly with
582
+ * no letterbox and the host needs no height/aspect magic numbers. The host
583
+ * only constrains the width.
584
+ */
585
+ type MejiroReaderFit = 'fill' | 'width';
586
+ /** Built-in reader theme presets. */
587
+ type MejiroThemeName = 'light' | 'dark' | 'sepia' | 'high-contrast' | 'auto';
588
+ /**
589
+ * Theme configuration for the reader. Either a preset name, or an object
590
+ * with a preset and an `override` map of CSS custom properties that take
591
+ * precedence over the preset values.
592
+ */
593
+ type MejiroTheme = MejiroThemeName | {
594
+ name: MejiroThemeName;
595
+ override?: Record<string, string>;
596
+ };
597
+ /** Reading position exposed by {@link MejiroReaderHandle.getReadingPosition}. */
598
+ interface ReadingPosition {
599
+ chapter: number;
600
+ spreadIdx: number;
601
+ totalPages: number;
602
+ totalSpreads: number;
62
603
  }
63
- /** Options for {@link useImageOverlay}. */
64
- interface UseImageOverlayOptions {
65
- /** Default image width in pixels. @defaultValue 120 */
604
+ /**
605
+ * Event payloads emitted by {@link MejiroReaderHandle.subscribe}.
606
+ *
607
+ * Each property maps an event name to its listener signature.
608
+ */
609
+ interface MejiroReaderEventMap {
610
+ /** Fires after the current spread index changes. */
611
+ spreadChanged: (payload: {
612
+ chapter: number;
613
+ spreadIdx: number;
614
+ }) => void;
615
+ /** Fires when a turn animation begins (before the new spread is shown). */
616
+ turnStart: (payload: {
617
+ from: number;
618
+ }) => void;
619
+ /** Fires after a turn animation completes (the new spread is now shown). */
620
+ turnEnd: (payload: {
621
+ to: number;
622
+ }) => void;
623
+ /** Fires when the reader reaches the last spread of the current chapter. */
624
+ chapterFinished: (payload: {
625
+ chapter: number;
626
+ }) => void;
627
+ }
628
+ /** Imperative handle returned by `ref={...}` on {@link MejiroReader}. */
629
+ interface MejiroReaderHandle {
630
+ /** Jump to a specific spread (0-based, clamped to [0, totalSpreads − 1]). */
631
+ goToSpread(index: number): void;
632
+ /** Advance one spread forward. */
633
+ next(): void;
634
+ /** Go back one spread. */
635
+ prev(): void;
636
+ /** Jump to a chapter (resets spread index to 0). */
637
+ goToChapter(index: number): void;
638
+ /** Read the current reading position. */
639
+ getReadingPosition(): ReadingPosition;
640
+ /**
641
+ * Navigate to a {@link ReadingAnchor}. If the chapter differs from the
642
+ * current one, the chapter is switched first; once the new layout is ready
643
+ * the anchor is resolved and the matching spread is opened.
644
+ *
645
+ * Returns a promise that resolves once the spread has been applied. If
646
+ * another `goToAnchor` is invoked before the previous one settles, the
647
+ * earlier promise resolves immediately (superseded). Resolves on unmount.
648
+ */
649
+ goToAnchor(anchor: ReadingAnchor): Promise<void>;
650
+ /**
651
+ * Returns the {@link ReadingAnchor} at the start of the current spread,
652
+ * or `null` if the layout is not ready.
653
+ */
654
+ getAnchor(): ReadingAnchor | null;
655
+ /**
656
+ * Returns the half-open range of {@link ReadingAnchor}s visible on the
657
+ * current spread. `end` points at the start of the next spread (or the end
658
+ * of the chapter for the last spread).
659
+ */
660
+ getVisibleRange(): {
661
+ start: ReadingAnchor;
662
+ end: ReadingAnchor;
663
+ } | null;
664
+ /**
665
+ * Updates book options at runtime. Same shape as {@link MejiroBook.setOptions}
666
+ * — font / size changes re-measure and re-layout asynchronously.
667
+ */
668
+ setOptions(partial: Partial<BookOptions>): Promise<void>;
669
+ /**
670
+ * Subscribes to a reader lifecycle event. Returns a function that
671
+ * detaches the listener.
672
+ */
673
+ subscribe<E extends keyof MejiroReaderEventMap>(event: E, listener: MejiroReaderEventMap[E]): () => void;
674
+ }
675
+ /**
676
+ * Context passed to a {@link MejiroReaderCommonProps.renderSettings} render prop.
677
+ *
678
+ * Lets a host replace the built-in settings controls with its own UI while
679
+ * keeping the panel chrome and its open/close accordion. All fields are wired
680
+ * to the live reader.
681
+ */
682
+ interface MejiroReaderSettingsSlot {
683
+ /** Current effective settings (font, size, line spacing, kinsoku, hanging). */
684
+ settings: EditableSettings;
685
+ /** Applies a partial settings change and re-flows the chapter. */
686
+ update: (partial: Partial<BookOptions>) => void;
687
+ /** Whether the settings panel is currently open. */
688
+ open: boolean;
689
+ /** Toggles the settings panel open/closed (same as the header button). */
690
+ toggle: () => void;
691
+ }
692
+ /** Props shared across every {@link MejiroReader} source mode. */
693
+ interface MejiroReaderCommonProps {
694
+ /**
695
+ * Initial book options. Optional — defaults to {@link DEFAULT_BOOK_OPTIONS}
696
+ * (`serif` 16px, line spacing 1.8, strict kinsoku, hanging punctuation on).
697
+ * Spread the defaults to tweak only a few:
698
+ *
699
+ * ```ts
700
+ * { ...DEFAULT_BOOK_OPTIONS, fontFamily: '"Noto Serif JP"', fontSize: 18 }
701
+ * ```
702
+ */
703
+ options?: BookOptions;
704
+ /**
705
+ * Page-geometry overrides forwarded to `MejiroBook.computePageSize`. Use to
706
+ * tune how the spread is sized inside the surface — most usefully to shrink
707
+ * the reserved margins so the pages fill their frame, e.g.
708
+ * `pageGeometry={{ gutterOffset: 0, headerOffset: 0 }}`. Also accepts
709
+ * `aspect`, `minWidth`, `minHeight`, `maxHeight`, and inner `padding`; omitted
710
+ * fields fall back to the built-in defaults.
711
+ */
712
+ pageGeometry?: ComputePageSizeOptions;
713
+ /** Font choices for the settings panel. */
714
+ fonts?: FontChoice[];
715
+ /**
716
+ * Controlled chapter index. When omitted, the reader manages its own
717
+ * chapter state and resets to 0 on EPUB change.
718
+ */
719
+ chapter?: number;
720
+ /**
721
+ * Controlled spread index. When supplied, the reader is driven by this
722
+ * value and emits {@link MejiroReaderProps.onSpreadIdxChange} on user
723
+ * navigation. Combine with `useReadingPosition` for save/restore.
724
+ */
725
+ spreadIdx?: number;
726
+ /**
727
+ * Visual theme preset, or `{ name, override }` to layer custom
728
+ * CSS variables on top of a preset. @defaultValue 'light'
729
+ *
730
+ * The selected name is reflected as `data-mejiro-theme` on the reader
731
+ * root, which the bundled CSS uses to swap palettes.
732
+ */
733
+ theme?: MejiroTheme;
734
+ /**
735
+ * Reading-flow mode. `paginated` (default) shows one spread at a time;
736
+ * `scroll` stacks every page in the chapter inside a vertical scroller.
737
+ */
738
+ mode?: MejiroReaderMode;
739
+ /**
740
+ * Spread layout. `double` (default) renders two pages; `single` renders
741
+ * only the right page; `auto` flips to `single` for portrait viewports.
742
+ */
743
+ spreadMode?: MejiroSpreadMode;
744
+ /**
745
+ * How the reader sizes itself in its container. `fill` (default) fills the
746
+ * container height and letterboxes the spread; `width` makes the reader
747
+ * self-size — it derives its height from its width and the page aspect, so an
748
+ * embedding host only has to constrain the width (no height/aspect magic
749
+ * numbers, no letterbox). In `width` mode the reserved `gutterOffset` /
750
+ * `headerOffset` default to 0 so the spread fills edge-to-edge; override via
751
+ * {@link MejiroReaderProps.pageGeometry} if you still want them. @defaultValue 'fill'
752
+ */
753
+ fit?: MejiroReaderFit;
754
+ /**
755
+ * Enable surface-tap chrome toggling. Tapping the center of the spread
756
+ * (away from buttons) hides the header and chapter panel; tapping again
757
+ * shows them. @defaultValue true
758
+ */
759
+ enableSurfaceTap?: boolean;
760
+ /**
761
+ * Static fallback rendered while the layout is still hydrating. Pair with
762
+ * {@link renderEpubStatic} to ship server-rendered vertical text that
763
+ * search engines and slow connections can see before the client reader
764
+ * is ready.
765
+ */
766
+ fallback?: ReactNode;
767
+ /**
768
+ * Extra options merged into the EPUB `fetch` call (URL mode). Useful for
769
+ * sending bearer tokens or cookies.
770
+ */
771
+ fetchOptions?: RequestInit;
772
+ /**
773
+ * Custom EPUB fetcher used in place of the global `fetch`. Overrides
774
+ * {@link fetchOptions} when set.
775
+ */
776
+ fetchEpub?: (url: string) => Promise<ArrayBuffer>;
777
+ /**
778
+ * Built-in locale for UI strings (`'en'` / `'ja'`). Pair with `messages`
779
+ * to override individual strings. @defaultValue 'en'
780
+ */
781
+ locale?: MejiroLocale;
782
+ /**
783
+ * Partial override of the message catalog. Merged on top of the catalog
784
+ * selected by `locale`. Useful for projects that ship their own UI strings
785
+ * without re-implementing every label.
786
+ */
787
+ messages?: Partial<MejiroMessages>;
788
+ /** Header title text. @defaultValue 'mejiro' */
789
+ title?: string;
790
+ /** Header subtitle. @defaultValue `messages.logoSubtitle` */
791
+ subtitle?: string;
792
+ /**
793
+ * Replaces the default logo block (title + subtitle). Pass `null` to hide
794
+ * the logo while keeping the rest of the header. To remove the entire
795
+ * header, use `enableHeader={false}`.
796
+ */
797
+ logo?: ReactNode;
798
+ /**
799
+ * Shorthand for a chrome-less reader. When `true`, the defaults for
800
+ * `enableHeader`, `enableChapterNav`, `enableSettings`, `enableStats`, and
801
+ * `enablePageIndicator` flip from `true` to `false`. Explicitly-passed
802
+ * enable* props still win, so you can opt parts back in.
803
+ * @defaultValue false
804
+ */
805
+ bare?: boolean;
806
+ /** Show the built-in header. @defaultValue `!bare` */
807
+ enableHeader?: boolean;
808
+ /**
809
+ * Show the drop zone affordance. SaaS-style readers should keep this off
810
+ * (the host controls which EPUB is delivered); set true to accept
811
+ * user-supplied books.
812
+ * @defaultValue false
813
+ */
814
+ enableDropZone?: boolean;
815
+ /** Show the chapter selector in the header. @defaultValue `!bare` */
816
+ enableChapterNav?: boolean;
817
+ /**
818
+ * Where to render the built-in chapter navigation.
819
+ * @defaultValue 'select'
820
+ */
821
+ chapterNavMode?: MejiroChapterNavMode;
822
+ /** Show the settings panel toggle. @defaultValue `!bare` */
823
+ enableSettings?: boolean;
824
+ /**
825
+ * Replaces the built-in settings controls with custom UI while keeping the
826
+ * panel chrome and its open/close accordion. Receives a
827
+ * {@link MejiroReaderSettingsSlot} wired to the live reader, so a host can
828
+ * build its own settings form without a parallel `options` shadow. The header
829
+ * "Settings" button still toggles the panel. `enableSettings` remains the
830
+ * on/off switch.
831
+ */
832
+ renderSettings?: (slot: MejiroReaderSettingsSlot) => ReactNode;
833
+ /** Show the image-overlay editing/demo button. @defaultValue false */
834
+ enableImageOverlay?: boolean;
835
+ /** Show the stats line. @defaultValue `!bare` */
836
+ enableStats?: boolean;
837
+ /** Bind ArrowLeft/ArrowRight to navigation. @defaultValue true */
838
+ enableKeyboard?: boolean;
839
+ /** Show the "n / total" indicator. @defaultValue `!bare` */
840
+ enablePageIndicator?: boolean;
841
+ /**
842
+ * Which page of a spread shows its page number in the running head.
843
+ * `'both'` numbers each page (right = odd, left = even), `'right'` /
844
+ * `'left'` number only that side, `'none'` hides them (the
845
+ * {@link MejiroReaderProps.enablePageIndicator} "n / total" badge is
846
+ * independent). @defaultValue 'both'
847
+ */
848
+ pageNumbers?: PageNumberDisplay;
849
+ /**
850
+ * Reader-side annotations to render as highlights. Each annotation whose
851
+ * `chapter` matches the current chapter is converted to spread-local
852
+ * rectangles via `ChapterLayout.selectionRects` and drawn on top of the
853
+ * page content. Pair with {@link useAnnotations} for persistence, or pass
854
+ * any shape that satisfies `{ chapter, start, end }`.
855
+ */
856
+ annotations?: ReadonlyArray<{
857
+ chapter: number;
858
+ start: InChapterAnchor;
859
+ end: InChapterAnchor;
860
+ color?: string;
861
+ }>;
862
+ /** Called after a successful EPUB load. */
863
+ onLoad?: (book: EpubBook) => void;
864
+ /** Called when loading or parsing an EPUB fails. */
865
+ onError?: (error: Error) => void;
866
+ /** Called when the chapter index changes. */
867
+ onChapterChange?: (chapter: number) => void;
868
+ /** Called when the spread index changes (alias: `onSpreadIdxChange`). */
869
+ onSpreadChange?: (spreadIdx: number) => void;
870
+ /** Called when the spread index changes — pair with the `spreadIdx` prop for controlled use. */
871
+ onSpreadIdxChange?: (spreadIdx: number) => void;
872
+ /**
873
+ * Called when the reader leaves a spread. Receives the anchor of the
874
+ * spread that was just left and the dwell time in milliseconds (computed
875
+ * via `performance.now()`). Useful for engagement analytics.
876
+ */
877
+ onPageRead?: (anchor: ReadingAnchor, dwellMs: number) => void;
878
+ /**
879
+ * Called when the reader reaches the last spread of a chapter. Same
880
+ * trigger as the `chapterFinished` event on {@link MejiroReaderHandle.subscribe}.
881
+ */
882
+ onChapterCompleted?: (chapter: number) => void;
883
+ }
884
+ /**
885
+ * Controlled-source variant: render a pre-parsed `EpubBook`. Pass `null`
886
+ * to render an empty reader (e.g. while the book is still loading on the host).
887
+ */
888
+ interface MejiroReaderControlledProps extends MejiroReaderCommonProps {
889
+ /** Pre-parsed EPUB. Cannot be combined with `epubUrl` / `manuscript`. */
890
+ epub: EpubBook | null;
891
+ epubUrl?: never;
892
+ manuscript?: never;
893
+ }
894
+ /**
895
+ * URL-source variant: the reader fetches and parses the EPUB itself.
896
+ * Use this for "just open this book" scenarios.
897
+ */
898
+ interface MejiroReaderUrlProps extends MejiroReaderCommonProps {
899
+ /** EPUB URL fetched on mount. Cannot be combined with `epub` / `manuscript`. */
900
+ epubUrl: string;
901
+ epub?: never;
902
+ manuscript?: never;
903
+ }
904
+ /**
905
+ * File-source variant: the reader exposes its drop zone / file picker.
906
+ * Neither `epub` nor `epubUrl` is supplied — useful for free-form viewers
907
+ * that accept user-supplied books.
908
+ */
909
+ interface MejiroReaderFileProps extends MejiroReaderCommonProps {
910
+ epub?: never;
911
+ epubUrl?: never;
912
+ manuscript?: never;
913
+ }
914
+ /**
915
+ * Manuscript-source variant: render manuscript chapters directly without an
916
+ * EPUB ZIP round-trip. Designed for live preview in custom manuscript editors;
917
+ * each chapter body is split into paragraphs on blank lines and run through
918
+ * {@link parseManuscript} before layout.
919
+ */
920
+ interface MejiroReaderManuscriptProps extends MejiroReaderCommonProps {
921
+ /** Manuscript chapters to render. Cannot be combined with `epub` / `epubUrl`. */
922
+ manuscript: readonly ManuscriptChapter[];
923
+ /** Manuscript notation dialect. @defaultValue `'mejiro'` */
924
+ dialect?: ManuscriptDialect;
925
+ epub?: never;
926
+ epubUrl?: never;
927
+ }
928
+ /**
929
+ * Props for {@link MejiroReader}. Discriminated union of the four source
930
+ * modes — TypeScript prevents passing more than one source at once.
931
+ */
932
+ type MejiroReaderProps = MejiroReaderControlledProps | MejiroReaderUrlProps | MejiroReaderFileProps | MejiroReaderManuscriptProps;
933
+ declare const MejiroReader: react.ForwardRefExoticComponent<MejiroReaderProps & react.RefAttributes<MejiroReaderHandle>>;
934
+
935
+ /** Props for {@link MejiroScrollView}. */
936
+ interface MejiroScrollViewProps {
937
+ /** Layout containing the pages to render. */
938
+ layout: ChapterLayout;
939
+ /** Page width in px. */
940
+ pageWidth: number;
941
+ /** Page height in px. */
942
+ pageHeight: number;
943
+ /** Content area height (px). */
944
+ contentHeight: number;
945
+ /** CSS font family applied to the content. */
946
+ fontFamily?: FontFamily;
947
+ /** Font size override (px). */
948
+ fontSize?: number;
949
+ /** Line spacing multiplier. */
950
+ lineSpacing?: number;
951
+ /** Force slot-based rendering on every page. */
952
+ slotMode?: boolean;
953
+ /**
954
+ * Visible page index reported to the parent as the user scrolls. The page
955
+ * with the largest intersection with the viewport is treated as visible.
956
+ */
957
+ onVisiblePageChange?: (pageIdx: number) => void;
958
+ /**
959
+ * Target page to scroll into view. When set, the view scrolls so that the
960
+ * matching page sits at the top of the scroll container.
961
+ */
962
+ scrollToPage?: number;
963
+ /** Vertical gap between pages (px). @defaultValue 24 */
964
+ pageGap?: number;
965
+ }
966
+ /**
967
+ * Continuous-scroll variant of {@link MejiroSpread}. Stacks every page in
968
+ * the chapter inside a vertically scrollable container. The visible page is
969
+ * detected via `IntersectionObserver` and reported through
970
+ * {@link MejiroScrollViewProps.onVisiblePageChange}.
971
+ */
972
+ declare function MejiroScrollView({ layout, pageWidth, pageHeight, contentHeight, fontFamily, fontSize, lineSpacing, onVisiblePageChange, scrollToPage, pageGap, }: MejiroScrollViewProps): ReactNode;
973
+
974
+ /** Props for {@link MejiroSelectionLayer}. */
975
+ interface MejiroSelectionLayerProps {
976
+ /**
977
+ * Spread-local rectangles to highlight. Compute these via
978
+ * {@link ChapterLayout.selectionRects} and pass the full array; the layer
979
+ * picks the entries matching {@link MejiroSelectionLayerProps.side}.
980
+ */
981
+ rects: readonly AnchorRect[];
982
+ /** Which page-side this layer is rendered into. */
983
+ side: 'right' | 'left';
984
+ /** Optional class added to the outer wrapper. */
985
+ className?: string;
986
+ /** Optional inline style merged onto the outer wrapper. */
987
+ style?: CSSProperties;
988
+ /** Optional class for each rectangle (defaults to `mejiro-selection-rect`). */
989
+ rectClassName?: string;
990
+ /** Optional inline style for each rectangle. */
991
+ rectStyle?: CSSProperties;
992
+ }
993
+ /**
994
+ * Renders a selection-highlight overlay inside a page-content container.
995
+ *
996
+ * Use as a child of `.mejiro-reader-page-clip` (or any element that shares the
997
+ * page's content coordinate frame). The layer is pointer-transparent so it
998
+ * does not interfere with selection drag on the page below.
999
+ */
1000
+ declare function MejiroSelectionLayer({ rects, side, className, style, rectClassName, rectStyle, }: MejiroSelectionLayerProps): ReactNode;
1001
+
1002
+ /** A single volume in a multi-book library. */
1003
+ interface VolumeInfo<T = unknown> {
1004
+ /** Stable identifier (e.g. ISBN, slug, internal ID). */
1005
+ id: string;
1006
+ /** Human-readable title shown in pickers. */
1007
+ label: string;
1008
+ /** Optional author / byline. */
1009
+ author?: string;
1010
+ /** Optional cover image URL. */
1011
+ cover?: string;
1012
+ /** Free-form metadata attached by the host. */
1013
+ meta?: T;
1014
+ }
1015
+ /** Options for {@link useLibrary}. */
1016
+ interface UseLibraryOptions<T = unknown> {
1017
+ /** The volumes available to the reader. */
1018
+ volumes: readonly VolumeInfo<T>[];
1019
+ /** ID of the volume to start on. Defaults to the first entry. */
1020
+ initialVolumeId?: string;
1021
+ /** Called whenever the active volume changes. */
1022
+ onChange?: (volume: VolumeInfo<T>) => void;
1023
+ }
1024
+ /** Return value of {@link useLibrary}. */
1025
+ interface UseLibraryReturn<T = unknown> {
1026
+ /** The list of volumes (same reference as the options input). */
1027
+ list: readonly VolumeInfo<T>[];
1028
+ /** Currently active volume, or `null` for an empty library. */
1029
+ current: VolumeInfo<T> | null;
1030
+ /** Index of the current volume, or `-1` for an empty library. */
1031
+ currentIndex: number;
1032
+ /** Advance to the next volume in the list (no-op at the end). */
1033
+ next(): void;
1034
+ /** Go back to the previous volume in the list (no-op at the start). */
1035
+ prev(): void;
1036
+ /** Jump to a volume by ID. No-op when the ID is not in the list. */
1037
+ goTo(id: string): void;
1038
+ }
1039
+ /**
1040
+ * Headless hook for managing a multi-volume reading session. Pair it with
1041
+ * {@link MejiroReader} (driving its `epub` / `epubUrl` prop) or
1042
+ * {@link MejiroShelf} (visual picker).
1043
+ */
1044
+ declare function useLibrary<T = unknown>(options: UseLibraryOptions<T>): UseLibraryReturn<T>;
1045
+
1046
+ /** Props for {@link MejiroShelf}. */
1047
+ interface MejiroShelfProps<T = unknown> {
1048
+ /** Volumes to display. */
1049
+ volumes: readonly VolumeInfo<T>[];
1050
+ /** ID of the currently active volume (highlighted in the grid). */
1051
+ currentId?: string;
1052
+ /** Called when the user picks a volume. */
1053
+ onSelect?: (volume: VolumeInfo<T>) => void;
1054
+ /** Heading shown above the grid. @defaultValue 'Library' */
1055
+ title?: string;
1056
+ }
1057
+ /**
1058
+ * Visual bookshelf picker. Renders each volume as a card with cover, label,
1059
+ * and author. Pair with {@link useLibrary} to drive selection.
1060
+ */
1061
+ declare function MejiroShelf<T = unknown>({ volumes, currentId, onSelect, title, }: MejiroShelfProps<T>): ReactNode;
1062
+
1063
+ /** A single image overlay placed on a specific spread. */
1064
+ interface MultiImageItem {
1065
+ /** Stable ID for the overlay (auto-assigned on add). */
1066
+ id: string;
1067
+ /** Position and size in pixels, relative to the right page. */
1068
+ rect: ImageRect;
1069
+ }
1070
+ /** Options for {@link useMultiImageOverlay}. */
1071
+ interface UseMultiImageOverlayOptions {
1072
+ /** Default image width. @defaultValue 120 */
66
1073
  defaultWidth?: number;
67
- /** Default image height in pixels. @defaultValue 160 */
1074
+ /** Default image height. @defaultValue 160 */
68
1075
  defaultHeight?: number;
69
- /** Default x position. @defaultValue 80 */
1076
+ /** Default x. @defaultValue 80 */
70
1077
  defaultX?: number;
71
- /** Default y position. @defaultValue 100 */
1078
+ /** Default y. @defaultValue 100 */
72
1079
  defaultY?: number;
73
- /** Margin around the image in pixels. Defaults to the book's font size. */
1080
+ /** Margin around each image, in pixels. Defaults to the book's font size. */
74
1081
  margin?: number;
1082
+ /** Called after every reflow caused by adding/removing/moving an image. */
1083
+ onUpdate?: (spread: SpreadResult) => void;
75
1084
  }
76
- /** Return value of {@link useImageOverlay}. */
77
- interface UseImageOverlayReturn {
78
- /** Current image rectangle, or `null` if no overlay is active. */
79
- imageRect: ImageRect | null;
80
- /** Whether an image overlay is currently active. */
81
- hasImage: boolean;
82
- /** Toggle the image overlay on/off. */
83
- toggleImage: () => void;
84
- /** Pointer-down handler for the overlay body (initiates drag). */
85
- onOverlayPointerDown: (e: PointerEvent) => void;
1085
+ /** Return value of {@link useMultiImageOverlay}. */
1086
+ interface UseMultiImageOverlayReturn {
1087
+ /** Map of all images keyed by spread index. */
1088
+ imagesBySpread: Map<number, MultiImageItem[]>;
1089
+ /** Images on the current spread. */
1090
+ currentImages: MultiImageItem[];
1091
+ /** Whether any spread has at least one image. */
1092
+ hasImages: boolean;
1093
+ /** Add a new image on the current spread. */
1094
+ addImage: (overrides?: Partial<ImageRect>) => MultiImageItem;
1095
+ /** Remove the image with the given id. */
1096
+ removeImage: (id: string) => void;
1097
+ /** Replace an image's rectangle. */
1098
+ updateImage: (id: string, rect: Partial<ImageRect>) => void;
1099
+ /** Remove every image (or only those on `spreadIdx` if given). */
1100
+ clearImages: (spreadIdx?: number) => void;
1101
+ /** Pointer-down handler for the overlay body (drag). */
1102
+ onOverlayPointerDown: (id: string, e: PointerEvent) => void;
86
1103
  /** Pointer-down handler for the resize handle. */
87
- onResizePointerDown: (e: PointerEvent) => void;
1104
+ onResizePointerDown: (id: string, e: PointerEvent) => void;
88
1105
  }
89
1106
  /**
90
- * React hook that manages a draggable/resizable image overlay
91
- * with automatic text reflow via {@link ChapterLayout.syncImages}.
1107
+ * React hook that manages multiple draggable/resizable image overlays
1108
+ * per spread with automatic reflow via {@link ChapterLayout.setImages}.
1109
+ */
1110
+ declare function useMultiImageOverlay(layout: ChapterLayout | null, spreadIdx: number, options?: UseMultiImageOverlayOptions): UseMultiImageOverlayReturn;
1111
+
1112
+ /** Per-page header data. */
1113
+ interface PageHeaderData {
1114
+ /** Running title (left-aligned). */
1115
+ title?: string;
1116
+ /** Page number (right-aligned). `null` hides it. */
1117
+ pageNumber?: number | null;
1118
+ }
1119
+ /** Props for {@link MejiroSpread}. */
1120
+ interface MejiroSpreadProps {
1121
+ /** Spread data to render. */
1122
+ spread: SpreadResult;
1123
+ /** Page width (px). */
1124
+ pageWidth: number;
1125
+ /** Page height (px). */
1126
+ pageHeight: number;
1127
+ /** Content area height (px). */
1128
+ contentHeight: number;
1129
+ /** CSS font family applied to the content. */
1130
+ fontFamily?: FontFamily;
1131
+ /** Font size override (px). */
1132
+ fontSize?: number;
1133
+ /** Line spacing multiplier (slot mode). */
1134
+ lineSpacing?: number;
1135
+ /** Animate the page turn. */
1136
+ turning?: boolean;
1137
+ /** Header data for the right page. */
1138
+ rightHeader?: PageHeaderData;
1139
+ /** Header data for the left page. */
1140
+ leftHeader?: PageHeaderData;
1141
+ /** Image overlays on the current spread. */
1142
+ images?: MultiImageItem[];
1143
+ /** Force slot-mode rendering on both pages. */
1144
+ slotMode?: boolean;
1145
+ /** Custom renderer for per-page headers. */
1146
+ renderPageHeader?: (side: 'right' | 'left', header: PageHeaderData) => ReactNode;
1147
+ /** Custom indicator placed inside the spread (e.g. {@link MejiroPageIndicator}). */
1148
+ indicator?: ReactNode;
1149
+ /** Called when the previous-page zone is clicked. */
1150
+ onPrev?: () => void;
1151
+ /** Called when the next-page zone is clicked. */
1152
+ onNext?: () => void;
1153
+ /** Pointer-down on an image overlay. */
1154
+ onImagePointerDown?: (id: string, e: PointerEvent) => void;
1155
+ /** Pointer-down on an image resize handle. */
1156
+ onImageResizePointerDown?: (id: string, e: PointerEvent) => void;
1157
+ /** Image overlay close button. */
1158
+ onImageClose?: (id: string) => void;
1159
+ /**
1160
+ * Resolves a spread-local pixel coordinate to an in-chapter anchor.
1161
+ * Typically `(x, y) => layout.anchorAtCoord(spreadIdx, x, y)`. When provided
1162
+ * together with {@link MejiroSpreadProps.onSelectionChange}, the spread
1163
+ * enables pointer-drag selection on the page content area.
1164
+ */
1165
+ anchorAtCoord?: (x: number, y: number) => InChapterAnchor | null;
1166
+ /**
1167
+ * Selection rectangles to render as a highlight overlay. Compute via
1168
+ * {@link ChapterLayout.selectionRects}. Entries with `spreadIdx` other than
1169
+ * the current spread are ignored.
1170
+ */
1171
+ selectionRects?: readonly AnchorRect[];
1172
+ /**
1173
+ * Called when the user drags-selects text on the spread.
1174
+ * Called with `null` on a single-click (caret) where start equals end.
1175
+ */
1176
+ onSelectionChange?: (range: AnchorRange | null) => void;
1177
+ /**
1178
+ * Hide the left page and render only the right page (centered). Use this
1179
+ * for portrait viewports or when a reader explicitly opts into single-page
1180
+ * mode.
1181
+ */
1182
+ singlePage?: boolean;
1183
+ /**
1184
+ * Called when the user makes a quick swipe gesture on the spread.
1185
+ * `direction === 'next'` means the user swiped from right to left (the
1186
+ * natural direction in vertical-rl for advancing).
1187
+ */
1188
+ onSwipe?: (direction: 'next' | 'prev') => void;
1189
+ /** Called when the user taps the center area without swiping. */
1190
+ onSurfaceTap?: () => void;
1191
+ }
1192
+ /**
1193
+ * Renders a two-page spread with the book frame, page chrome, navigation
1194
+ * zones, and image overlays. Used inside `mejiro-reader-surface`.
1195
+ */
1196
+ declare function MejiroSpread({ spread, pageWidth, pageHeight, contentHeight, fontFamily, fontSize, lineSpacing, turning, rightHeader, leftHeader, images, renderPageHeader, indicator, onPrev, onNext, onImagePointerDown, onImageResizePointerDown, onImageClose, anchorAtCoord, selectionRects, onSelectionChange, singlePage, onSwipe, onSurfaceTap, }: MejiroSpreadProps): ReactNode;
1197
+
1198
+ /** Props for {@link MejiroStats}. */
1199
+ interface MejiroStatsProps {
1200
+ /** Chapter currently displayed. */
1201
+ chapter: EpubChapter | null;
1202
+ /** Total page count for the current layout. */
1203
+ totalPages: number;
1204
+ /** Most recent layout time in ms. */
1205
+ elapsedMs: number;
1206
+ /** Optional label for the current font (e.g. "Noto Serif JP 16px"). */
1207
+ fontLabel?: string;
1208
+ /** Show estimated reading time in the stats line. @defaultValue false */
1209
+ showReadingTime?: boolean;
1210
+ /** Characters-per-minute used for the reading-time estimate. @defaultValue 600 */
1211
+ cpm?: number;
1212
+ /** Locale used to format the reading-time label. @defaultValue 'ja' */
1213
+ readingTimeLocale?: 'ja' | 'en';
1214
+ }
1215
+ /** Compact reading stats line. */
1216
+ declare function MejiroStats({ chapter, totalPages, elapsedMs, fontLabel, showReadingTime, cpm, readingTimeLocale, }: MejiroStatsProps): ReactNode;
1217
+
1218
+ /** Props for {@link MejiroToc}. */
1219
+ interface MejiroTocProps {
1220
+ /** EPUB whose chapter list is rendered. */
1221
+ epub: EpubBook;
1222
+ /** Current reading anchor; the matching chapter is highlighted. */
1223
+ currentAnchor?: ReadingAnchor | null;
1224
+ /** Show a search input that filters chapter titles. @defaultValue false */
1225
+ searchable?: boolean;
1226
+ /** Heading displayed above the list. @defaultValue 'Contents' */
1227
+ title?: string;
1228
+ /** Placeholder shown in the search input. @defaultValue 'Search chapters…' */
1229
+ searchPlaceholder?: string;
1230
+ /** Called when the user picks a chapter. */
1231
+ onSelect?: (chapter: number) => void;
1232
+ }
1233
+ /**
1234
+ * Searchable, current-anchor-aware table of contents. Long-form replacement
1235
+ * for {@link MejiroChapterNav}. Supersedes the older `MejiroChapterNav`
1236
+ * for new code — the panel variant remains for backwards compatibility.
1237
+ */
1238
+ declare function MejiroToc({ epub, currentAnchor, searchable, title, searchPlaceholder, onSelect, }: MejiroTocProps): ReactNode;
1239
+
1240
+ /**
1241
+ * A user-authored annotation on a book — a half-open range of in-chapter
1242
+ * anchors plus optional metadata (color, note). Pair with the `annotations`
1243
+ * prop on {@link MejiroReader} to render highlights, or with `goToAnchor` to
1244
+ * implement bookmarks.
1245
+ */
1246
+ interface Annotation {
1247
+ /** Stable identifier. Auto-generated when omitted from {@link useAnnotations.add}. */
1248
+ id: string;
1249
+ /** Chapter the annotation lives in. */
1250
+ chapter: number;
1251
+ /** Range start (inclusive). */
1252
+ start: InChapterAnchor;
1253
+ /** Range end (exclusive). Equal to `start` for caret-style bookmarks. */
1254
+ end: InChapterAnchor;
1255
+ /** Free-form color identifier (e.g. `'yellow'`, `'#ffd166'`). */
1256
+ color?: string;
1257
+ /** Optional note text attached to the annotation. */
1258
+ note?: string;
1259
+ /** Unix epoch ms. Auto-set on {@link useAnnotations.add} when omitted. */
1260
+ createdAt?: number;
1261
+ }
1262
+ /** Minimal storage interface required by {@link useAnnotations}. */
1263
+ interface AnnotationsStorage {
1264
+ getItem(key: string): string | null;
1265
+ setItem(key: string, value: string): void;
1266
+ removeItem(key: string): void;
1267
+ }
1268
+ /** Options for {@link useAnnotations}. */
1269
+ interface UseAnnotationsOptions {
1270
+ /** Storage key — usually scoped per-book (e.g. `mejiro:annotations:${bookId}`). */
1271
+ key: string;
1272
+ /**
1273
+ * Storage backend. Defaults to `window.localStorage` when available.
1274
+ * SSR consumers can omit this and the hook will fall back to in-memory.
1275
+ */
1276
+ storage?: AnnotationsStorage;
1277
+ /** Throttle ms between writes. @defaultValue 250 */
1278
+ throttleMs?: number;
1279
+ /**
1280
+ * Called immediately after each mutation (`add` / `remove` / `update` /
1281
+ * `clear`) with the new annotation list. Use to mirror state to a server
1282
+ * — `storage` only covers the local persistence side. Not invoked on
1283
+ * initial hydration or when the `key` changes.
1284
+ */
1285
+ onChange?: (next: readonly Annotation[]) => void;
1286
+ }
1287
+ /** Return value of {@link useAnnotations}. */
1288
+ interface UseAnnotationsReturn {
1289
+ /** Currently saved annotations, sorted by chapter / paragraph / charIndex. */
1290
+ annotations: readonly Annotation[];
1291
+ /** Add a new annotation. `id` and `createdAt` are auto-filled when omitted. */
1292
+ add(input: Omit<Annotation, 'id' | 'createdAt'> & Partial<Pick<Annotation, 'id' | 'createdAt'>>): Annotation;
1293
+ /** Remove an annotation by id. */
1294
+ remove(id: string): void;
1295
+ /** Patch an annotation by id. Pass `undefined` on a field to leave it untouched. */
1296
+ update(id: string, patch: Partial<Omit<Annotation, 'id'>>): void;
1297
+ /** Remove all annotations. */
1298
+ clear(): void;
1299
+ }
1300
+ /**
1301
+ * Persistence helper for reader annotations. Returns the saved annotation list
1302
+ * plus add / update / remove helpers. Pair with the `annotations` prop on
1303
+ * {@link MejiroReader} to render the corresponding highlight rectangles.
92
1304
  *
93
- * @param layout - Current chapter layout (or `null` if not yet loaded).
94
- * @param spreadIdx - Current spread index.
95
- * @param onUpdate - Called with the updated {@link SpreadResult} after every reflow.
96
- * @param options - Default dimensions and position for the overlay.
1305
+ * ```tsx
1306
+ * const { annotations, add } = useAnnotations({ key: `mejiro:ann:${bookId}` });
1307
+ * const handle = useRef<MejiroReaderHandle>(null);
1308
+ *
1309
+ * // Save a highlight at the current visible range:
1310
+ * const range = handle.current?.getVisibleRange();
1311
+ * if (range) add({ chapter: 0, start: range.start, end: range.end, color: 'yellow' });
1312
+ *
1313
+ * <MejiroReader epub={book} annotations={annotations} ref={handle} />
1314
+ * ```
1315
+ */
1316
+ declare function useAnnotations(options: UseAnnotationsOptions): UseAnnotationsReturn;
1317
+
1318
+ /** Options for {@link useChapterLayout}. */
1319
+ interface UseChapterLayoutOptions {
1320
+ /** Observe the surface element for size changes and re-flow. @defaultValue true */
1321
+ enableResize?: boolean;
1322
+ /** Debounce window (ms) applied to size-triggered re-flows. @defaultValue 120 */
1323
+ resizeDebounce?: number;
1324
+ /**
1325
+ * Resolver for page-geometry overrides forwarded to
1326
+ * {@link MejiroBook.computePageSize} — e.g. to shrink the reserved
1327
+ * `gutterOffset` / `headerOffset` so the pages fill their frame. Called on
1328
+ * every (re)layout so it may return a value that changes over time.
1329
+ */
1330
+ pageGeometry?: () => ComputePageSizeOptions | undefined;
1331
+ /**
1332
+ * Capture the current reading position from the outgoing layout, just before
1333
+ * a **reflow** (non-blank) re-layout replaces it. The returned anchor is
1334
+ * exposed via {@link UseChapterLayoutReturn.pendingRestore} and should be
1335
+ * applied once the new layout commits. Return `null` to skip preservation.
1336
+ * Never called for blank (content-change) re-layouts.
1337
+ *
1338
+ * A reflow yields a brand-new {@link ChapterLayout} object, which resets any
1339
+ * downstream spread index to 0; capturing here and restoring after keeps the
1340
+ * reader on the same passage across resizes and font / option changes.
1341
+ */
1342
+ capturePosition?: (layout: ChapterLayout) => InChapterAnchor | null;
1343
+ }
1344
+ /** Options for {@link UseChapterLayoutReturn.recompute}. */
1345
+ interface RecomputeOptions {
1346
+ /**
1347
+ * Blank the current layout while the new one is computed. Use for content
1348
+ * changes (chapter / book swaps); skip for size or option changes so the
1349
+ * previous spread stays visible until the re-flow completes (no flicker).
1350
+ * @defaultValue true
1351
+ */
1352
+ blank?: boolean;
1353
+ }
1354
+ /** Page dimensions returned by {@link MejiroBook.computePageSize}. */
1355
+ interface PageDimensions {
1356
+ pageWidth: number;
1357
+ pageHeight: number;
1358
+ contentHeight: number;
1359
+ }
1360
+ /** Return value of {@link useChapterLayout}. */
1361
+ interface UseChapterLayoutReturn {
1362
+ /** Current layout, or `null` before the first computation. */
1363
+ layout: ChapterLayout | null;
1364
+ /** Page width in pixels. */
1365
+ pageWidth: number;
1366
+ /** Page height in pixels. */
1367
+ pageHeight: number;
1368
+ /** Content height in pixels. */
1369
+ contentHeight: number;
1370
+ /** Most recent layout time (ms). */
1371
+ elapsedMs: number;
1372
+ /** Force a fresh layout computation. */
1373
+ recompute: (opts?: RecomputeOptions) => Promise<void>;
1374
+ /**
1375
+ * Anchor captured before the most recent reflow re-layout, awaiting
1376
+ * restoration into the new {@link layout}. Consume it in a layout effect keyed
1377
+ * on `layout` (after any index reset) and clear it back to `null`.
1378
+ */
1379
+ pendingRestore: RefObject<InChapterAnchor | null>;
1380
+ }
1381
+ /**
1382
+ * React hook that lays out the currently-selected chapter and re-flows when the
1383
+ * surface resizes.
1384
+ *
1385
+ * Re-layouts whenever `book`, `epub`, or `chapterIndex` change, against the
1386
+ * dimensions of `surface`. The `surface` element is observed with a
1387
+ * {@link ResizeObserver}: the first observation runs immediately (so a reader
1388
+ * mounted before its container had a final box is still sized correctly on
1389
+ * first paint), and later size changes trigger a debounced **full re-layout**.
1390
+ *
1391
+ * A full re-layout — rather than a `ChapterLayout.resize()` fast-path — is used
1392
+ * for size changes on purpose: the fast-path only stretches `pageWidth` and does
1393
+ * not re-paginate, leaving sparse, half-empty pages after a non-trivial size
1394
+ * delta. Because a full re-layout yields a new {@link ChapterLayout} object
1395
+ * (resetting any downstream spread index to 0), the reading position is
1396
+ * preserved across reflows via {@link UseChapterLayoutOptions.capturePosition}
1397
+ * and {@link UseChapterLayoutReturn.pendingRestore}.
1398
+ *
1399
+ * @param book - The book instance to lay out with.
1400
+ * @param epub - The current parsed EPUB.
1401
+ * @param chapterIndex - Zero-based chapter index to lay out.
1402
+ * @param surface - DOM ref for the reading surface used for page sizing.
1403
+ * @param options - Behavior overrides.
1404
+ */
1405
+ declare function useChapterLayout(book: MejiroBook, epub: EpubBook | null, chapterIndex: number, surface: RefObject<HTMLElement | null>, options?: UseChapterLayoutOptions): UseChapterLayoutReturn;
1406
+
1407
+ interface EditableEpubSelection {
1408
+ chapter: number;
1409
+ paragraph: number;
1410
+ }
1411
+ interface UseEditableEpubOptions {
1412
+ /** URL fetched and loaded on mount. */
1413
+ defaultUrl?: string;
1414
+ /** Called after a successful load. */
1415
+ onLoad?: (editor: EditableEpub) => void;
1416
+ /** Called when a load fails. */
1417
+ onError?: (error: Error) => void;
1418
+ /** Called after export completes. */
1419
+ onExport?: (buffer: ArrayBuffer) => void;
1420
+ }
1421
+ interface UseEditableEpubReturn {
1422
+ editor: EditableEpub | null;
1423
+ book: EditableEpubBook | null;
1424
+ previewBook: EditableEpubBook | null;
1425
+ loading: boolean;
1426
+ exporting: boolean;
1427
+ error: Error | null;
1428
+ revision: number;
1429
+ history: {
1430
+ canUndo: boolean;
1431
+ canRedo: boolean;
1432
+ depth: number;
1433
+ redoDepth: number;
1434
+ } | null;
1435
+ selection: EditableEpubSelection;
1436
+ selectedParagraph: AnnotatedParagraph | null;
1437
+ setSelection: (selection: EditableEpubSelection) => void;
1438
+ loadBuffer: (buffer: ArrayBuffer) => Promise<EditableEpub | null>;
1439
+ loadFile: (file: File) => Promise<EditableEpub | null>;
1440
+ loadUrl: (url: string) => Promise<EditableEpub | null>;
1441
+ updateParagraph: (text: string, inlineAnnotations?: readonly InlineAnnotation[]) => void;
1442
+ setInlineAnnotations: (inlineAnnotations: readonly InlineAnnotation[]) => void;
1443
+ addImage: (image: AddImageInput | EditableEpubImage) => void;
1444
+ undo: () => boolean;
1445
+ redo: () => boolean;
1446
+ exportEpub: (options?: EpubExportOptions) => Promise<ArrayBuffer | null>;
1447
+ }
1448
+ /** Headless editable-EPUB state for custom proofreading/editor UIs. */
1449
+ declare function useEditableEpub(options?: UseEditableEpubOptions): UseEditableEpubReturn;
1450
+
1451
+ /** Options for {@link useEpub}. */
1452
+ interface UseEpubOptions {
1453
+ /** URL fetched on mount. A non-OK response is treated as "no default". */
1454
+ defaultUrl?: string;
1455
+ /** Called after a successful load. */
1456
+ onLoad?: (book: EpubBook) => void;
1457
+ /** Called when a load fails. Non-OK URL responses are still treated as "no default". */
1458
+ onError?: (error: Error) => void;
1459
+ /**
1460
+ * Extra options merged into the `fetch` call when loading by URL. Useful
1461
+ * for sending bearer tokens or cookies (`credentials: 'include'`).
1462
+ */
1463
+ fetchOptions?: RequestInit;
1464
+ /**
1465
+ * Custom EPUB fetcher used in place of the global `fetch`. Returns the
1466
+ * raw `ArrayBuffer`. Overrides {@link fetchOptions} when set — common
1467
+ * for hosts that already own an auth-aware HTTP client.
1468
+ */
1469
+ fetchEpub?: (url: string) => Promise<ArrayBuffer>;
1470
+ }
1471
+ /** Return value of {@link useEpub}. */
1472
+ interface UseEpubReturn {
1473
+ /** Current parsed EPUB, or `null` before any load. */
1474
+ epub: EpubBook | null;
1475
+ /** Whether a load is in progress. */
1476
+ loading: boolean;
1477
+ /** Last load error, if any. */
1478
+ error: Error | null;
1479
+ /** Parse from an in-memory buffer. */
1480
+ loadBuffer: (buffer: ArrayBuffer) => Promise<EpubBook | null>;
1481
+ /** Parse from a {@link File}. */
1482
+ loadFile: (file: File) => Promise<EpubBook | null>;
1483
+ /** Fetch a URL and parse the response. */
1484
+ loadUrl: (url: string) => Promise<EpubBook | null>;
1485
+ /** Replace the current EPUB without going through the parser. */
1486
+ setEpub: (book: EpubBook | null) => void;
1487
+ }
1488
+ /**
1489
+ * React hook that parses EPUB files and exposes loading/error state plus
1490
+ * convenience loaders for buffers, files, and URLs.
1491
+ */
1492
+ declare function useEpub(options?: UseEpubOptions): UseEpubReturn;
1493
+
1494
+ interface EpubProjectChapterDraft {
1495
+ id: string;
1496
+ title: string;
1497
+ body: string;
1498
+ }
1499
+ interface UseEpubProjectOptions {
1500
+ metadata?: Partial<EpubProjectMetadata>;
1501
+ chapters?: EpubProjectChapterDraft[];
1502
+ debounceMs?: number;
1503
+ /**
1504
+ * Resolves URL-only project assets into bytes when the preview or export
1505
+ * pipeline materializes them. Forwarded to `project.export()`. Useful when
1506
+ * cover / illustration assets are registered as remote URLs and you want
1507
+ * the host (not the client) to provide auth headers.
1508
+ */
1509
+ assetResolver?: AssetResolver;
1510
+ onPreview?: (book: EpubBook) => void;
1511
+ onExport?: (buffer: ArrayBuffer) => void;
1512
+ }
1513
+ interface UseEpubProjectReturn {
1514
+ metadata: EpubProjectMetadata;
1515
+ chapters: EpubProjectChapterDraft[];
1516
+ selectedChapter: number;
1517
+ currentChapter: EpubProjectChapterDraft | null;
1518
+ previewBook: EpubBook | null;
1519
+ previewError: Error | null;
1520
+ previewing: boolean;
1521
+ setMetadata: (patch: Partial<EpubProjectMetadata>) => void;
1522
+ setChapters: (chapters: EpubProjectChapterDraft[]) => void;
1523
+ setSelectedChapter: (index: number) => void;
1524
+ patchChapter: (index: number, patch: Partial<EpubProjectChapterDraft>) => void;
1525
+ addChapter: (chapter?: Partial<EpubProjectChapterDraft>) => void;
1526
+ removeChapter: (index?: number) => void;
1527
+ reorderChapters: (from: number, to: number) => void;
1528
+ buildProject: () => EpubProject;
1529
+ exportEpub: () => Promise<ArrayBuffer>;
1530
+ }
1531
+ /** Headless manuscript-to-EPUB project state for custom authoring UIs. */
1532
+ declare function useEpubProject(options?: UseEpubProjectOptions): UseEpubProjectReturn;
1533
+
1534
+ /** Options for {@link useManuscriptDraft}. */
1535
+ interface UseManuscriptDraftOptions {
1536
+ /** Initial chapters. Defaults to a single empty chapter. */
1537
+ initialChapters?: ManuscriptEditorChapter[];
1538
+ /**
1539
+ * Called when the draft changes (debounced). Use to persist to
1540
+ * localStorage, IndexedDB, or upload to a server.
1541
+ */
1542
+ onAutosave?: (chapters: ManuscriptEditorChapter[]) => void | Promise<void>;
1543
+ /** Debounce delay in milliseconds. @defaultValue 800 */
1544
+ autosaveDelay?: number;
1545
+ }
1546
+ /** Return value of {@link useManuscriptDraft}. */
1547
+ interface UseManuscriptDraftReturn {
1548
+ chapters: ManuscriptEditorChapter[];
1549
+ /** Index of the chapter currently being edited. */
1550
+ selected: number;
1551
+ setSelected(index: number): void;
1552
+ setChapters(chapters: ManuscriptEditorChapter[]): void;
1553
+ patchChapter(index: number, patch: Partial<ManuscriptEditorChapter>): void;
1554
+ addChapter(chapter?: Partial<ManuscriptEditorChapter>): void;
1555
+ removeChapter(index: number): void;
1556
+ reorderChapters(from: number, to: number): void;
1557
+ }
1558
+ /**
1559
+ * Reactive store for manuscript drafts.
1560
+ *
1561
+ * Wraps the chapter array with helpers for adding, removing, reordering, and
1562
+ * patching individual chapters, plus a debounced autosave hook that fires
1563
+ * `onAutosave` whenever the chapter list settles.
1564
+ */
1565
+ declare function useManuscriptDraft(options?: UseManuscriptDraftOptions): UseManuscriptDraftReturn;
1566
+
1567
+ /** Options for {@link useManuscriptLayout}. */
1568
+ interface UseManuscriptLayoutOptions {
1569
+ /** Manuscript notation dialect. @defaultValue `'mejiro'` */
1570
+ dialect?: ManuscriptDialect;
1571
+ /** Listen for `window.resize` and re-flow on resize. @defaultValue true */
1572
+ enableResize?: boolean;
1573
+ /** Debounce window (ms) for resize re-flows. @defaultValue 120 */
1574
+ resizeDebounce?: number;
1575
+ }
1576
+ /** Page dimensions returned by {@link MejiroBook.computePageSize}. */
1577
+ interface ManuscriptPageDimensions {
1578
+ pageWidth: number;
1579
+ pageHeight: number;
1580
+ contentHeight: number;
1581
+ }
1582
+ /** Return value of {@link useManuscriptLayout}. */
1583
+ interface UseManuscriptLayoutReturn {
1584
+ /** Current layout, or `null` before the first computation. */
1585
+ layout: ChapterLayout | null;
1586
+ /** Page width in pixels. */
1587
+ pageWidth: number;
1588
+ /** Page height in pixels. */
1589
+ pageHeight: number;
1590
+ /** Content height in pixels. */
1591
+ contentHeight: number;
1592
+ /** Most recent layout time (ms). */
1593
+ elapsedMs: number;
1594
+ /** Force a fresh layout computation. */
1595
+ recompute: () => Promise<void>;
1596
+ }
1597
+ /**
1598
+ * React hook that lays out a single manuscript chapter directly, skipping the
1599
+ * EPUB ZIP round-trip used by {@link useChapterLayout}.
1600
+ *
1601
+ * Intended for live preview in custom manuscript editors: pair with
1602
+ * {@link useManuscriptDraft} for the chapter array and feed the resulting
1603
+ * {@link ChapterLayout} into {@link MejiroSpread} or {@link MejiroScrollView}.
1604
+ *
1605
+ * Re-layouts whenever `chapter`, `dialect`, or `book` change. On `window.resize`,
1606
+ * page dimensions are recomputed and {@link ChapterLayout.resize} is called
1607
+ * instead of a full re-layout.
1608
+ */
1609
+ declare function useManuscriptLayout(book: MejiroBook, chapter: ManuscriptChapter | null, surface: RefObject<HTMLElement | null>, options?: UseManuscriptLayoutOptions): UseManuscriptLayoutReturn;
1610
+
1611
+ /** Return value of {@link useMejiroBook}. */
1612
+ interface UseMejiroBookReturn {
1613
+ /** The managed {@link MejiroBook} instance. Stable across renders. */
1614
+ book: MejiroBook;
1615
+ /** Reactive snapshot of the current options. */
1616
+ options: Readonly<BookOptions>;
1617
+ /**
1618
+ * Update options on the underlying book and the snapshot. The returned
1619
+ * promise resolves once {@link MejiroBook.setOptions} has propagated
1620
+ * font / size changes (re-measurement runs lazily inside that call).
1621
+ */
1622
+ setOptions: (next: Partial<BookOptions>) => Promise<void>;
1623
+ }
1624
+ /**
1625
+ * React hook that owns a {@link MejiroBook} instance and exposes its
1626
+ * options as React state. The book is created once on mount; subsequent
1627
+ * option changes must go through {@link UseMejiroBookReturn.setOptions}
1628
+ * (e.g. via the `MejiroReader` imperative handle).
1629
+ */
1630
+ declare function useMejiroBook(initial: BookOptions): UseMejiroBookReturn;
1631
+
1632
+ /**
1633
+ * Persisted reading position. Anchor-shaped — pair with
1634
+ * {@link MejiroReaderHandle.goToAnchor} to restore the user's exact location
1635
+ * even after a reflow that invalidates spread indices.
1636
+ */
1637
+ type ReadingPositionValue = ReadingAnchor;
1638
+ /** Minimal storage interface required by {@link useReadingPosition}. */
1639
+ interface ReadingPositionStorage {
1640
+ getItem(key: string): string | null;
1641
+ setItem(key: string, value: string): void;
1642
+ removeItem(key: string): void;
1643
+ }
1644
+ /** Options for {@link useReadingPosition}. */
1645
+ interface UseReadingPositionOptions {
1646
+ /** Storage key — usually scoped per-book (e.g. `mejiro:position:${bookId}`). */
1647
+ key: string;
1648
+ /**
1649
+ * Storage backend. Defaults to `window.localStorage` when available.
1650
+ * SSR consumers can omit this and the hook will fall back to in-memory.
1651
+ */
1652
+ storage?: ReadingPositionStorage;
1653
+ /** Throttle ms between writes. @defaultValue 250 */
1654
+ throttleMs?: number;
1655
+ /**
1656
+ * Called immediately after `save()` (with the new anchor) or `clear()`
1657
+ * (with `null`). Use to mirror the position to a server alongside the
1658
+ * local `storage`. Not invoked on initial hydration or `key` changes.
1659
+ */
1660
+ onChange?: (next: ReadingPositionValue | null) => void;
1661
+ }
1662
+ /** Return value of {@link useReadingPosition}. */
1663
+ interface UseReadingPositionReturn {
1664
+ /** Most recently persisted position, or `null` if none. */
1665
+ position: ReadingPositionValue | null;
1666
+ /** Persist a new position (throttled). */
1667
+ save(next: ReadingPositionValue): void;
1668
+ /** Remove the persisted position. */
1669
+ clear(): void;
1670
+ }
1671
+ /**
1672
+ * Persistence helper for reader state. Returns the saved anchor and a
1673
+ * throttled saver. Pair with {@link MejiroReaderHandle.goToAnchor} and
1674
+ * {@link MejiroReaderHandle.subscribe} for exact reflow-safe restore.
97
1675
  *
98
- * @example
99
1676
  * ```tsx
100
- * const { imageRect, hasImage, toggleImage, onOverlayPointerDown, onResizePointerDown } =
101
- * useImageOverlay(layout, spreadIdx, setSpread);
1677
+ * const { position, save } = useReadingPosition({ key: `mejiro:${bookId}` });
1678
+ * const reader = useRef<MejiroReaderHandle>(null);
1679
+ *
1680
+ * useEffect(() => {
1681
+ * if (position) reader.current?.goToAnchor(position);
1682
+ * }, [position]);
1683
+ *
1684
+ * useEffect(() => {
1685
+ * const off = reader.current?.subscribe('spreadChanged', () => {
1686
+ * const anchor = reader.current?.getAnchor();
1687
+ * if (anchor) save(anchor);
1688
+ * });
1689
+ * return off;
1690
+ * }, [save]);
102
1691
  * ```
1692
+ *
1693
+ * Legacy `{ chapter, spreadIdx }` JSON from v0.4 is migrated automatically:
1694
+ * the chapter is preserved, `spreadIdx` is dropped (it does not survive
1695
+ * reflow), and the position is treated as the start of the chapter.
103
1696
  */
104
- declare function useImageOverlay(layout: ChapterLayout | null, spreadIdx: number, onUpdate: (spread: SpreadResult) => void, options?: UseImageOverlayOptions): UseImageOverlayReturn;
1697
+ declare function useReadingPosition(options: UseReadingPositionOptions): UseReadingPositionReturn;
1698
+
1699
+ /** Options for {@link useSpread}. */
1700
+ interface UseSpreadOptions {
1701
+ /** Enable ArrowLeft/ArrowRight navigation. @defaultValue true */
1702
+ enableKeyboard?: boolean;
1703
+ /** Page-turn animation duration in ms. @defaultValue 180 */
1704
+ turnDuration?: number;
1705
+ /** Called when the spread index changes (after the turn animation midpoint). */
1706
+ onChange?: (spreadIdx: number) => void;
1707
+ }
1708
+ /** Return value of {@link useSpread}. */
1709
+ interface UseSpreadReturn {
1710
+ /** Current spread index. */
1711
+ spreadIdx: number;
1712
+ /** Current spread result, or `null` until the layout is ready. */
1713
+ spread: SpreadResult | null;
1714
+ /** Total number of pages. */
1715
+ totalPages: number;
1716
+ /** Total number of two-page spreads. */
1717
+ totalSpreads: number;
1718
+ /** Whether the turn animation is currently in flight. */
1719
+ turning: boolean;
1720
+ /** Advance one spread forward. */
1721
+ next: () => void;
1722
+ /** Go back one spread. */
1723
+ prev: () => void;
1724
+ /** Jump to an arbitrary spread index (clamped). */
1725
+ goTo: (index: number) => void;
1726
+ /**
1727
+ * Set the spread index immediately, with no page-turn animation, clamped to
1728
+ * `[0, totalSpreads − 1]`. Use to restore a reading position after a reflow
1729
+ * re-layout (where an animated {@link goTo} would briefly flash spread 0).
1730
+ */
1731
+ setSpread: (index: number) => void;
1732
+ /** Manually refresh `spread` from `layout` at the current index. */
1733
+ refresh: () => void;
1734
+ }
1735
+ /**
1736
+ * React hook that tracks the current spread index for a chapter layout
1737
+ * and exposes navigation helpers with an optional page-turn animation.
1738
+ */
1739
+ declare function useSpread(layout: ChapterLayout | null, options?: UseSpreadOptions): UseSpreadReturn;
105
1740
 
106
- export { type ImageRect, MejiroPage, type MejiroPageProps, MejiroPageView, type MejiroPageViewProps, type UseImageOverlayOptions, type UseImageOverlayReturn, useImageOverlay };
1741
+ export { type Annotation, type AnnotationsStorage, type EditableEpubSelection, type EditableSettings, type EpubProjectChapterDraft, type FontChoice, type ImageRect, type ManuscriptEditorChapter, type ManuscriptPageDimensions, type ManuscriptPreviewProps, MejiroChapterNav, type MejiroChapterNavMode, type MejiroChapterNavProps, type MejiroChapterNavVariant, MejiroDropZone, type MejiroDropZoneProps, MejiroEditor, type MejiroEditorProps, type MejiroExportPolicy, MejiroI18nProvider, MejiroImageOverlay, type MejiroImageOverlayProps, type MejiroLocale, MejiroManuscriptEditor, type MejiroManuscriptEditorProps, type MejiroMessages, MejiroNotationHighlighter, type MejiroNotationHighlighterProps, MejiroPage, MejiroPageIndicator, type MejiroPageIndicatorProps, type MejiroPageProps, MejiroPageView, type MejiroPageViewProps, MejiroReader, type MejiroReaderControlledProps, type MejiroReaderEventMap, type MejiroReaderFileProps, type MejiroReaderFit, type MejiroReaderHandle, type MejiroReaderMode, type MejiroReaderProps, type MejiroReaderSettingsSlot, type MejiroReaderUrlProps, MejiroScrollView, type MejiroScrollViewProps, MejiroSelectionLayer, type MejiroSelectionLayerProps, MejiroSettingsPanel, type MejiroSettingsPanelProps, MejiroShelf, type MejiroShelfProps, MejiroSpread, type MejiroSpreadMode, type MejiroSpreadProps, MejiroStats, type MejiroStatsProps, type MejiroTheme, type MejiroThemeName, MejiroToc, type MejiroTocProps, type MultiImageItem, type PageDimensions, type PageHeaderData, type PageNumberDisplay, type ReadingPosition, type ReadingPositionStorage, type ReadingPositionValue, type UseAnnotationsOptions, type UseAnnotationsReturn, type UseChapterLayoutOptions, type UseChapterLayoutReturn, type UseEditableEpubOptions, type UseEditableEpubReturn, type UseEpubOptions, type UseEpubProjectOptions, type UseEpubProjectReturn, type UseEpubReturn, type UseI18nOptions, type UseImageOverlayOptions, type UseImageOverlayReturn, type UseLibraryOptions, type UseLibraryReturn, type UseManuscriptDraftOptions, type UseManuscriptDraftReturn, type UseManuscriptLayoutOptions, type UseManuscriptLayoutReturn, type UseMejiroBookReturn, type UseMultiImageOverlayOptions, type UseMultiImageOverlayReturn, type UseReadingPositionOptions, type UseReadingPositionReturn, type UseSpreadOptions, type UseSpreadReturn, type VolumeInfo, enMessages, format, jaMessages, resolveMessages, useAnnotations, useChapterLayout, useEditableEpub, useEpub, useEpubProject, useI18n, useImageOverlay, useLibrary, useManuscriptDraft, useManuscriptLayout, useMejiroBook, useMultiImageOverlay, useReadingPosition, useSpread };