@libraz/mejiro-react 0.10.0 → 0.11.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 +1 -0
  2. package/dist/index.d.ts +1187 -1004
  3. package/dist/index.js +2009 -1637
  4. package/package.json +2 -2
package/dist/index.d.ts CHANGED
@@ -1,20 +1,28 @@
1
- import { MejiroLocale, MejiroMessages, EditableSettings, FontChoice, ImageOverlayRect, PageHeaderData, MejiroStorage, Annotation, ReadingPositionValue } from '@libraz/mejiro';
2
- export { Annotation, EditableSettings, FontChoice, ImageOverlayRect, MejiroLocale, MejiroMessages, PageHeaderData, ReadingPositionValue, enMessages, jaMessages } from '@libraz/mejiro';
1
+ import { MejiroLocale, MejiroMessages, EditableSettings, FontChoice, MejiroStorage, Annotation, MejiroError, ImageOverlayRect, PageHeaderData, BinaryInput, ManuscriptChapter as ManuscriptChapter$1, InlineAnnotation, ReadingAnchor as ReadingAnchor$1 } from '@libraz/mejiro';
2
+ export { Annotation, EditableSettings, FontChoice, ImageOverlayRect, ManuscriptChapter, MejiroError, MejiroErrorCode, MejiroLocale, MejiroMessages, MejiroStorage, MejiroWarning, MejiroWarningCode, PageHeaderData, ReadingAnchor, enMessages, formatMessage, jaMessages, resolveMessages } from '@libraz/mejiro';
3
3
  import * as react from 'react';
4
- import { ReactNode, CSSProperties, PointerEvent, TextareaHTMLAttributes, MutableRefObject, RefObject } from 'react';
5
- import { EpubBook, AssetResolver, EpubParseLimits, EditableEpub, ManuscriptDialect, EpubChapter, EditableEpubBook, EditableEpubSelection, AnnotatedParagraph, AddImageInput, EditableEpubImage, EpubExportOptions, EpubProjectMetadata, EpubProjectAsset, EpubProject } from '@libraz/mejiro/epub';
4
+ import { ReactNode, ComponentPropsWithoutRef, CSSProperties, PointerEvent, TextareaHTMLAttributes, RefAttributes, RefObject, MutableRefObject } from 'react';
5
+ import { EpubBook, EpubParseLimits, ManuscriptDialect, AssetResolver, EditableEpub, EpubChapter, EditableEpubBook, EditHistoryState, EditableEpubSelection, EditableParagraphBlock, ParagraphPatch, AddImageInput, EditableImageBlock, EditableEpubExportOptions, EpubProjectMetadata, CoverInput, EpubProjectAsset, EpubProject } from '@libraz/mejiro/epub';
6
6
  export { EditableEpubSelection } from '@libraz/mejiro/epub';
7
- import { ChapterLayout, SpreadResult, BookOptions, MejiroBookOptions, ComputePageSizeOptions, InChapterAnchor, ReadingAnchor, ManuscriptChapter, PageResult, AnchorRect, AnchorRange, MejiroBook } from '@libraz/mejiro/book';
7
+ import { SpreadResult, ChapterLayout, InChapterAnchor, AnchorRect, SpreadPoint, AnchorRange, BookOptions, FitPageSizeOptions, ReadingAnchor, BookOptionsPatch, ManuscriptChapter, PageResult, MejiroBook } from '@libraz/mejiro/book';
8
8
  import { PageNumberDisplay, RenderPage } from '@libraz/mejiro/render';
9
9
  export { PageNumberDisplay } from '@libraz/mejiro/render';
10
- import { FontFamily, InlineAnnotation } from '@libraz/mejiro/browser';
10
+ import { FontFamily } from '@libraz/mejiro/browser';
11
11
 
12
- /** Provider that scopes a message catalog to its descendants. */
13
- declare function MejiroI18nProvider({ locale, messages, children, }: {
12
+ /** Props for {@link MejiroI18nProvider}. */
13
+ interface MejiroI18nProviderProps {
14
+ /**
15
+ * Built-in locale to use as the base catalog. Without it the catalog of the
16
+ * enclosing provider is the base, or the English catalog at the top level.
17
+ */
14
18
  locale?: MejiroLocale;
19
+ /** Overrides merged on top of the base catalog. */
15
20
  messages?: Partial<MejiroMessages>;
21
+ /** Subtree the catalog applies to. */
16
22
  children: ReactNode;
17
- }): ReactNode;
23
+ }
24
+ /** Provider that scopes a message catalog to its descendants. */
25
+ declare function MejiroI18nProvider({ locale, messages, children, }: MejiroI18nProviderProps): ReactNode;
18
26
  /** Options for {@link useI18n}. */
19
27
  interface UseI18nOptions {
20
28
  /**
@@ -31,45 +39,45 @@ interface UseI18nOptions {
31
39
  * passing `locale` / `messages` resolves a catalog without context.
32
40
  */
33
41
  declare function useI18n(options?: UseI18nOptions): MejiroMessages;
34
- /** Build a catalog without invoking React (useful in plain modules). */
35
- declare function resolveMessages(locale: MejiroLocale | undefined, overrides: Partial<MejiroMessages> | undefined): MejiroMessages;
36
- /** Replace `{name}` placeholders in a template. */
37
- declare function format(template: string, vars: Record<string, string | number>): string;
38
42
 
39
43
  /**
40
44
  * Layout of {@link MejiroChapterNav}: `'select'` is a compact dropdown,
41
45
  * `'panel'` a chapter list with titles, headings and a text preview.
42
46
  */
43
47
  type MejiroChapterNavVariant = 'select' | 'panel';
44
- /** Props for {@link MejiroChapterNav}. */
45
- interface MejiroChapterNavProps {
48
+ /** Props for {@link MejiroChapterNav}; other `div` attributes go to the root element. */
49
+ interface MejiroChapterNavProps extends ComponentPropsWithoutRef<'div'> {
46
50
  /** EPUB to list chapters from. */
47
51
  epub: EpubBook;
48
- /** Current chapter index. */
49
- chapter: number;
50
- /** Called when a new chapter is picked. */
51
- onChange: (chapter: number) => void;
52
+ /** Zero-based index of the chapter shown as current. */
53
+ chapterIndex: number;
54
+ /** Called with the zero-based index of the chapter the user picks. */
55
+ onChapterIndexChange: (chapterIndex: number) => void;
52
56
  /** Visual treatment for the chapter list. @defaultValue 'select' */
53
57
  variant?: MejiroChapterNavVariant;
54
58
  }
55
59
  /**
56
- * Chapter selector for an {@link EpubBook}.
57
- *
58
- * @deprecated Prefer {@link MejiroToc} for new code. `MejiroToc` adds
59
- * search and current-anchor highlighting and renders the same data with a
60
- * more accessible markup. `MejiroChapterNav` remains supported for the
61
- * `<select>` variant and existing `MejiroReader` integration.
60
+ * Chapter picker for an {@link EpubBook}: a compact `<select>`, or a panel
61
+ * listing each chapter with its sub-headings and a text preview. For a
62
+ * searchable table of contents that follows the reading anchor, use
63
+ * {@link MejiroToc}.
62
64
  */
63
- declare function MejiroChapterNav({ epub, chapter, onChange, variant, }: MejiroChapterNavProps): ReactNode;
65
+ declare const MejiroChapterNav: react.ForwardRefExoticComponent<MejiroChapterNavProps & react.RefAttributes<HTMLElement>>;
64
66
 
65
- /** Props for {@link MejiroDropZone}. */
66
- interface MejiroDropZoneProps {
67
+ /** Props for {@link MejiroDropZone}; other `button` attributes go to the root element. */
68
+ interface MejiroDropZoneProps extends ComponentPropsWithoutRef<'button'> {
67
69
  /** File `accept` filter for the picker. @defaultValue '.epub' */
68
70
  accept?: string;
69
71
  /** Validator. Defaults to the `accept` filter, with extensions matched case-insensitively. */
70
72
  validateFile?: (file: File) => boolean;
71
73
  /** Called with the selected/dropped file. */
72
74
  onFile: (file: File) => void;
75
+ /**
76
+ * Called with a selected or dropped file that is not accepted: `'type'` when
77
+ * it fails the `accept` filter, `'validation'` when
78
+ * {@link MejiroDropZoneProps.validateFile} returns `false`.
79
+ */
80
+ onReject?: (file: File, reason: 'type' | 'validation') => void;
73
81
  /** Custom body content (replaces default icon/text). */
74
82
  children?: ReactNode;
75
83
  /** Additional class name for the root element. */
@@ -82,18 +90,21 @@ interface MejiroDropZoneProps {
82
90
  * picker. The root is a real button, so it is reachable with Tab, announced as
83
91
  * a control by screen readers, and opens the picker on Enter / Space.
84
92
  */
85
- declare function MejiroDropZone({ accept, validateFile, onFile, children, className, style, }: MejiroDropZoneProps): ReactNode;
93
+ declare const MejiroDropZone: react.ForwardRefExoticComponent<MejiroDropZoneProps & react.RefAttributes<HTMLButtonElement>>;
86
94
 
87
- /** Props for {@link MejiroSettingsPanel}. */
88
- interface MejiroSettingsPanelProps {
95
+ /** Props for {@link MejiroSettingsPanel}; other `div` attributes go to the root element. */
96
+ interface MejiroSettingsPanelProps extends ComponentPropsWithoutRef<'div'> {
89
97
  /** Whether the panel is expanded. */
90
98
  open: boolean;
91
99
  /** Current editable settings. */
92
100
  settings: EditableSettings;
93
- /** Called when a setting changes. */
94
- onChange: (next: EditableSettings) => void;
101
+ /**
102
+ * Called when the user changes a setting, with the whole settings value:
103
+ * {@link MejiroSettingsPanelProps.settings} with that one field replaced.
104
+ */
105
+ onSettingsChange: (next: EditableSettings) => void;
95
106
  /** Font choices. */
96
- fonts?: FontChoice[];
107
+ fonts?: readonly FontChoice[];
97
108
  /** Min font size. @defaultValue 10 */
98
109
  minFontSize?: number;
99
110
  /** Max font size. @defaultValue 48 */
@@ -102,181 +113,427 @@ interface MejiroSettingsPanelProps {
102
113
  /**
103
114
  * Reader settings panel: font, size, kinsoku mode, hanging punctuation, line spacing.
104
115
  */
105
- declare function MejiroSettingsPanel({ open, settings, onChange, fonts, minFontSize, maxFontSize, }: MejiroSettingsPanelProps): ReactNode;
116
+ declare const MejiroSettingsPanel: react.ForwardRefExoticComponent<MejiroSettingsPanelProps & react.RefAttributes<HTMLDivElement>>;
106
117
 
107
- /** Props for {@link MejiroEditor}. */
108
- interface MejiroEditorProps {
109
- /** URL fetched and loaded on mount. */
110
- epubUrl?: string;
111
- /** Font choices passed to the preview reader. */
112
- fonts?: FontChoice[];
113
- /**
114
- * Allow editing paragraph text (the "Proofread" section). When `false` and
115
- * `enableRuby` is on, the paragraph text stays visible read-only so a ruby
116
- * base range can still be selected.
117
- * @defaultValue true
118
- */
119
- enableProofread?: boolean;
120
- /**
121
- * Allow editing ruby annotations (the "Ruby" section).
122
- * @defaultValue true
123
- */
124
- enableRuby?: boolean;
125
- /**
126
- * Allow inserting images into the EPUB (the "Images" section).
127
- * @defaultValue true
128
- */
129
- enableImages?: boolean;
130
- /**
131
- * Allow exporting the edited EPUB. SaaS publishers can disable this to
132
- * restrict downloads (e.g. server-side export only).
133
- * @defaultValue true
134
- */
135
- enableExport?: boolean;
136
- /**
137
- * Called before the export buffer is offered as a download. Return `false`
138
- * (or a `Promise<false>`) to suppress the browser download — useful for
139
- * uploading the buffer to a backend instead.
140
- */
141
- onBeforeExport?: (buffer: ArrayBuffer) => boolean | undefined | Promise<boolean | undefined>;
118
+ /** Options for {@link useAnnotations}. */
119
+ interface UseAnnotationsOptions {
120
+ /** Storage key — usually scoped per-book (e.g. `mejiro:annotations:${bookId}`). */
121
+ key: string;
142
122
  /**
143
- * Declarative export policy. When set, supersedes `onBeforeExport` for
144
- * download control and threads watermark / encrypt transforms through the
145
- * export pipeline.
123
+ * Storage backend. Defaults to `window.localStorage` when available.
124
+ * SSR consumers can omit this and the hook will fall back to in-memory.
125
+ * The latest reference is always used, so an object literal recreated on
126
+ * every render is safe — only `key` triggers a re-hydration.
146
127
  */
147
- exportPolicy?: MejiroExportPolicy;
128
+ storage?: MejiroStorage;
148
129
  /**
149
- * Resolves URL-only image assets ({@link EditableImageAsset.url} set, `data`
150
- * unset) into bytes at export time. Forwarded to `editor.export()`. Use this
151
- * to keep large image bytes off the client during editing and only fetch
152
- * them once when the EPUB is assembled — e.g. signed S3 URLs that require
153
- * custom auth headers.
130
+ * Debounce delay (ms): the latest value is written this long after the last
131
+ * change. A pending write is flushed on page hide, key change and unmount.
132
+ * @defaultValue 250
154
133
  */
155
- assetResolver?: AssetResolver;
134
+ debounceMs?: number;
156
135
  /**
157
- * Archive resource limits applied while opening an EPUB. Raise them for
158
- * trusted, image-heavy books; tighten them for a public drop zone. Omitted
159
- * fields keep their `DEFAULT_EPUB_PARSE_LIMITS` value.
136
+ * Called immediately after each mutation (`add` / `remove` / `update` /
137
+ * `clear`) with the new annotation list. Use to mirror state to a server
138
+ * — `storage` only covers the local persistence side. Not invoked on
139
+ * initial hydration or when the `key` changes.
160
140
  */
161
- limits?: Partial<EpubParseLimits>;
162
- /** Called after an EPUB is loaded into the editor. */
163
- onLoad?: (editor: EditableEpub) => void;
164
- /** Called after export completes. */
165
- onExport?: (buffer: ArrayBuffer) => void;
141
+ onAnnotationsChange?: (next: readonly Annotation[]) => void;
166
142
  /**
167
- * Called when loading, image insertion, export or the preview reader fails.
168
- * The error is also shown in the editor until a later load, image insertion
169
- * or export succeeds.
143
+ * Receives a storage failure: `storage-read-failed` or `storage-corrupt` when
144
+ * hydrating (the list starts empty, and the stored value is left alone until
145
+ * the next mutation), `storage-write-failed` when a write or removal throws
146
+ * (the in-memory list is kept). Without it the error goes to `console.warn`.
170
147
  */
171
- onError?: (error: Error) => void;
148
+ onError?: (error: MejiroError) => void;
172
149
  }
173
- /**
174
- * Declarative restrictions on the export pipeline. Mejiro applies the
175
- * transforms in this order: `watermark` (applied to an export-only copy of the
176
- * book, never to the edited document) → `encrypt` (replaces the buffer with the
177
- * result) → `allowDownload` (skips the browser download when `false`).
178
- */
179
- interface MejiroExportPolicy {
180
- /**
181
- * If `false`, the EPUB buffer is still produced (and `onExport` still fires)
182
- * but no browser download is triggered. Use when shipping the buffer
183
- * elsewhere (e.g. uploading to a backend).
184
- * @defaultValue true
185
- */
186
- allowDownload?: boolean;
187
- /**
188
- * Transforms the EPUB buffer before it is offered for download. Typically
189
- * a server round-trip that returns a DRM-wrapped EPUB.
190
- */
191
- encrypt?: (buffer: ArrayBuffer) => ArrayBuffer | Promise<ArrayBuffer>;
192
- /**
193
- * Embeds a visible watermark string into the exported EPUB. Implemented as a
194
- * paragraph block prefixed with `[mejiro-watermark]` at the top of every
195
- * chapter, so a downstream renderer can theme it by that prefix. The block
196
- * only exists in the exported file — the edited document is unchanged.
197
- */
198
- watermark?: {
199
- text: string;
200
- };
150
+ /** Return value of {@link useAnnotations}. */
151
+ interface UseAnnotationsReturn {
152
+ /** Currently saved annotations, sorted by chapter / paragraph / charIndex. */
153
+ annotations: readonly Annotation[];
154
+ /** Add a new annotation. `id` and `createdAt` are auto-filled when omitted. */
155
+ add(input: Omit<Annotation, 'id' | 'createdAt'> & Partial<Pick<Annotation, 'id' | 'createdAt'>>): Annotation;
156
+ /** Remove an annotation by id. */
157
+ remove(id: string): void;
158
+ /** Patch an annotation by id. Pass `undefined` on a field to leave it untouched. */
159
+ update(id: string, patch: Partial<Omit<Annotation, 'id'>>): void;
160
+ /** Remove all annotations. */
161
+ clear(): void;
201
162
  }
202
- /** EPUB editor UI for proofreading, ruby edits, image insertion, and export. */
203
- declare function MejiroEditor({ epubUrl, fonts, enableProofread, enableRuby, enableImages, enableExport, onBeforeExport, exportPolicy, assetResolver, limits, onLoad, onExport, onError, }: MejiroEditorProps): ReactNode;
204
-
205
163
  /**
206
- * Rectangle describing an image overlay position and size.
164
+ * Persistence helper for reader annotations. Returns the saved annotation list
165
+ * plus add / update / remove helpers. Pair with the `annotations` prop on
166
+ * {@link MejiroReader} to render the corresponding highlight rectangles.
167
+ *
168
+ * ```tsx
169
+ * const { annotations, add } = useAnnotations({ key: `mejiro:ann:${bookId}` });
170
+ * const handle = useRef<MejiroReaderHandle>(null);
171
+ *
172
+ * // Save a highlight at the current visible range:
173
+ * const range = handle.current?.getVisibleRange();
174
+ * if (range) add({ chapterIndex: 0, start: range.start, end: range.end, color: 'yellow' });
207
175
  *
208
- * @deprecated Alias of {@link ImageOverlayRect}, which is the single name for
209
- * this shape across the package family. The core package exports an unrelated
210
- * `ImageRect` (an exclusion rectangle, with margin fields), so the two names
211
- * collide when both packages are imported into one module.
176
+ * <MejiroReader epub={book} annotations={annotations} ref={handle} />
177
+ * ```
212
178
  */
213
- type ImageRect = ImageOverlayRect;
179
+ declare function useAnnotations(options: UseAnnotationsOptions): UseAnnotationsReturn;
180
+
181
+ /** A single image overlay placed on a specific spread. */
182
+ interface MultiImageItem {
183
+ /** Stable ID for the overlay (auto-assigned on add). */
184
+ id: string;
185
+ /** Position and size in pixels, relative to the right page. */
186
+ rect: ImageOverlayRect;
187
+ }
214
188
  /** Options for {@link useImageOverlay}. */
215
189
  interface UseImageOverlayOptions {
216
- /** Default image width in pixels. @defaultValue 120 */
190
+ /** Layout spread that {@link UseImageOverlayReturn.addOverlay} places overlays on. */
191
+ spreadIndex: number;
192
+ /** Default overlay width in pixels. @defaultValue 120 */
217
193
  defaultWidth?: number;
218
- /** Default image height in pixels. @defaultValue 160 */
194
+ /** Default overlay height in pixels. @defaultValue 160 */
219
195
  defaultHeight?: number;
220
- /** Default x position. @defaultValue 80 */
196
+ /** Default x position, relative to the right page. @defaultValue 80 */
221
197
  defaultX?: number;
222
198
  /** Default y position. @defaultValue 100 */
223
199
  defaultY?: number;
224
- /** Margin around the image in pixels. Defaults to the book's font size. */
200
+ /** Margin around each overlay, in pixels. Defaults to the book's font size. */
225
201
  margin?: number;
202
+ /**
203
+ * Called with each spread the layout re-flowed after an overlay change, and
204
+ * after the overlays are re-applied to a replacement layout or a new margin.
205
+ * Pair it with `useSpread().refresh()` so the spread on screen picks up the
206
+ * reflow. @defaultValue no-op
207
+ */
208
+ onReflow?: (spread: SpreadResult) => void;
226
209
  }
227
210
  /** Return value of {@link useImageOverlay}. */
228
211
  interface UseImageOverlayReturn {
229
- /** Current image rectangle, or `null` if no overlay is active. */
230
- imageRect: ImageOverlayRect | null;
231
- /** Whether an image overlay is currently active. */
232
- hasImage: boolean;
233
- /** Toggle the image overlay on/off. */
234
- toggleImage: () => void;
235
- /** Pointer-down handler for the overlay body (initiates drag). */
236
- onOverlayPointerDown: (e: PointerEvent) => void;
237
- /** Pointer-down handler for the resize handle. */
238
- onResizePointerDown: (e: PointerEvent) => void;
212
+ /**
213
+ * Overlays keyed by spread index. A new map whenever the overlays or the
214
+ * margin they are applied with change, and a replacement layout has them
215
+ * before any view renders from it, so this map and the layout together key
216
+ * anything derived from the layout.
217
+ */
218
+ imagesBySpread: ReadonlyMap<number, readonly MultiImageItem[]>;
219
+ /** Overlays on {@link UseImageOverlayOptions.spreadIndex}; a new array whenever `imagesBySpread` changes. */
220
+ currentImages: readonly MultiImageItem[];
221
+ /** Whether any spread holds at least one overlay. */
222
+ hasOverlays: boolean;
223
+ /** Adds an overlay on the current spread and returns it. */
224
+ addOverlay: (overrides?: Partial<ImageOverlayRect>) => MultiImageItem;
225
+ /** Removes the overlay with the given id; a no-op for an unknown id. */
226
+ removeOverlay: (id: string) => void;
227
+ /** Merges `rect` into the rectangle of the overlay with the given id; a no-op for an unknown id. */
228
+ updateOverlay: (id: string, rect: Partial<ImageOverlayRect>) => void;
229
+ /** Removes every overlay, or only those on `spreadIndex` when given. */
230
+ clearOverlays: (spreadIndex?: number) => void;
231
+ /** Pointer-down handler for an overlay body: starts a move drag. */
232
+ onOverlayPointerDown: (id: string, e: PointerEvent) => void;
233
+ /** Pointer-down handler for an overlay's resize handle: starts a resize drag. */
234
+ onResizePointerDown: (id: string, e: PointerEvent) => void;
239
235
  }
240
236
  /**
241
- * React hook that manages a draggable/resizable image overlay
242
- * with automatic text reflow via {@link ChapterLayout.syncImages}.
237
+ * React hook that manages draggable, resizable image overlays per spread and
238
+ * re-flows the text around them via `ChapterLayout.setImages`.
243
239
  *
244
- * While an overlay is active the exclusion is re-issued whenever `layout` is
245
- * replaced, `spreadIdx` changes or `options.margin` changes, so a re-layout, a
246
- * page turn or a new margin never leaves the overlay drawn without its matching
247
- * text reflow.
248
- *
249
- * @param layout - Current chapter layout (or `null` if not yet loaded).
250
- * @param spreadIdx - Current spread index.
251
- * @param onUpdate - Called with the updated {@link SpreadResult} after every reflow.
252
- * @param options - Default dimensions and position for the overlay.
240
+ * @param layout - Chapter layout the overlays exclude text from, or `null` while none is ready.
241
+ * @param options - Current spread, overlay defaults, margin and reflow notification.
242
+ * @returns The overlay state, mutators and pointer handlers.
253
243
  *
254
244
  * @example
255
245
  * ```tsx
256
- * const { imageRect, hasImage, toggleImage, onOverlayPointerDown, onResizePointerDown } =
257
- * useImageOverlay(layout, spreadIdx, setSpread);
246
+ * const nav = useSpread(layout);
247
+ * const overlays = useImageOverlay(layout, {
248
+ * spreadIndex: nav.spreadIndex,
249
+ * onReflow: () => nav.refresh(),
250
+ * });
258
251
  * ```
259
252
  */
260
- declare function useImageOverlay(layout: ChapterLayout | null, spreadIdx: number, onUpdate: (spread: SpreadResult) => void, options?: UseImageOverlayOptions): UseImageOverlayReturn;
253
+ declare function useImageOverlay(layout: ChapterLayout | null, options: UseImageOverlayOptions): UseImageOverlayReturn;
261
254
 
262
- /** Props for {@link MejiroImageOverlay}. */
263
- interface MejiroImageOverlayProps {
264
- /** Position and size of the overlay (px). */
265
- rect: ImageRect;
266
- /** Label inside the overlay. @defaultValue `messages.imageButton` */
267
- label?: string;
268
- /** Pointer-down on the body (drag). */
269
- onOverlayPointerDown?: (e: PointerEvent) => void;
270
- /** Pointer-down on the resize handle. */
271
- onResizePointerDown?: (e: PointerEvent) => void;
272
- /** Triggered when the close button is pressed. */
273
- onClose?: () => void;
255
+ /**
256
+ * Spread mode a view renders in once `'auto'` has been decided: one page at a
257
+ * time, or two-page spreads.
258
+ */
259
+ type ResolvedSpreadMode = 'single' | 'double';
260
+ /** Options for {@link UseSpreadReturn.goTo}. */
261
+ interface GoToOptions {
262
+ /**
263
+ * Run the page-turn animation. With `false` the index changes immediately,
264
+ * cancelling any turn in flight — use it to restore a position after a
265
+ * re-layout, where an animated turn would briefly show the first spread.
266
+ * @defaultValue true
267
+ */
268
+ animate?: boolean;
269
+ }
270
+ /** Options for {@link useSpread}. */
271
+ interface UseSpreadOptions {
272
+ /** Enable ArrowLeft/ArrowRight navigation. @defaultValue true */
273
+ enableKeyboard?: boolean;
274
+ /** Page-turn animation duration in milliseconds; `0` turns without animating. @defaultValue 180 */
275
+ turnDurationMs?: number;
276
+ /**
277
+ * Spread mode to navigate in. In `'single'` the navigation index counts
278
+ * pages; in `'double'` it counts two-page spreads. Changing it converts the
279
+ * index so the page on screen stays visible. @defaultValue 'double'
280
+ */
281
+ spreadMode?: ResolvedSpreadMode;
282
+ /**
283
+ * Keep the visible text on screen when `layout` is replaced: the new layout
284
+ * opens at the position holding the first text the old one showed, instead
285
+ * of at index 0. @defaultValue false
286
+ */
287
+ preservePosition?: boolean;
288
+ /**
289
+ * Called with the new navigation index after it changes: when an animated
290
+ * turn lands, at the end of {@link UseSpreadOptions.turnDurationMs}, or
291
+ * immediately for a turn without animation. Not called while a turn is in
292
+ * flight, and not called when a turn ends where it started.
293
+ */
294
+ onIndexChange?: (index: number) => void;
295
+ }
296
+ /** Return value of {@link useSpread}. */
297
+ interface UseSpreadReturn {
298
+ /** Navigation index: a page index in single mode, a spread index otherwise. `0` without a layout. */
299
+ index: number;
300
+ /** Number of navigation positions: pages in single mode, spreads otherwise. `0` without a layout. */
301
+ count: number;
302
+ /** Index of the layout spread holding the visible page(s), as `ChapterLayout.getSpread` takes it. */
303
+ spreadIndex: number;
304
+ /** Number of spreads in the layout, whatever the spread mode. */
305
+ spreadCount: number;
306
+ /** Number of pages in the layout, as of {@link UseSpreadReturn.spread}. */
307
+ pageCount: number;
308
+ /** Layout spread holding the visible page(s), or `null` until the layout is ready. */
309
+ spread: SpreadResult | null;
310
+ /** Index of the first page shown, in reading order. */
311
+ firstPage: number;
312
+ /** Side of {@link UseSpreadReturn.spread} shown in single mode; `null` in double mode. */
313
+ singleSide: 'right' | 'left' | null;
314
+ /** Whether a page-turn animation is in flight. */
315
+ turning: boolean;
316
+ /** Move one position forward, counting from the target of a turn in flight. */
317
+ next: () => void;
318
+ /** Move one position back, counting from the target of a turn in flight. */
319
+ prev: () => void;
320
+ /**
321
+ * Go to a navigation index. Negative values go to `0` and values past the
322
+ * end to `count − 1`. A call during a turn replaces that turn's target, so
323
+ * the turn lands on the last index requested. A no-op without a layout or
324
+ * when `count` is `0`.
325
+ *
326
+ * @throws RangeError when `index` is not an integer (fractions, `NaN`, `±Infinity`).
327
+ */
328
+ goTo: (index: number, options?: GoToOptions) => void;
329
+ /** Navigation index that shows the given page. */
330
+ indexOfPage: (pageIndex: number) => number;
331
+ /**
332
+ * Anchor at the start of the text shown at navigation index `index`. A page an
333
+ * image blocks entirely holds no text, so the anchor then comes from the next
334
+ * page that does, even past `index`; `null` when no page from there on holds text.
335
+ */
336
+ anchorAt: (index: number) => InChapterAnchor | null;
337
+ /** Re-read {@link UseSpreadReturn.spread} from `layout` at the current index. */
338
+ refresh: () => void;
274
339
  }
275
340
  /**
276
- * Decorative image placeholder overlay with drag/resize affordances.
277
- * Pair the pointer handlers with {@link useImageOverlay} or {@link useMultiImageOverlay}.
341
+ * React hook that tracks the navigation position in a chapter layout and
342
+ * exposes navigation helpers with an optional page-turn animation.
343
+ *
344
+ * @param layout - Chapter layout to navigate, or `null` while none is ready.
345
+ * @param options - Spread mode, animation, keyboard and change notification.
346
+ * @returns The navigation state and helpers.
278
347
  */
279
- declare function MejiroImageOverlay({ rect, label, onOverlayPointerDown, onResizePointerDown, onClose, }: MejiroImageOverlayProps): ReactNode;
348
+ declare function useSpread(layout: ChapterLayout | null, options?: UseSpreadOptions): UseSpreadReturn;
349
+
350
+ /**
351
+ * A selection rectangle from `ChapterLayout.selectionRects`, optionally
352
+ * carrying its own CSS fill colour.
353
+ */
354
+ type SelectionRect = AnchorRect & {
355
+ /** CSS colour filling this rectangle; the stylesheet's selection fill when absent. */
356
+ color?: string;
357
+ };
358
+ /** The part of an {@link Annotation} a reader needs to draw it as a highlight. */
359
+ type ReaderAnnotation = Pick<Annotation, 'chapterIndex' | 'start' | 'end' | 'color'>;
360
+ /** Props for {@link MejiroSpread}; other `div` attributes go to the root element. */
361
+ interface MejiroSpreadProps extends ComponentPropsWithoutRef<'div'> {
362
+ /** Spread data to render. */
363
+ spread: SpreadResult;
364
+ /**
365
+ * Layout the spread comes from. When given, its `geometry` and `typography`
366
+ * supply every page-size and font prop left unset.
367
+ */
368
+ layout?: ChapterLayout;
369
+ /** Page width (px). Defaults to `layout.geometry.pageWidth`. */
370
+ pageWidth?: number;
371
+ /** Page height (px). Defaults to `layout.geometry.pageHeight`. */
372
+ pageHeight?: number;
373
+ /** Content area height (px). Defaults to `layout.geometry.contentHeight`. */
374
+ contentHeight?: number;
375
+ /** CSS font family applied to the content. Defaults to `layout.typography.fontFamily`. */
376
+ fontFamily?: FontFamily;
377
+ /** Font size (px). Defaults to `layout.typography.fontSize`. */
378
+ fontSize?: number;
379
+ /** Line spacing multiplier. Defaults to `layout.typography.lineSpacing`. */
380
+ lineSpacing?: number;
381
+ /** Animate the page turn. */
382
+ turning?: boolean;
383
+ /** Header data for the right page. */
384
+ rightHeader?: PageHeaderData;
385
+ /** Header data for the left page. */
386
+ leftHeader?: PageHeaderData;
387
+ /**
388
+ * Image overlays on the current spread, relative to the right page. In
389
+ * single spread mode, only those whose centre lies on the page shown are drawn.
390
+ */
391
+ images?: readonly MultiImageItem[];
392
+ /**
393
+ * Force slot-mode rendering on both pages. When omitted, each page falls
394
+ * through to {@link MejiroPageView}'s automatic choice: slot mode for pages
395
+ * with images, native `writing-mode: vertical-rl` otherwise.
396
+ */
397
+ slotMode?: boolean;
398
+ /** Custom renderer for per-page headers. */
399
+ renderPageHeader?: (side: 'right' | 'left', header: PageHeaderData) => ReactNode;
400
+ /** Custom indicator placed inside the spread (e.g. {@link MejiroPageIndicator}). */
401
+ indicator?: ReactNode;
402
+ /** Called when the previous-page zone is clicked. */
403
+ onPrev?: () => void;
404
+ /** Called when the next-page zone is clicked. */
405
+ onNext?: () => void;
406
+ /** Pointer-down on an image overlay. */
407
+ onImagePointerDown?: (id: string, e: PointerEvent) => void;
408
+ /** Pointer-down on an image resize handle. */
409
+ onImageResizePointerDown?: (id: string, e: PointerEvent) => void;
410
+ /** Image overlay close button. */
411
+ onImageClose?: (id: string) => void;
412
+ /**
413
+ * Resolves a spread-local point to an in-chapter anchor.
414
+ * Typically `(point) => layout.anchorAtCoord(spreadIndex, point)`. When provided
415
+ * together with {@link MejiroSpreadProps.onSelectionChange}, the spread
416
+ * enables pointer-drag selection on the page content area.
417
+ */
418
+ anchorAtCoord?: (point: SpreadPoint) => InChapterAnchor | null;
419
+ /**
420
+ * Zero-based index of the spread being rendered. Rectangles passed through
421
+ * {@link MejiroSpreadProps.selectionRects} carry spread-local coordinates,
422
+ * so this is what scopes them to this spread. When omitted, every supplied
423
+ * rectangle is painted.
424
+ */
425
+ spreadIndex?: number;
426
+ /**
427
+ * Selection rectangles to render as a highlight overlay. Compute via
428
+ * {@link ChapterLayout.selectionRects}. Entries whose `spreadIndex` differs
429
+ * from {@link MejiroSpreadProps.spreadIndex} are ignored. An entry may carry
430
+ * a `color` that becomes that rectangle's fill.
431
+ */
432
+ selectionRects?: readonly SelectionRect[];
433
+ /**
434
+ * Called when the user drags-selects text on the spread.
435
+ * Called with `null` on a single-click (caret) where start equals end.
436
+ */
437
+ onSelectionChange?: (range: AnchorRange | null) => void;
438
+ /**
439
+ * `'single'` renders one page of the spread (centered), chosen by
440
+ * {@link MejiroSpreadProps.singleSide}; `'double'` renders both. Use
441
+ * `'single'` for portrait viewports or when a reader opts into single-page
442
+ * mode. @defaultValue 'double'
443
+ */
444
+ spreadMode?: ResolvedSpreadMode;
445
+ /**
446
+ * Page of the spread shown in single spread mode, as `useSpread` reports it
447
+ * in `singleSide`; `null` (what `useSpread` reports in double mode) shows the
448
+ * right page. @defaultValue 'right'
449
+ */
450
+ singleSide?: 'right' | 'left' | null;
451
+ /**
452
+ * Called when the user makes a quick swipe gesture on the spread.
453
+ * `direction === 'next'` means the user swiped from right to left (the
454
+ * natural direction in vertical-rl for advancing).
455
+ */
456
+ onSwipe?: (direction: 'next' | 'prev') => void;
457
+ /** Called when the user taps the center area without swiping. */
458
+ onSurfaceTap?: () => void;
459
+ }
460
+ /**
461
+ * Renders a two-page spread (or one page of it in single spread mode) with the
462
+ * book frame, page chrome, navigation zones, and image overlays. Used inside
463
+ * `mejiro-reader-surface`.
464
+ */
465
+ declare const MejiroSpread: react.ForwardRefExoticComponent<MejiroSpreadProps & react.RefAttributes<HTMLDivElement>>;
466
+
467
+ /** Second argument of a host `fetchEpub` callback. */
468
+ interface FetchEpubOptions {
469
+ /**
470
+ * Aborted when the load is superseded — by a newer load, a `url` change,
471
+ * `setEpub` or unmount. Pass it on to the request so the download stops too.
472
+ */
473
+ signal: AbortSignal;
474
+ }
475
+ /** Options for {@link useEpub}. */
476
+ interface UseEpubOptions {
477
+ /**
478
+ * URL to load. Fetched on mount and again whenever it changes; clearing it
479
+ * abandons a load still in flight and keeps the current EPUB.
480
+ */
481
+ url?: string;
482
+ /** Called after a successful load. */
483
+ onLoad?: (epub: EpubBook) => void;
484
+ /**
485
+ * Called when a load fails, including a non-2xx URL response
486
+ * (`MejiroError` `fetch-failed`). A `MejiroError` is passed as thrown.
487
+ */
488
+ onError?: (error: Error) => void;
489
+ /**
490
+ * Extra options merged into the `fetch` call when loading by URL. Useful
491
+ * for sending bearer tokens or cookies (`credentials: 'include'`).
492
+ */
493
+ fetchOptions?: RequestInit;
494
+ /**
495
+ * Custom EPUB fetcher used in place of the global `fetch`. Resolves to the
496
+ * raw bytes. Overrides {@link UseEpubOptions.fetchOptions} when set — common
497
+ * for hosts that already own an auth-aware HTTP client.
498
+ */
499
+ fetchEpub?: (url: string, options: FetchEpubOptions) => Promise<BinaryInput>;
500
+ /**
501
+ * Archive resource limits applied while opening an EPUB. Raise them for
502
+ * trusted, image-heavy books; tighten them for a public drop zone. Omitted
503
+ * fields keep their `DEFAULT_EPUB_PARSE_LIMITS` value.
504
+ */
505
+ limits?: Partial<EpubParseLimits>;
506
+ }
507
+ /** Return value of {@link useEpub}. */
508
+ interface UseEpubReturn {
509
+ /** Current parsed EPUB, or `null` before any load. */
510
+ epub: EpubBook | null;
511
+ /** Whether a load is in progress. */
512
+ loading: boolean;
513
+ /** Last load error, if any. */
514
+ error: Error | null;
515
+ /**
516
+ * Parse from in-memory bytes. A `Uint8Array` is read through its own view
517
+ * and is neither retained nor modified.
518
+ */
519
+ loadBuffer: (bytes: BinaryInput) => Promise<EpubBook | null>;
520
+ /** Parse from a {@link File}. */
521
+ loadFile: (file: File) => Promise<EpubBook | null>;
522
+ /** Fetch a URL and parse the response. */
523
+ loadUrl: (url: string) => Promise<EpubBook | null>;
524
+ /** Replace the current EPUB without going through the parser, abandoning a load in flight. */
525
+ setEpub: (epub: EpubBook | null) => void;
526
+ }
527
+ /**
528
+ * React hook that parses EPUB files and exposes loading/error state plus
529
+ * convenience loaders for buffers, files, and URLs.
530
+ *
531
+ * One load runs at a time: starting another, changing `url`, calling
532
+ * `setEpub` or unmounting aborts the one in flight — its fetch and parse
533
+ * included — and its result is dropped. Each loader resolves to the parsed
534
+ * EPUB, or `null` when the load failed or was superseded.
535
+ */
536
+ declare function useEpub(options?: UseEpubOptions): UseEpubReturn;
280
537
 
281
538
  /**
282
539
  * Where {@link MejiroReader} places its chapter navigation: the header
@@ -292,9 +549,9 @@ type MejiroReaderMode = 'paginated' | 'scroll';
292
549
  /**
293
550
  * Two-page vs single-page rendering of a spread.
294
551
  * - `double` — always render two pages (default).
295
- * - `single` — render one page at a time, centered; spread indices count pages.
552
+ * - `single` — render one page at a time, centered.
296
553
  * - `auto` — switch based on the surface aspect ratio (single when portrait),
297
- * converting the index so the page on screen stays visible.
554
+ * keeping the page on screen visible.
298
555
  */
299
556
  type MejiroSpreadMode = 'double' | 'single' | 'auto';
300
557
 
@@ -320,60 +577,90 @@ type MejiroTheme = MejiroThemeName | {
320
577
  name: MejiroThemeName;
321
578
  override?: Record<string, string>;
322
579
  };
323
- /** Reading position exposed by {@link MejiroReaderHandle.getReadingPosition}. */
324
- interface ReadingPosition {
580
+ /**
581
+ * Spread-level position of a reader, as {@link MejiroReaderHandle.getSpreadPosition}
582
+ * reports it. A spread index does not survive a re-layout; persist a
583
+ * {@link ReadingAnchor} to keep a reading position.
584
+ */
585
+ interface SpreadPosition {
325
586
  /** Zero-based index of the current chapter. */
326
- chapter: number;
327
- /** Zero-based index of the current spread within the chapter. */
328
- spreadIdx: number;
329
- /** Number of pages in the current chapter's layout. */
330
- totalPages: number;
587
+ chapterIndex: number;
588
+ /** Zero-based index of the layout spread holding the visible page(s). */
589
+ spreadIndex: number;
331
590
  /** Number of spreads in the current chapter's layout. */
332
- totalSpreads: number;
591
+ spreadCount: number;
592
+ /** Number of pages in the current chapter's layout. */
593
+ pageCount: number;
594
+ }
595
+ /** Payload of the `spreadIndexChange` reader event. */
596
+ interface MejiroReaderSpreadIndexChangeEvent {
597
+ /** Chapter the spread belongs to. */
598
+ chapterIndex: number;
599
+ /** Layout spread now shown. */
600
+ spreadIndex: number;
601
+ }
602
+ /** Payload of the `chapterFinished` reader event. */
603
+ interface MejiroReaderChapterFinishedEvent {
604
+ /** Chapter whose last position was reached. */
605
+ chapterIndex: number;
606
+ }
607
+ /** Payload of the `turnStart` reader event. */
608
+ interface MejiroReaderTurnStartEvent {
609
+ /** Navigation position the turn leaves: a page in single mode, a spread otherwise. */
610
+ from: number;
611
+ }
612
+ /** Payload of the `turnEnd` reader event. */
613
+ interface MejiroReaderTurnEndEvent {
614
+ /** Navigation position the turn landed on: a page in single mode, a spread otherwise. */
615
+ to: number;
616
+ }
617
+ /** Half-open range of reading anchors, as {@link MejiroReaderHandle.getVisibleRange} returns it. */
618
+ interface ReadingAnchorRange {
619
+ /** First visible position (inclusive). */
620
+ start: ReadingAnchor;
621
+ /** Position where the text after the visible range begins (exclusive). */
622
+ end: ReadingAnchor;
333
623
  }
334
624
  /**
335
625
  * Event payloads emitted by {@link MejiroReaderHandle.subscribe}.
336
626
  *
337
- * Each property maps an event name to its listener signature.
627
+ * Each property maps an event name to its listener signature. An event that
628
+ * has a prop twin is named after it without the `on` prefix.
338
629
  */
339
630
  interface MejiroReaderEventMap {
340
- /** Fires after the current spread index changes. */
341
- spreadChanged: (payload: {
342
- chapter: number;
343
- spreadIdx: number;
344
- }) => void;
345
- /** Fires when a turn animation begins (before the new spread is shown). */
346
- turnStart: (payload: {
347
- from: number;
348
- }) => void;
349
- /** Fires after a turn animation completes (the new spread is now shown). */
350
- turnEnd: (payload: {
351
- to: number;
352
- }) => void;
353
- /** Fires when the reader reaches the last spread of the current chapter. */
354
- chapterFinished: (payload: {
355
- chapter: number;
356
- }) => void;
631
+ /** Fires once the reader shows another layout spread or chapter. */
632
+ spreadIndexChange: (payload: MejiroReaderSpreadIndexChangeEvent) => void;
633
+ /** Fires when a turn animation begins (before the new position is shown). */
634
+ turnStart: (payload: MejiroReaderTurnStartEvent) => void;
635
+ /** Fires after a turn animation completes (the new position is now shown). */
636
+ turnEnd: (payload: MejiroReaderTurnEndEvent) => void;
637
+ /** Fires when the reader reaches the last position of the current chapter. */
638
+ chapterFinished: (payload: MejiroReaderChapterFinishedEvent) => void;
357
639
  }
358
640
  /** Imperative handle returned by `ref={...}` on {@link MejiroReader}. */
359
641
  interface MejiroReaderHandle {
360
- /** Jump to a specific spread (0-based, clamped to [0, totalSpreads − 1]). */
361
- goToSpread(index: number): void;
362
- /** Advance one spread forward. */
642
+ /**
643
+ * Turns to a layout spread (0-based, clamped to the chapter's spreads). In
644
+ * single-page mode the spread opens on its right page.
645
+ *
646
+ * @throws RangeError when `spreadIndex` is not an integer.
647
+ */
648
+ goToSpread(spreadIndex: number): void;
649
+ /** Advance one position: a page in single-page mode, a spread otherwise. */
363
650
  next(): void;
364
- /** Go back one spread. */
651
+ /** Go back one position. */
365
652
  prev(): void;
366
- /** Jump to a chapter (clamped to the book's chapters; resets spread index to 0). */
367
- goToChapter(index: number): void;
368
- /** Read the current reading position. */
369
- getReadingPosition(): ReadingPosition;
653
+ /** Jump to a chapter (clamped to the book's chapters; opens at its first spread). */
654
+ goToChapter(chapterIndex: number): void;
655
+ /** Reads the current spread-level position. */
656
+ getSpreadPosition(): SpreadPosition;
370
657
  /**
371
658
  * Navigate to a {@link ReadingAnchor}. If the chapter differs from the
372
659
  * current one, the chapter is switched first; once the new layout is ready
373
- * the anchor is resolved and the matching spread is opened. A call made
660
+ * the anchor is resolved and the matching position is opened. A call made
374
661
  * before the book has loaded is applied once it has.
375
662
  *
376
- * Returns a promise that resolves once the spread has been applied, or
663
+ * Returns a promise that resolves once the position has been applied, or
377
664
  * without moving when the anchor's chapter or position does not exist in the
378
665
  * book. If another `goToAnchor` is invoked before the previous one settles,
379
666
  * the earlier promise resolves immediately (superseded). Resolves on unmount.
@@ -381,31 +668,28 @@ interface MejiroReaderHandle {
381
668
  goToAnchor(anchor: ReadingAnchor): Promise<void>;
382
669
  /**
383
670
  * Returns the {@link ReadingAnchor} at the start of the text on the current
384
- * spread, or `null` if the layout is not ready. When an image blocks every
385
- * page of the spread, the anchor is where the text resumes after it; `null`
386
- * again if none follows.
671
+ * position, or `null` if the layout is not ready. When an image blocks every
672
+ * page shown, the anchor is where the text resumes after it; `null` again if
673
+ * none follows.
387
674
  */
388
675
  getAnchor(): ReadingAnchor | null;
389
676
  /**
390
677
  * Returns the half-open range of {@link ReadingAnchor}s visible on the
391
- * current spread. `end` points at the start of the text on the next spread
392
- * that has any (or the end of the chapter when none does).
678
+ * current position. `end` points at the start of the text on the next
679
+ * position that has any (or the end of the chapter when none does).
393
680
  */
394
- getVisibleRange(): {
395
- start: ReadingAnchor;
396
- end: ReadingAnchor;
397
- } | null;
681
+ getVisibleRange(): ReadingAnchorRange | null;
398
682
  /**
399
- * Updates book options at runtime. Same shape as {@link MejiroBook.setOptions}
400
- * — font / size changes re-measure and re-layout asynchronously.
683
+ * Merges `patch` into the book options at runtime; font / size changes
684
+ * re-measure and re-layout asynchronously.
401
685
  *
402
686
  * Successive calls are coalesced into a single application; the returned
403
687
  * promise resolves once that application has settled. A failed application
404
688
  * (such as a font that measures as a fallback under `strictFontCheck`) is
405
- * reported through {@link MejiroReaderProps.onError} rather than rejecting
406
- * the promise.
689
+ * reported through {@link MejiroReaderCommonProps.onError} rather than
690
+ * rejecting the promise.
407
691
  */
408
- setOptions(partial: Partial<BookOptions>): Promise<void>;
692
+ updateOptions(patch: BookOptionsPatch): Promise<void>;
409
693
  /**
410
694
  * Subscribes to a reader lifecycle event. Returns a function that
411
695
  * detaches the listener.
@@ -422,51 +706,89 @@ interface MejiroReaderHandle {
422
706
  interface MejiroReaderSettingsSlot {
423
707
  /** Current effective settings (font, size, line spacing, kinsoku, hanging). */
424
708
  settings: EditableSettings;
425
- /** Applies a partial settings change and re-flows the chapter. */
426
- update: (partial: Partial<BookOptions>) => void;
709
+ /** Merges a settings change into the book options and re-flows the chapter. */
710
+ update: (patch: BookOptionsPatch) => void;
427
711
  /** Whether the settings panel is currently open. */
428
712
  open: boolean;
429
713
  /** Toggles the settings panel open/closed (same as the header button). */
430
714
  toggle: () => void;
431
715
  }
432
- /** Props shared across every {@link MejiroReader} source mode. */
716
+ /**
717
+ * Props shared across every {@link MejiroReader} source mode.
718
+ *
719
+ * The position props come in value / handler pairs (`chapterIndex` /
720
+ * `onChapterIndexChange`, `spreadIndex` / `onSpreadIndexChange`, `anchor` /
721
+ * `onAnchorChange`). A value without its handler is the initial position; with
722
+ * its handler the pair is controlled: the reader reports each navigation
723
+ * through the handler and shows the value it is given, returning to it when
724
+ * the host keeps it unchanged (an absent index reads as 0). When `anchor` is
725
+ * given it decides the chapter and spread, and `chapterIndex` / `spreadIndex`
726
+ * are not read.
727
+ */
433
728
  interface MejiroReaderCommonProps {
434
729
  /**
435
- * Initial book options. Optional — defaults to {@link DEFAULT_BOOK_OPTIONS}
436
- * (`serif` 16px, line spacing 1.8, strict kinsoku, hanging punctuation on).
437
- * Spread the defaults to tweak only a few:
438
- *
439
- * ```ts
440
- * { ...DEFAULT_BOOK_OPTIONS, fontFamily: '"Noto Serif JP"', fontSize: 18 }
441
- * ```
730
+ * Book options. Optional — defaults to {@link DEFAULT_BOOK_OPTIONS}
731
+ * (`serif` 16px, line spacing 1.8, strict kinsoku, hanging punctuation on);
732
+ * the keys given here override those defaults.
442
733
  *
443
- * `strictFontCheck` is read once, when the reader mounts.
734
+ * The keys `MejiroBook.updateOptions` accepts follow this prop by content;
735
+ * the construction-only keys (`strictFontCheck`, `analyzer`, …) are read
736
+ * once, when the reader mounts.
444
737
  */
445
- options?: Partial<MejiroBookOptions>;
738
+ options?: Partial<BookOptions>;
446
739
  /**
447
- * Page-geometry overrides forwarded to `MejiroBook.computePageSize`. Use to
740
+ * Page-geometry overrides forwarded to `MejiroBook.fitPageSize`. Use to
448
741
  * tune how the spread is sized inside the surface — most usefully to shrink
449
742
  * the reserved margins so the pages fill their frame, e.g.
450
743
  * `pageGeometry={{ gutterOffset: 0, headerOffset: 0 }}`. Also accepts
451
- * `aspect`, `minWidth`, `minHeight`, `maxHeight`, and inner `padding`; omitted
452
- * fields fall back to the built-in defaults.
744
+ * `aspect`, `minWidth`, `minHeight`, `maxHeight`, and the page padding;
745
+ * omitted fields fall back to the built-in defaults.
453
746
  */
454
- pageGeometry?: ComputePageSizeOptions;
747
+ pageGeometry?: FitPageSizeOptions;
455
748
  /** Font choices for the settings panel. */
456
- fonts?: FontChoice[];
749
+ fonts?: readonly FontChoice[];
750
+ /**
751
+ * Chapter to show. The initial chapter unless
752
+ * {@link MejiroReaderCommonProps.onChapterIndexChange} is given, which
753
+ * makes it controlled. An uncontrolled chapter resets to 0 when the book
754
+ * changes. @defaultValue 0
755
+ */
756
+ chapterIndex?: number;
757
+ /**
758
+ * Called with the chapter the reader moves to. Passing it makes
759
+ * {@link MejiroReaderCommonProps.chapterIndex} controlled.
760
+ */
761
+ onChapterIndexChange?: (chapterIndex: number) => void;
762
+ /**
763
+ * Layout spread to show — always a spread of the chapter layout, also in
764
+ * single-page mode, where the spread opens on its right page (the first in
765
+ * reading order). The initial spread unless
766
+ * {@link MejiroReaderCommonProps.onSpreadIndexChange} is given, which makes
767
+ * it controlled. A spread index does not survive a re-layout; use `anchor`
768
+ * to keep a reading position. @defaultValue 0
769
+ */
770
+ spreadIndex?: number;
457
771
  /**
458
- * Controlled chapter index. When omitted, the reader manages its own
459
- * chapter state and resets to 0 on EPUB change.
772
+ * Called with the layout spread the reader moves to. Turning from the right
773
+ * to the left page of one spread in single-page mode keeps the spread, so it
774
+ * does not fire; follow `onAnchorChange` for page-level changes. Passing it
775
+ * makes {@link MejiroReaderCommonProps.spreadIndex} controlled.
460
776
  */
461
- chapter?: number;
777
+ onSpreadIndexChange?: (spreadIndex: number) => void;
462
778
  /**
463
- * Controlled spread index. When supplied, the reader is driven by this
464
- * value and emits {@link MejiroReaderProps.onSpreadIdxChange} on user
465
- * navigation. A spread index does not survive reflow, so persist the
466
- * reading position with `useReadingPosition` and the handle's
467
- * `getAnchor` / `goToAnchor` instead.
779
+ * Reading position to show. When given, it decides the chapter and spread
780
+ * and `chapterIndex` / `spreadIndex` are not read. The initial position
781
+ * unless {@link MejiroReaderCommonProps.onAnchorChange} is given, which
782
+ * makes it controlled.
468
783
  */
469
- spreadIdx?: number;
784
+ anchor?: ReadingAnchor | null;
785
+ /**
786
+ * Called with the anchor at the start of the text the reader moves to, on
787
+ * each move of the visible page(s) or chapter. Passing it makes
788
+ * {@link MejiroReaderCommonProps.anchor} controlled. Pair with
789
+ * `useReadingPosition` to persist the position.
790
+ */
791
+ onAnchorChange?: (anchor: ReadingAnchor) => void;
470
792
  /**
471
793
  * Visual theme preset, or `{ name, override }` to layer custom
472
794
  * CSS variables on top of a preset. @defaultValue 'light'
@@ -482,9 +804,8 @@ interface MejiroReaderCommonProps {
482
804
  mode?: MejiroReaderMode;
483
805
  /**
484
806
  * Spread layout. `double` (default) renders two pages; `single` renders one
485
- * page at a time, and `spreadIdx`, `goToSpread`, `onSpreadIdxChange` and
486
- * `spreadChanged` then count pages; `auto` flips to `single` for portrait
487
- * viewports, keeping the page on screen visible.
807
+ * page at a time; `auto` flips to `single` for portrait viewports, keeping
808
+ * the page on screen visible.
488
809
  */
489
810
  spreadMode?: MejiroSpreadMode;
490
811
  /**
@@ -494,7 +815,7 @@ interface MejiroReaderCommonProps {
494
815
  * embedding host only has to constrain the width (no height/aspect magic
495
816
  * numbers, no letterbox). In `width` mode the reserved `gutterOffset` /
496
817
  * `headerOffset` default to 0 so the spread fills edge-to-edge; override via
497
- * {@link MejiroReaderProps.pageGeometry} if you still want them. @defaultValue 'fill'
818
+ * {@link MejiroReaderCommonProps.pageGeometry} if you still want them. @defaultValue 'fill'
498
819
  */
499
820
  fit?: MejiroReaderFit;
500
821
  /**
@@ -523,10 +844,12 @@ interface MejiroReaderCommonProps {
523
844
  */
524
845
  limits?: Partial<EpubParseLimits>;
525
846
  /**
526
- * Custom EPUB fetcher used in place of the global `fetch`. Overrides
527
- * {@link fetchOptions} when set.
847
+ * Custom EPUB fetcher used in place of the global `fetch`; resolves to the
848
+ * raw bytes. `options.signal` aborts when the load is superseded or the
849
+ * reader unmounts. Overrides {@link MejiroReaderCommonProps.fetchOptions}
850
+ * when set.
528
851
  */
529
- fetchEpub?: (url: string) => Promise<ArrayBuffer>;
852
+ fetchEpub?: (url: string, options: FetchEpubOptions) => Promise<BinaryInput>;
530
853
  /**
531
854
  * Built-in locale for UI strings (`'en'` / `'ja'`). Pair with `messages`
532
855
  * to override individual strings. Defaults to the nearest `MejiroI18nProvider`
@@ -539,26 +862,19 @@ interface MejiroReaderCommonProps {
539
862
  * without re-implementing every label.
540
863
  */
541
864
  messages?: Partial<MejiroMessages>;
542
- /** Header title text. @defaultValue 'mejiro' */
865
+ /**
866
+ * Header title text. The header — chapter select, stats, Open / Image /
867
+ * Settings buttons — is drawn only when a title or a
868
+ * {@link MejiroReaderCommonProps.logo} is given.
869
+ */
543
870
  title?: string;
544
- /** Header subtitle. @defaultValue `messages.logoSubtitle` */
871
+ /** Header subtitle, shown under the title. @defaultValue `messages.logoSubtitle` */
545
872
  subtitle?: string;
546
873
  /**
547
- * Replaces the default logo block (title + subtitle). Pass `null` to hide
548
- * the logo while keeping the rest of the header. To remove the entire
549
- * header, use `enableHeader={false}`.
874
+ * Header slot replacing the default logo block (title + subtitle). Giving
875
+ * it draws the header; pass `null` to draw the header without a logo.
550
876
  */
551
877
  logo?: ReactNode;
552
- /**
553
- * Shorthand for a chrome-less reader. When `true`, the defaults for
554
- * `enableHeader`, `enableChapterNav`, `enableSettings`, `enableStats`, and
555
- * `enablePageIndicator` flip from `true` to `false`. Explicitly-passed
556
- * enable* props still win, so you can opt parts back in.
557
- * @defaultValue false
558
- */
559
- bare?: boolean;
560
- /** Show the built-in header. @defaultValue `!bare` */
561
- enableHeader?: boolean;
562
878
  /**
563
879
  * Show the drop zone affordance. SaaS-style readers should keep this off
564
880
  * (the host controls which EPUB is delivered); set true to accept
@@ -566,90 +882,85 @@ interface MejiroReaderCommonProps {
566
882
  * @defaultValue false
567
883
  */
568
884
  enableDropZone?: boolean;
569
- /** Show the chapter selector in the header. @defaultValue `!bare` */
570
- enableChapterNav?: boolean;
571
885
  /**
572
- * Where to render the built-in chapter navigation.
886
+ * Where to render the built-in chapter navigation; `'none'` renders
887
+ * neither. The `'select'` dropdown sits in the header.
573
888
  * @defaultValue 'select'
574
889
  */
575
890
  chapterNavMode?: MejiroChapterNavMode;
576
- /** Show the settings panel toggle. @defaultValue `!bare` */
577
- enableSettings?: boolean;
891
+ /**
892
+ * Show the settings panel and its header toggle. @defaultValue true
893
+ */
894
+ showSettings?: boolean;
578
895
  /**
579
896
  * Replaces the built-in settings controls with custom UI while keeping the
580
897
  * panel chrome and its open/close accordion. Receives a
581
898
  * {@link MejiroReaderSettingsSlot} wired to the live reader, so a host can
582
899
  * build its own settings form without a parallel `options` shadow. The header
583
- * "Settings" button still toggles the panel. `enableSettings` remains the
900
+ * "Settings" button still toggles the panel. `showSettings` remains the
584
901
  * on/off switch.
585
902
  */
586
903
  renderSettings?: (slot: MejiroReaderSettingsSlot) => ReactNode;
587
- /** Show the image-overlay editing/demo button. @defaultValue false */
588
- enableImageOverlay?: boolean;
589
- /** Show the stats line. @defaultValue `!bare` */
590
- enableStats?: boolean;
591
- /** Bind ArrowLeft/ArrowRight to navigation. @defaultValue true */
904
+ /** Show the header button that places a sample image overlay. @defaultValue false */
905
+ showImageOverlayButton?: boolean;
906
+ /** Show the layout statistics in the header. @defaultValue false */
907
+ showStats?: boolean;
908
+ /**
909
+ * Bind ArrowLeft / ArrowRight to navigation while focus is inside the
910
+ * reader. The reader root takes focus for this. @defaultValue true
911
+ */
592
912
  enableKeyboard?: boolean;
593
- /** Show the "n / total" indicator. @defaultValue `!bare` */
594
- enablePageIndicator?: boolean;
913
+ /** Show the "n / total" indicator. @defaultValue true */
914
+ showPageIndicator?: boolean;
595
915
  /**
596
916
  * Which page of a spread shows its page number in the running head.
597
917
  * `'both'` numbers each page (right = odd, left = even), `'right'` /
598
918
  * `'left'` number only that side, `'none'` hides them (the
599
- * {@link MejiroReaderProps.enablePageIndicator} "n / total" badge is
919
+ * {@link MejiroReaderCommonProps.showPageIndicator} "n / total" badge is
600
920
  * independent). @defaultValue 'both'
601
921
  */
602
922
  pageNumbers?: PageNumberDisplay;
603
923
  /**
604
924
  * Reader-side annotations to render as highlights. Each annotation whose
605
- * `chapter` matches the current chapter is converted to spread-local
925
+ * `chapterIndex` matches the current chapter is converted to spread-local
606
926
  * rectangles via `ChapterLayout.selectionRects`; the rectangles landing on
607
927
  * the spread on screen are drawn on top of the page content. Pair with
608
- * {@link useAnnotations} for persistence, or pass any shape that satisfies
609
- * `{ chapter, start, end }`.
610
- */
611
- annotations?: ReadonlyArray<{
612
- chapter: number;
613
- start: InChapterAnchor;
614
- end: InChapterAnchor;
615
- color?: string;
616
- }>;
928
+ * {@link useAnnotations} for persistence.
929
+ */
930
+ annotations?: readonly ReaderAnnotation[];
617
931
  /** Called after a successful EPUB load. */
618
- onLoad?: (book: EpubBook) => void;
932
+ onLoad?: (epub: EpubBook) => void;
619
933
  /**
620
934
  * Called when loading or parsing an EPUB fails, and when applying an option
621
935
  * change fails (such as a font that measures as a fallback under
622
936
  * `strictFontCheck`; without it, a font that fails to load is not an error).
623
937
  */
624
938
  onError?: (error: Error) => void;
625
- /** Called when the chapter index changes. */
626
- onChapterChange?: (chapter: number) => void;
627
- /** Called when the spread index changes (alias: `onSpreadIdxChange`). */
628
- onSpreadChange?: (spreadIdx: number) => void;
629
- /** Called when the spread index changes — pair with the `spreadIdx` prop for controlled use. */
630
- onSpreadIdxChange?: (spreadIdx: number) => void;
631
- /**
632
- * Called when the reader moves from one spread to another within the shown
633
- * book (a change of chapter or spread index, like `spreadChanged`). Receives
634
- * the anchor of the spread that was just left and the dwell time in
939
+ /**
940
+ * Called when the reader moves from one position to another within the
941
+ * shown book (a change of chapter or navigation position). Receives the
942
+ * anchor of the position that was just left and the dwell time in
635
943
  * milliseconds (computed via `performance.now()`). A book swap and unmount
636
- * do not report the spread being left. Useful for engagement analytics.
944
+ * do not report the position being left. Useful for engagement analytics.
637
945
  */
638
946
  onPageRead?: (anchor: ReadingAnchor, dwellMs: number) => void;
639
947
  /**
640
- * Called when the reader reaches the last spread of a chapter. Same
641
- * trigger as the `chapterFinished` event on {@link MejiroReaderHandle.subscribe}.
948
+ * Called with the chapter index when the reader reaches the last position
949
+ * of a chapter. Same trigger as the `chapterFinished` event on
950
+ * {@link MejiroReaderHandle.subscribe}.
642
951
  */
643
- onChapterCompleted?: (chapter: number) => void;
952
+ onChapterFinished?: (chapterIndex: number) => void;
644
953
  }
645
954
  /**
646
- * Controlled-source variant: render a pre-parsed `EpubBook`. Pass `null`
647
- * to render an empty reader (e.g. while the book is still loading on the host).
955
+ * Parsed-source variant: render a pre-parsed `EpubBook`. Pass `null` to
956
+ * render an empty reader (e.g. while the book is still loading on the host).
648
957
  */
649
- interface MejiroReaderControlledProps extends MejiroReaderCommonProps {
958
+ interface MejiroReaderParsedProps extends MejiroReaderCommonProps {
650
959
  /** Pre-parsed EPUB. Cannot be combined with `epubUrl` / `manuscript`. */
651
960
  epub: EpubBook | null;
961
+ /** Not accepted alongside `epub`. */
652
962
  epubUrl?: never;
963
+ /** Not accepted alongside `epub`. */
653
964
  manuscript?: never;
654
965
  }
655
966
  /**
@@ -657,19 +968,24 @@ interface MejiroReaderControlledProps extends MejiroReaderCommonProps {
657
968
  * Use this for "just open this book" scenarios.
658
969
  */
659
970
  interface MejiroReaderUrlProps extends MejiroReaderCommonProps {
660
- /** EPUB URL fetched on mount. Cannot be combined with `epub` / `manuscript`. */
971
+ /** EPUB URL, fetched on mount and whenever it changes. Cannot be combined with `epub` / `manuscript`. */
661
972
  epubUrl: string;
973
+ /** Not accepted alongside `epubUrl`. */
662
974
  epub?: never;
975
+ /** Not accepted alongside `epubUrl`. */
663
976
  manuscript?: never;
664
977
  }
665
978
  /**
666
- * File-source variant: the reader exposes its drop zone / file picker.
979
+ * Picker-source variant: the reader exposes its drop zone / file picker.
667
980
  * Neither `epub` nor `epubUrl` is supplied — useful for free-form viewers
668
981
  * that accept user-supplied books.
669
982
  */
670
- interface MejiroReaderFileProps extends MejiroReaderCommonProps {
983
+ interface MejiroReaderPickerProps extends MejiroReaderCommonProps {
984
+ /** Not accepted by the picker source. */
671
985
  epub?: never;
986
+ /** Not accepted by the picker source. */
672
987
  epubUrl?: never;
988
+ /** Not accepted by the picker source. */
673
989
  manuscript?: never;
674
990
  }
675
991
  /**
@@ -683,30 +999,30 @@ interface MejiroReaderManuscriptProps extends MejiroReaderCommonProps {
683
999
  manuscript: readonly ManuscriptChapter[];
684
1000
  /** Manuscript notation dialect. @defaultValue `'mejiro'` */
685
1001
  dialect?: ManuscriptDialect;
1002
+ /** Not accepted alongside `manuscript`. */
686
1003
  epub?: never;
1004
+ /** Not accepted alongside `manuscript`. */
687
1005
  epubUrl?: never;
688
1006
  }
689
1007
  /**
690
1008
  * Props for {@link MejiroReader}. Discriminated union of the four source
691
1009
  * modes — TypeScript prevents passing more than one source at once.
692
1010
  */
693
- type MejiroReaderProps = MejiroReaderControlledProps | MejiroReaderUrlProps | MejiroReaderFileProps | MejiroReaderManuscriptProps;
1011
+ type MejiroReaderProps = MejiroReaderParsedProps | MejiroReaderUrlProps | MejiroReaderPickerProps | MejiroReaderManuscriptProps;
694
1012
  /**
695
1013
  * Full-page EPUB reader. Composes the package's primitives into a working
696
- * reader; features opt out via the `enableX` props or all chrome via `bare`.
697
- * The `ref` exposes {@link MejiroReaderHandle} for imperative navigation.
1014
+ * reader: the header (drawn when a `title` or `logo` is given), chapter
1015
+ * navigation, settings, page indicator and keyboard navigation scoped to the
1016
+ * reader root. The `ref` exposes {@link MejiroReaderHandle} for imperative
1017
+ * navigation.
1018
+ *
1019
+ * ```tsx
1020
+ * const reader = useRef<MejiroReaderHandle>(null);
1021
+ * reader.current?.goToSpread(12);
1022
+ * ```
698
1023
  */
699
1024
  declare const MejiroReader: react.ForwardRefExoticComponent<MejiroReaderProps & react.RefAttributes<MejiroReaderHandle>>;
700
1025
 
701
- /** One chapter of the manuscript being edited. */
702
- interface ManuscriptEditorChapter {
703
- /** Stable identifier; survives reordering and title edits. */
704
- id: string;
705
- /** Chapter title; an empty title falls back to the localized "untitled" label. */
706
- title: string;
707
- /** Chapter body in the editor's manuscript notation. */
708
- body: string;
709
- }
710
1026
  /** Autosave payload emitted by {@link MejiroManuscriptEditor}. */
711
1027
  interface ManuscriptAutosaveDraft {
712
1028
  /** Book title as currently entered. */
@@ -716,53 +1032,18 @@ interface ManuscriptAutosaveDraft {
716
1032
  /** Selected cover image, or `null` when none is set. */
717
1033
  cover: File | null;
718
1034
  /** Chapters in reading order, as plain (non-reactive) copies. */
719
- chapters: ManuscriptEditorChapter[];
1035
+ chapters: readonly ManuscriptChapter$1[];
720
1036
  }
721
1037
  /**
722
- * Subset of {@link MejiroReader} props that the manuscript editor passes
723
- * through to the live preview. Properties driven by the editor itself
724
- * (`manuscript`, `fonts`, `chapter`, `onChapterChange`) are ignored if
725
- * supplied here.
1038
+ * The {@link MejiroReader} props an editor forwards to its live preview. The
1039
+ * book, the chapter and its change handler, `fonts` and `onError` are driven
1040
+ * by the editor itself.
726
1041
  */
727
- interface ManuscriptPreviewProps {
728
- /** Header subtitle of the preview reader. */
729
- subtitle?: string;
730
- /** Header title of the preview reader. */
731
- title?: string;
732
- /** Where the preview places its chapter navigation. @defaultValue 'panel' */
733
- chapterNavMode?: 'select' | 'panel' | 'both' | 'none';
734
- /** Show the preview header. */
735
- enableHeader?: boolean;
736
- /** Show the preview chapter navigation. */
737
- enableChapterNav?: boolean;
738
- /** Show the preview settings panel toggle. */
739
- enableSettings?: boolean;
740
- /** Show the preview layout statistics. */
741
- enableStats?: boolean;
742
- /** Enable arrow-key page turns in the preview. */
743
- enableKeyboard?: boolean;
744
- /** Show the preview page indicator. */
745
- enablePageIndicator?: boolean;
746
- /** Book options forwarded to the embedded reader preview. */
747
- options?: Partial<BookOptions>;
748
- /** Theme forwarded to the embedded reader preview. */
749
- theme?: MejiroTheme;
750
- /** Custom settings controls forwarded to the embedded reader preview. */
751
- renderSettings?: (slot: MejiroReaderSettingsSlot) => ReactNode;
752
- /**
753
- * Toggle the surface-tap chrome-hide behavior of the embedded reader.
754
- * Defaults to `false` here (the editor's preview is part of a side-by-side
755
- * authoring workflow where tap-to-hide would surprise the author). Set
756
- * to `true` to demo the fullscreen-reader behavior.
757
- */
758
- enableSurfaceTap?: boolean;
759
- /** Render the preview without any reader chrome. */
760
- bare?: boolean;
761
- }
1042
+ type ManuscriptPreviewProps = Pick<MejiroReaderCommonProps, 'title' | 'subtitle' | 'logo' | 'theme' | 'options' | 'pageGeometry' | 'mode' | 'spreadMode' | 'fit' | 'chapterNavMode' | 'pageNumbers' | 'showSettings' | 'renderSettings' | 'showStats' | 'showPageIndicator' | 'enableKeyboard' | 'enableSurfaceTap' | 'locale' | 'messages' | 'fallback'>;
762
1043
  /** Props for {@link MejiroManuscriptEditor}. */
763
1044
  interface MejiroManuscriptEditorProps {
764
1045
  /** Font choices passed to the preview reader. */
765
- fonts?: FontChoice[];
1046
+ fonts?: readonly FontChoice[];
766
1047
  /**
767
1048
  * Title. When `onTitleChange` is supplied, this is the controlled value
768
1049
  * (changes propagate from parent to the input). Otherwise it is the
@@ -785,8 +1066,17 @@ interface MejiroManuscriptEditorProps {
785
1066
  cover?: File | null;
786
1067
  /** Called whenever the cover changes. Enables controlled mode. */
787
1068
  onCoverChange?: (cover: File | null) => void;
788
- /** Initial chapters. */
789
- chapters?: ManuscriptEditorChapter[];
1069
+ /**
1070
+ * Chapters. Controlled when `onChaptersChange` is supplied; otherwise the
1071
+ * initial chapters. Without any, the editor starts with one chapter titled
1072
+ * and filled from the active message catalog.
1073
+ */
1074
+ chapters?: readonly ManuscriptChapter$1[];
1075
+ /**
1076
+ * Called with the whole chapter list after each chapter edit, addition,
1077
+ * removal or reorder. Enables controlled mode for `chapters`.
1078
+ */
1079
+ onChaptersChange?: (chapters: readonly ManuscriptChapter$1[]) => void;
790
1080
  /**
791
1081
  * Manuscript notation dialect. Drives the notation highlighter, the live
792
1082
  * preview, and the exported EPUB alike, so the whole editor interprets one
@@ -794,17 +1084,16 @@ interface MejiroManuscriptEditorProps {
794
1084
  */
795
1085
  dialect?: ManuscriptDialect;
796
1086
  /**
797
- * Called whenever the chapter draft settles (debounced by
798
- * {@link MejiroManuscriptEditorProps.autosaveDelay}). Use to persist drafts
799
- * to localStorage, IndexedDB, or upload to a server.
1087
+ * Called whenever the draft settles (debounced by
1088
+ * {@link MejiroManuscriptEditorProps.autosaveDebounceMs}). Use to persist
1089
+ * drafts to localStorage, IndexedDB, or upload to a server.
800
1090
  */
801
1091
  onAutosave?: (draft: ManuscriptAutosaveDraft) => void | Promise<void>;
802
1092
  /** Autosave debounce in milliseconds. @defaultValue 800 */
803
- autosaveDelay?: number;
1093
+ autosaveDebounceMs?: number;
804
1094
  /**
805
- * Props forwarded to the embedded {@link MejiroReader} preview. Lets
806
- * hosts customize subtitle / chapterNavMode / etc.; `manuscript`, `fonts`,
807
- * `chapter`, and `onChapterChange` remain driven by the editor.
1095
+ * Props forwarded to the embedded {@link MejiroReader} preview. The preview
1096
+ * header shows the work's title unless `title` is given here.
808
1097
  */
809
1098
  previewProps?: ManuscriptPreviewProps;
810
1099
  /**
@@ -815,8 +1104,8 @@ interface MejiroManuscriptEditorProps {
815
1104
  * references that must be fetched at export time.
816
1105
  */
817
1106
  assetResolver?: AssetResolver;
818
- /** Called after export completes. */
819
- onExport?: (buffer: ArrayBuffer) => void;
1107
+ /** Called with the exported bytes, before the download. */
1108
+ onExport?: (bytes: Uint8Array<ArrayBuffer>) => void;
820
1109
  /**
821
1110
  * Called when the EPUB export fails (asset resolution, cover reading, or
822
1111
  * packaging) or the preview reader fails. The same error is shown in the editor panel, so hosts only
@@ -831,14 +1120,138 @@ interface MejiroManuscriptEditorProps {
831
1120
  panelSide?: 'left' | 'right';
832
1121
  }
833
1122
  /** Manuscript-to-EPUB editor for author drafts from posting sites. */
834
- declare function MejiroManuscriptEditor({ fonts, title: titleProp, onTitleChange, author: authorProp, onAuthorChange, cover: coverProp, onCoverChange, chapters: initialChapters, dialect, onAutosave, autosaveDelay, previewProps, assetResolver, onExport, onError, panelSide, }: MejiroManuscriptEditorProps): ReactNode;
1123
+ declare function MejiroManuscriptEditor({ fonts, title: titleProp, onTitleChange, author: authorProp, onAuthorChange, cover: coverProp, onCoverChange, chapters: chaptersProp, onChaptersChange, dialect, onAutosave, autosaveDebounceMs, previewProps, assetResolver, onExport, onError, panelSide, }: MejiroManuscriptEditorProps): ReactNode;
1124
+
1125
+ /** Props for {@link MejiroEditor}. */
1126
+ interface MejiroEditorProps {
1127
+ /** EPUB URL, loaded on mount and whenever it changes. */
1128
+ epubUrl?: string;
1129
+ /**
1130
+ * Extra options merged into the `fetch` call that loads `epubUrl`. Useful
1131
+ * for sending bearer tokens or cookies (`credentials: 'include'`).
1132
+ */
1133
+ fetchOptions?: RequestInit;
1134
+ /**
1135
+ * Custom EPUB fetcher used in place of the global `fetch`; resolves to the
1136
+ * raw bytes. `options.signal` aborts when the load is superseded or the
1137
+ * editor unmounts. Overrides {@link MejiroEditorProps.fetchOptions} when set.
1138
+ */
1139
+ fetchEpub?: (url: string, options: FetchEpubOptions) => Promise<BinaryInput>;
1140
+ /** Font choices passed to the preview reader. */
1141
+ fonts?: readonly FontChoice[];
1142
+ /**
1143
+ * Allow editing paragraph text (the "Proofread" section). When `false` and
1144
+ * `enableRuby` is on, the paragraph text stays visible read-only so a ruby
1145
+ * base range can still be selected.
1146
+ * @defaultValue true
1147
+ */
1148
+ enableProofread?: boolean;
1149
+ /**
1150
+ * Allow editing ruby annotations (the "Ruby" section).
1151
+ * @defaultValue true
1152
+ */
1153
+ enableRuby?: boolean;
1154
+ /**
1155
+ * Allow inserting images into the EPUB (the "Images" section).
1156
+ * @defaultValue true
1157
+ */
1158
+ enableImages?: boolean;
1159
+ /**
1160
+ * Show the export button. SaaS publishers can hide it to restrict downloads
1161
+ * (e.g. server-side export only).
1162
+ * @defaultValue true
1163
+ */
1164
+ showExportButton?: boolean;
1165
+ /** What the export button produces and whether it is offered as a download. */
1166
+ exportPolicy?: ExportPolicy;
1167
+ /**
1168
+ * Resolves URL-only image assets (`url` set, `data` unset) into bytes at
1169
+ * export time. Forwarded to `editor.export()`. Use this to keep large image
1170
+ * bytes off the client during editing and only fetch them once when the
1171
+ * EPUB is assembled — e.g. signed S3 URLs that require custom auth headers.
1172
+ */
1173
+ assetResolver?: AssetResolver;
1174
+ /**
1175
+ * Archive resource limits applied while opening an EPUB. Raise them for
1176
+ * trusted, image-heavy books; tighten them for a public drop zone. Omitted
1177
+ * fields keep their `DEFAULT_EPUB_PARSE_LIMITS` value.
1178
+ */
1179
+ limits?: Partial<EpubParseLimits>;
1180
+ /**
1181
+ * Props forwarded to the embedded {@link MejiroReader} preview (theme,
1182
+ * options, chrome). The book, the chapter and its change handler, `fonts`
1183
+ * and `onError` remain driven by the editor.
1184
+ */
1185
+ previewProps?: ManuscriptPreviewProps;
1186
+ /** Called after an EPUB is loaded into the editor. */
1187
+ onLoad?: (editor: EditableEpub) => void;
1188
+ /** Called with the exported bytes, after `exportPolicy.transform`, before any download. */
1189
+ onExport?: (bytes: Uint8Array<ArrayBuffer>) => void;
1190
+ /**
1191
+ * Called when loading, image insertion, export or the preview reader fails.
1192
+ * The error is also shown in the editor until a later load, image insertion
1193
+ * or export succeeds.
1194
+ */
1195
+ onError?: (error: Error) => void;
1196
+ }
1197
+ /**
1198
+ * Restrictions on what {@link MejiroEditor}'s export produces. Applied in
1199
+ * this order: `watermark` (written into the exported copy only, never into the
1200
+ * edited document) → `transform` (replaces the bytes with its result) →
1201
+ * `download`.
1202
+ */
1203
+ interface ExportPolicy {
1204
+ /**
1205
+ * Offer the exported bytes as a browser download. With `false` the bytes are
1206
+ * still produced and `onExport` still fires — use it to ship the bytes
1207
+ * elsewhere (e.g. uploading to a backend).
1208
+ * @defaultValue true
1209
+ */
1210
+ download?: boolean;
1211
+ /**
1212
+ * Transforms the exported bytes before `onExport` and the download.
1213
+ * Typically a server round-trip that returns a DRM-wrapped EPUB.
1214
+ */
1215
+ transform?: (bytes: Uint8Array<ArrayBuffer>) => Uint8Array<ArrayBuffer> | Promise<Uint8Array<ArrayBuffer>>;
1216
+ /**
1217
+ * Visible watermark text. Each exported chapter starts with a paragraph
1218
+ * holding it after a `[mejiro-watermark]` prefix, so a downstream renderer
1219
+ * can theme it by that prefix. An empty string adds nothing.
1220
+ */
1221
+ watermark?: string;
1222
+ }
1223
+ /** EPUB editor UI for proofreading, ruby edits, image insertion, and export. */
1224
+ declare function MejiroEditor({ epubUrl, fetchOptions, fetchEpub, fonts, enableProofread, enableRuby, enableImages, showExportButton, exportPolicy, assetResolver, limits, previewProps, onLoad, onExport, onError, }: MejiroEditorProps): ReactNode;
1225
+
1226
+ /** Props for {@link MejiroImagePlaceholder}; other `div` attributes go to the root element. */
1227
+ interface MejiroImagePlaceholderProps extends ComponentPropsWithoutRef<'div'> {
1228
+ /** Position and size of the placeholder (px). */
1229
+ rect: ImageOverlayRect;
1230
+ /** Label inside the placeholder. @defaultValue `messages.imageButton` */
1231
+ label?: string;
1232
+ /** Pointer-down on the body (drag). */
1233
+ onOverlayPointerDown?: (e: PointerEvent) => void;
1234
+ /** Pointer-down on the resize handle. */
1235
+ onResizePointerDown?: (e: PointerEvent) => void;
1236
+ /** Triggered when the close button is pressed. */
1237
+ onClose?: () => void;
1238
+ }
1239
+ /**
1240
+ * Decorative image placeholder with drag and resize affordances, drawn where
1241
+ * an image overlay excludes text. Pair the pointer handlers with
1242
+ * {@link useImageOverlay}.
1243
+ */
1244
+ declare const MejiroImagePlaceholder: react.ForwardRefExoticComponent<MejiroImagePlaceholderProps & react.RefAttributes<HTMLDivElement>>;
835
1245
 
836
- /** Props for {@link MejiroNotationHighlighter}. */
837
- interface MejiroNotationHighlighterProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'value' | 'onChange'> {
1246
+ /**
1247
+ * Props for {@link MejiroNotationHighlighter}; other `textarea` attributes,
1248
+ * the native `onChange` included, go to the textarea.
1249
+ */
1250
+ interface MejiroNotationHighlighterProps extends Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'value'> {
838
1251
  /** Source manuscript text. */
839
1252
  value: string;
840
- /** Called whenever the textarea content changes. */
841
- onChange: (next: string) => void;
1253
+ /** Called with the new text whenever the textarea content changes, and when an IME composition ends. */
1254
+ onValueChange: (next: string) => void;
842
1255
  /** Manuscript notation dialect. @defaultValue `'mejiro'` */
843
1256
  dialect?: ManuscriptDialect;
844
1257
  /** Class applied to the wrapper element. */
@@ -859,8 +1272,8 @@ interface MejiroNotationHighlighterProps extends Omit<TextareaHTMLAttributes<HTM
859
1272
  */
860
1273
  declare const MejiroNotationHighlighter: react.ForwardRefExoticComponent<MejiroNotationHighlighterProps & react.RefAttributes<HTMLTextAreaElement>>;
861
1274
 
862
- /** Props for the MejiroPage component. */
863
- interface MejiroPageProps {
1275
+ /** Props for the MejiroPage component; other `div` attributes go to the root element. */
1276
+ interface MejiroPageProps extends ComponentPropsWithoutRef<'div'> {
864
1277
  /** Render page data from `buildRenderPage()`. */
865
1278
  page: RenderPage;
866
1279
  /** Additional CSS class name for the root element. */
@@ -874,25 +1287,36 @@ interface MejiroPageProps {
874
1287
  * Converts a `RenderPage` data structure into DOM elements using
875
1288
  * `mejiro-` prefixed CSS classes for layout.
876
1289
  */
877
- declare function MejiroPage({ page, className, style }: MejiroPageProps): ReactNode;
1290
+ declare const MejiroPage: react.ForwardRefExoticComponent<MejiroPageProps & react.RefAttributes<HTMLDivElement>>;
878
1291
 
879
- /** Props for {@link MejiroPageIndicator}. */
880
- interface MejiroPageIndicatorProps {
881
- /** Current spread (1-based). */
882
- current: number;
883
- /** Total number of spreads. */
884
- total: number;
1292
+ /** Props for {@link MejiroPageIndicator}; other `div` attributes go to the root element. */
1293
+ interface MejiroPageIndicatorProps extends ComponentPropsWithoutRef<'div'> {
1294
+ /** Zero-based navigation index, as `useSpread` reports it in `index`. Shown one-based. */
1295
+ index: number;
1296
+ /** Number of navigation positions, as `useSpread` reports it in `count`. */
1297
+ count: number;
885
1298
  }
886
- /** Displays "current / total" spread position under the book. */
887
- declare function MejiroPageIndicator({ current, total }: MejiroPageIndicatorProps): ReactNode;
1299
+ /** Displays the one-based position out of the total, e.g. `3 / 12`, under the book. */
1300
+ declare const MejiroPageIndicator: react.ForwardRefExoticComponent<MejiroPageIndicatorProps & react.RefAttributes<HTMLDivElement>>;
888
1301
 
889
- /** Props for the MejiroPageView component. */
890
- interface MejiroPageViewProps {
1302
+ /** Props for the MejiroPageView component; other `div` attributes go to the root element. */
1303
+ interface MejiroPageViewProps extends ComponentPropsWithoutRef<'div'> {
891
1304
  /** Page result from {@link ChapterLayout.getSpread} or {@link ChapterLayout.getPage}. */
892
1305
  result: PageResult;
893
- /** CSS font family for slot-based rendering (used when images are present). */
1306
+ /**
1307
+ * Layout the page comes from. When given, its `typography` supplies
1308
+ * `fontFamily` and `lineSpacing` when they are left unset.
1309
+ */
1310
+ layout?: ChapterLayout;
1311
+ /**
1312
+ * CSS font family for slot-based rendering (used when images are present).
1313
+ * Defaults to `layout.typography.fontFamily`.
1314
+ */
894
1315
  fontFamily?: FontFamily;
895
- /** Line spacing multiplier for slot-based rendering (used when images are present). */
1316
+ /**
1317
+ * Line spacing multiplier for slot-based rendering (used when images are
1318
+ * present). Defaults to `layout.typography.lineSpacing`.
1319
+ */
896
1320
  lineSpacing?: number;
897
1321
  /**
898
1322
  * Force slot-based rendering even when `result.hasImages` is false.
@@ -918,77 +1342,26 @@ interface MejiroPageViewProps {
918
1342
  * <MejiroPageView result={spread.right} fontFamily="serif" lineSpacing={1.8} />
919
1343
  * ```
920
1344
  */
921
- declare function MejiroPageView({ result, fontFamily, lineSpacing, slotMode, className, style, }: MejiroPageViewProps): ReactNode;
1345
+ declare const MejiroPageView: react.ForwardRefExoticComponent<MejiroPageViewProps & react.RefAttributes<HTMLDivElement>>;
922
1346
 
923
- /** A single image overlay placed on a specific spread. */
924
- interface MultiImageItem {
925
- /** Stable ID for the overlay (auto-assigned on add). */
926
- id: string;
927
- /** Position and size in pixels, relative to the right page. */
928
- rect: ImageRect;
929
- }
930
- /** Options for {@link useMultiImageOverlay}. */
931
- interface UseMultiImageOverlayOptions {
932
- /** Default image width. @defaultValue 120 */
933
- defaultWidth?: number;
934
- /** Default image height. @defaultValue 160 */
935
- defaultHeight?: number;
936
- /** Default x. @defaultValue 80 */
937
- defaultX?: number;
938
- /** Default y. @defaultValue 100 */
939
- defaultY?: number;
940
- /** Margin around each image, in pixels. Defaults to the book's font size. */
941
- margin?: number;
942
- /** Called after every reflow caused by adding/removing/moving an image. */
943
- onUpdate?: (spread: SpreadResult) => void;
944
- }
945
- /** Return value of {@link useMultiImageOverlay}. */
946
- interface UseMultiImageOverlayReturn {
1347
+ /** Props for {@link MejiroScrollView}; other `div` attributes go to the scroll container. */
1348
+ interface MejiroScrollViewProps extends ComponentPropsWithoutRef<'div'> {
947
1349
  /**
948
- * Map of all images keyed by spread index. A new map whenever the images or
949
- * the margin they are applied with change, and a replacement layout has them
950
- * before any view renders from it, so this map and the layout together key
951
- * anything derived from the layout.
1350
+ * Layout containing the pages to render. Its `geometry` and `typography`
1351
+ * supply every page-size and font prop left unset.
952
1352
  */
953
- imagesBySpread: Map<number, MultiImageItem[]>;
954
- /** Images on the current spread; a new array whenever `imagesBySpread` changes. */
955
- currentImages: MultiImageItem[];
956
- /** Whether any spread has at least one image. */
957
- hasImages: boolean;
958
- /** Add a new image on the current spread. */
959
- addImage: (overrides?: Partial<ImageRect>) => MultiImageItem;
960
- /** Remove the image with the given id. */
961
- removeImage: (id: string) => void;
962
- /** Replace an image's rectangle. */
963
- updateImage: (id: string, rect: Partial<ImageRect>) => void;
964
- /** Remove every image (or only those on `spreadIdx` if given). */
965
- clearImages: (spreadIdx?: number) => void;
966
- /** Pointer-down handler for the overlay body (drag). */
967
- onOverlayPointerDown: (id: string, e: PointerEvent) => void;
968
- /** Pointer-down handler for the resize handle. */
969
- onResizePointerDown: (id: string, e: PointerEvent) => void;
970
- }
971
- /**
972
- * React hook that manages multiple draggable/resizable image overlays
973
- * per spread with automatic reflow via {@link ChapterLayout.setImages}.
974
- */
975
- declare function useMultiImageOverlay(layout: ChapterLayout | null, spreadIdx: number, options?: UseMultiImageOverlayOptions): UseMultiImageOverlayReturn;
976
-
977
- /** Props for {@link MejiroScrollView}. */
978
- interface MejiroScrollViewProps {
979
- /** Layout containing the pages to render. */
980
1353
  layout: ChapterLayout;
981
- /** Page width in px. */
982
- pageWidth: number;
983
- /** Page height in px. */
984
- pageHeight: number;
985
- /** Content area height (px). */
986
- contentHeight: number;
987
- /** CSS font family applied to the content. */
1354
+ /** Page width in px. Defaults to `layout.geometry.pageWidth`. */
1355
+ pageWidth?: number;
1356
+ /** Page height in px. Defaults to `layout.geometry.pageHeight`. */
1357
+ pageHeight?: number;
1358
+ /** Content area height (px). Defaults to `layout.geometry.contentHeight`. */
1359
+ contentHeight?: number;
1360
+ /** CSS font family applied to the content. Defaults to `layout.typography.fontFamily`. */
988
1361
  fontFamily?: FontFamily;
989
- /** Font size override (px). */
1362
+ /** Font size (px). Defaults to `layout.typography.fontSize`. */
990
1363
  fontSize?: number;
991
- /** Line spacing multiplier. */
1364
+ /** Line spacing multiplier. Defaults to `layout.typography.lineSpacing`. */
992
1365
  lineSpacing?: number;
993
1366
  /** Force slot-based rendering on every page. */
994
1367
  slotMode?: boolean;
@@ -996,7 +1369,7 @@ interface MejiroScrollViewProps {
996
1369
  * Visible page index reported to the parent as the user scrolls. The page
997
1370
  * with the largest intersection with the viewport is treated as visible.
998
1371
  */
999
- onVisiblePageChange?: (pageIdx: number, source: 'user' | 'programmatic') => void;
1372
+ onVisiblePageChange?: (pageIndex: number, source: 'user' | 'programmatic') => void;
1000
1373
  /**
1001
1374
  * Target page to scroll into view. When set, the view scrolls so that the
1002
1375
  * matching page sits at the top of the scroll container.
@@ -1007,22 +1380,20 @@ interface MejiroScrollViewProps {
1007
1380
  /**
1008
1381
  * Selection rectangles to highlight. Compute via
1009
1382
  * {@link ChapterLayout.selectionRects}; each entry is painted on the page its
1010
- * `pageIdx` addresses. An entry may carry a `color` fill.
1383
+ * `pageIndex` addresses. An entry may carry a `color` fill.
1011
1384
  */
1012
- selectionRects?: readonly (AnchorRect & {
1013
- color?: string;
1014
- })[];
1385
+ selectionRects?: readonly SelectionRect[];
1015
1386
  /** Zero-based spread whose pages carry {@link MejiroScrollViewProps.images}. */
1016
- spreadIdx?: number;
1387
+ spreadIndex?: number;
1017
1388
  /**
1018
- * Image overlays on the spread at {@link MejiroScrollViewProps.spreadIdx}.
1389
+ * Image overlays on the spread at {@link MejiroScrollViewProps.spreadIndex}.
1019
1390
  * Each renders on the page whose area contains its centre (see
1020
1391
  * `overlayPageSide`), matching {@link MejiroSpread}'s single-page routing.
1021
1392
  * The layout reflows in place when its images change, so a new array here is
1022
- * also what re-reads the pages; `useMultiImageOverlay`'s `currentImages`
1393
+ * also what re-reads the pages; `useImageOverlay`'s `currentImages`
1023
1394
  * provides one on every change.
1024
1395
  */
1025
- images?: MultiImageItem[];
1396
+ images?: readonly MultiImageItem[];
1026
1397
  /**
1027
1398
  * Which pages print their page number in the running head, by the same rule
1028
1399
  * as the paginated reader. @defaultValue 'both'
@@ -1041,10 +1412,10 @@ interface MejiroScrollViewProps {
1041
1412
  * detected via `IntersectionObserver` and reported through
1042
1413
  * {@link MejiroScrollViewProps.onVisiblePageChange}.
1043
1414
  */
1044
- declare function MejiroScrollView({ layout, pageWidth, pageHeight, contentHeight, fontFamily, fontSize, lineSpacing, slotMode, onVisiblePageChange, scrollToPage, pageGap, selectionRects, spreadIdx, images, pageNumbers, onImagePointerDown, onImageResizePointerDown, onImageClose, }: MejiroScrollViewProps): ReactNode;
1415
+ declare const MejiroScrollView: react.ForwardRefExoticComponent<MejiroScrollViewProps & react.RefAttributes<HTMLDivElement>>;
1045
1416
 
1046
- /** Props for {@link MejiroSelectionLayer}. */
1047
- interface MejiroSelectionLayerProps {
1417
+ /** Props for {@link MejiroSelectionLayer}; other `div` attributes go to the root element. */
1418
+ interface MejiroSelectionLayerProps extends ComponentPropsWithoutRef<'div'> {
1048
1419
  /**
1049
1420
  * Spread-local rectangles to highlight. Compute these via
1050
1421
  * {@link ChapterLayout.selectionRects} and pass the full array; the layer
@@ -1054,9 +1425,7 @@ interface MejiroSelectionLayerProps {
1054
1425
  * fill. Entries without one take the fill from the shipped stylesheet
1055
1426
  * (`.mejiro-selection-rect`, themeable via `--mejiro-selection-bg`).
1056
1427
  */
1057
- rects: readonly (AnchorRect & {
1058
- color?: string;
1059
- })[];
1428
+ rects: readonly SelectionRect[];
1060
1429
  /** Which page-side this layer is rendered into. */
1061
1430
  side: 'right' | 'left';
1062
1431
  /** Optional class added to the outer wrapper. */
@@ -1069,289 +1438,144 @@ interface MejiroSelectionLayerProps {
1069
1438
  rectStyle?: CSSProperties;
1070
1439
  }
1071
1440
  /**
1072
- * Renders a selection-highlight overlay inside a page-content container.
1073
- *
1074
- * Use as a child of `.mejiro-reader-page-clip` (or any element that shares the
1075
- * page's content coordinate frame). The layer is pointer-transparent so it
1076
- * does not interfere with selection drag on the page below.
1077
- */
1078
- declare function MejiroSelectionLayer({ rects, side, className, style, rectClassName, rectStyle, }: MejiroSelectionLayerProps): ReactNode;
1079
-
1080
- /** A single volume in a multi-book library. */
1081
- interface VolumeInfo<T = unknown> {
1082
- /** Stable identifier (e.g. ISBN, slug, internal ID). */
1083
- id: string;
1084
- /** Human-readable title shown in pickers. */
1085
- label: string;
1086
- /** Optional author / byline. */
1087
- author?: string;
1088
- /** Optional cover image URL. */
1089
- cover?: string;
1090
- /** Free-form metadata attached by the host. */
1091
- meta?: T;
1092
- }
1093
- /** Options for {@link useLibrary}. */
1094
- interface UseLibraryOptions<T = unknown> {
1095
- /** The volumes available to the reader. */
1096
- volumes: readonly VolumeInfo<T>[];
1097
- /** ID of the volume to start on. Defaults to the first entry. */
1098
- initialVolumeId?: string;
1099
- /** Called whenever the active volume changes. */
1100
- onChange?: (volume: VolumeInfo<T>) => void;
1101
- }
1102
- /** Return value of {@link useLibrary}. */
1103
- interface UseLibraryReturn<T = unknown> {
1104
- /** The list of volumes (same reference as the options input). */
1105
- list: readonly VolumeInfo<T>[];
1106
- /** Currently active volume, or `null` for an empty library. */
1107
- current: VolumeInfo<T> | null;
1108
- /** Index of the current volume, or `-1` for an empty library. */
1109
- currentIndex: number;
1110
- /** Advance to the next volume in the list (no-op at the end). */
1111
- next(): void;
1112
- /** Go back to the previous volume in the list (no-op at the start). */
1113
- prev(): void;
1114
- /** Jump to a volume by ID. No-op when the ID is not in the list. */
1115
- goTo(id: string): void;
1116
- }
1117
- /**
1118
- * Headless hook for managing a multi-volume reading session. Pair it with
1119
- * {@link MejiroReader} (driving its `epub` / `epubUrl` prop) or
1120
- * {@link MejiroShelf} (visual picker).
1121
- */
1122
- declare function useLibrary<T = unknown>(options: UseLibraryOptions<T>): UseLibraryReturn<T>;
1123
-
1124
- /** Props for {@link MejiroShelf}. */
1125
- interface MejiroShelfProps<T = unknown> {
1126
- /** Volumes to display. */
1127
- volumes: readonly VolumeInfo<T>[];
1128
- /** ID of the currently active volume (highlighted in the grid). */
1129
- currentId?: string;
1130
- /** Called when the user picks a volume. */
1131
- onSelect?: (volume: VolumeInfo<T>) => void;
1132
- /** Heading shown above the grid. Defaults to the active message catalog's `shelfTitle`. */
1133
- title?: string;
1134
- }
1135
- /**
1136
- * Visual bookshelf picker. Renders each volume as a card with cover, label,
1137
- * and author. Pair with {@link useLibrary} to drive selection.
1138
- */
1139
- declare function MejiroShelf<T = unknown>({ volumes, currentId, onSelect, title, }: MejiroShelfProps<T>): ReactNode;
1140
-
1141
- /** Props for {@link MejiroSpread}. */
1142
- interface MejiroSpreadProps {
1143
- /** Spread data to render. */
1144
- spread: SpreadResult;
1145
- /** Page width (px). */
1146
- pageWidth: number;
1147
- /** Page height (px). */
1148
- pageHeight: number;
1149
- /** Content area height (px). */
1150
- contentHeight: number;
1151
- /** CSS font family applied to the content. */
1152
- fontFamily?: FontFamily;
1153
- /** Font size override (px). */
1154
- fontSize?: number;
1155
- /** Line spacing multiplier (slot mode). */
1156
- lineSpacing?: number;
1157
- /** Animate the page turn. */
1158
- turning?: boolean;
1159
- /** Header data for the right page. */
1160
- rightHeader?: PageHeaderData;
1161
- /** Header data for the left page. */
1162
- leftHeader?: PageHeaderData;
1163
- /**
1164
- * Image overlays on the current spread, relative to the right page. With
1165
- * {@link MejiroSpreadProps.singlePage}, only those whose centre lies on the
1166
- * page shown are drawn.
1167
- */
1168
- images?: MultiImageItem[];
1169
- /**
1170
- * Force slot-mode rendering on both pages. When omitted, each page falls
1171
- * through to {@link MejiroPageView}'s automatic choice: slot mode for pages
1172
- * with images, native `writing-mode: vertical-rl` otherwise.
1173
- */
1174
- slotMode?: boolean;
1175
- /** Custom renderer for per-page headers. */
1176
- renderPageHeader?: (side: 'right' | 'left', header: PageHeaderData) => ReactNode;
1177
- /** Custom indicator placed inside the spread (e.g. {@link MejiroPageIndicator}). */
1178
- indicator?: ReactNode;
1179
- /** Called when the previous-page zone is clicked. */
1180
- onPrev?: () => void;
1181
- /** Called when the next-page zone is clicked. */
1182
- onNext?: () => void;
1183
- /** Pointer-down on an image overlay. */
1184
- onImagePointerDown?: (id: string, e: PointerEvent) => void;
1185
- /** Pointer-down on an image resize handle. */
1186
- onImageResizePointerDown?: (id: string, e: PointerEvent) => void;
1187
- /** Image overlay close button. */
1188
- onImageClose?: (id: string) => void;
1189
- /**
1190
- * Resolves a spread-local pixel coordinate to an in-chapter anchor.
1191
- * Typically `(x, y) => layout.anchorAtCoord(spreadIdx, x, y)`. When provided
1192
- * together with {@link MejiroSpreadProps.onSelectionChange}, the spread
1193
- * enables pointer-drag selection on the page content area.
1194
- */
1195
- anchorAtCoord?: (x: number, y: number) => InChapterAnchor | null;
1196
- /**
1197
- * Zero-based index of the spread being rendered. Rectangles passed through
1198
- * {@link MejiroSpreadProps.selectionRects} carry spread-local coordinates,
1199
- * so this is what scopes them to this spread. When omitted, every supplied
1200
- * rectangle is painted.
1201
- */
1202
- spreadIdx?: number;
1203
- /**
1204
- * Selection rectangles to render as a highlight overlay. Compute via
1205
- * {@link ChapterLayout.selectionRects}. Entries whose `spreadIdx` differs
1206
- * from {@link MejiroSpreadProps.spreadIdx} are ignored. An entry may carry
1207
- * a `color` that becomes that rectangle's fill.
1208
- */
1209
- selectionRects?: readonly (AnchorRect & {
1210
- color?: string;
1211
- })[];
1212
- /**
1213
- * Called when the user drags-selects text on the spread.
1214
- * Called with `null` on a single-click (caret) where start equals end.
1215
- */
1216
- onSelectionChange?: (range: AnchorRange | null) => void;
1217
- /**
1218
- * Render only one page of the spread (centered), chosen by
1219
- * {@link MejiroSpreadProps.singleSide}. Use this for portrait viewports or
1220
- * when a reader explicitly opts into single-page mode.
1221
- */
1222
- singlePage?: boolean;
1223
- /**
1224
- * Page of the spread shown when {@link MejiroSpreadProps.singlePage} is set
1225
- * (`useSpread` reports it as `singleSide`). @defaultValue 'right'
1226
- */
1227
- singleSide?: 'right' | 'left';
1228
- /**
1229
- * Called when the user makes a quick swipe gesture on the spread.
1230
- * `direction === 'next'` means the user swiped from right to left (the
1231
- * natural direction in vertical-rl for advancing).
1232
- */
1233
- onSwipe?: (direction: 'next' | 'prev') => void;
1234
- /** Called when the user taps the center area without swiping. */
1235
- onSurfaceTap?: () => void;
1236
- }
1237
- /**
1238
- * Renders a two-page spread (or one page of it in single-page mode) with the
1239
- * book frame, page chrome, navigation zones, and image overlays. Used inside
1240
- * `mejiro-reader-surface`.
1441
+ * Renders a selection-highlight overlay inside a page-content container.
1442
+ *
1443
+ * Use as a child of `.mejiro-reader-page-clip` (or any element that shares the
1444
+ * page's content coordinate frame). The layer is pointer-transparent so it
1445
+ * does not interfere with selection drag on the page below.
1446
+ */
1447
+ declare const MejiroSelectionLayer: react.ForwardRefExoticComponent<MejiroSelectionLayerProps & react.RefAttributes<HTMLDivElement>>;
1448
+
1449
+ /** A single volume in a multi-book library. */
1450
+ interface VolumeInfo<T = unknown> {
1451
+ /** Stable identifier (e.g. ISBN, slug, internal ID). */
1452
+ id: string;
1453
+ /** Human-readable title shown in pickers. */
1454
+ label: string;
1455
+ /** Optional author / byline. */
1456
+ author?: string;
1457
+ /** Optional cover image URL. */
1458
+ cover?: string;
1459
+ /** Free-form metadata attached by the host. */
1460
+ meta?: T;
1461
+ }
1462
+ /** Options for {@link useLibrary}. */
1463
+ interface UseLibraryOptions<T = unknown> {
1464
+ /** The volumes available to the reader. */
1465
+ volumes: readonly VolumeInfo<T>[];
1466
+ /** ID of the volume to start on. Defaults to the first entry. */
1467
+ initialVolumeId?: string;
1468
+ /** Called with the new active volume whenever it changes. */
1469
+ onVolumeChange?: (volume: VolumeInfo<T>) => void;
1470
+ }
1471
+ /** Return value of {@link useLibrary}. */
1472
+ interface UseLibraryReturn<T = unknown> {
1473
+ /** The list of volumes (same reference as the options input). */
1474
+ list: readonly VolumeInfo<T>[];
1475
+ /** Currently active volume, or `null` for an empty library. */
1476
+ volume: VolumeInfo<T> | null;
1477
+ /** Index of the active volume in the list, or `-1` for an empty library. */
1478
+ volumeIndex: number;
1479
+ /** Advance to the next volume in the list (no-op at the end). */
1480
+ next(): void;
1481
+ /** Go back to the previous volume in the list (no-op at the start). */
1482
+ prev(): void;
1483
+ /** Make the volume with the given ID active. No-op when the ID is not in the list. */
1484
+ selectVolume(id: string): void;
1485
+ }
1486
+ /**
1487
+ * Headless hook for managing a multi-volume reading session. Pair it with
1488
+ * {@link MejiroReader} (driving its `epub` / `epubUrl` prop) or
1489
+ * {@link MejiroShelf} (visual picker).
1490
+ */
1491
+ declare function useLibrary<T = unknown>(options: UseLibraryOptions<T>): UseLibraryReturn<T>;
1492
+
1493
+ /**
1494
+ * Props for {@link MejiroShelf}; other `section` attributes go to the root
1495
+ * element, except `title`, which here is the heading text.
1496
+ */
1497
+ interface MejiroShelfProps<T = unknown> extends ComponentPropsWithoutRef<'section'> {
1498
+ /** Volumes to display. */
1499
+ volumes: readonly VolumeInfo<T>[];
1500
+ /** ID of the currently active volume (highlighted in the grid). */
1501
+ currentId?: string;
1502
+ /** Called with the volume the user picks. */
1503
+ onVolumeSelect?: (volume: VolumeInfo<T>) => void;
1504
+ /** Heading shown above the grid. Defaults to the active message catalog's `shelfTitle`. */
1505
+ title?: string;
1506
+ }
1507
+ /**
1508
+ * Visual bookshelf picker. Renders each volume as a card with cover, label,
1509
+ * and author. Pair with {@link useLibrary} to drive selection.
1241
1510
  */
1242
- 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;
1511
+ declare const MejiroShelf: <T = unknown>(props: MejiroShelfProps<T> & RefAttributes<HTMLElement>) => ReactNode;
1243
1512
 
1244
- /** Props for {@link MejiroStats}. */
1245
- interface MejiroStatsProps {
1513
+ /** Props for {@link MejiroStats}; other `span` attributes go to the root element. */
1514
+ interface MejiroStatsProps extends ComponentPropsWithoutRef<'span'> {
1246
1515
  /** Chapter currently displayed. */
1247
1516
  chapter: EpubChapter | null;
1248
- /** Total page count for the current layout. */
1249
- totalPages: number;
1517
+ /** Number of pages in the current layout. */
1518
+ pageCount: number;
1250
1519
  /** Most recent layout time in ms. */
1251
1520
  elapsedMs: number;
1252
1521
  /** Optional label for the current font (e.g. "Noto Serif JP 16px"). */
1253
1522
  fontLabel?: string;
1254
1523
  /** Show estimated reading time in the stats line. @defaultValue false */
1255
1524
  showReadingTime?: boolean;
1256
- /** Characters-per-minute used for the reading-time estimate. @defaultValue 600 */
1257
- cpm?: number;
1258
- /** Locale used to format the reading-time label. @defaultValue 'ja' */
1259
- readingTimeLocale?: 'ja' | 'en';
1525
+ /** Characters per minute used for the reading-time estimate. @defaultValue 600 */
1526
+ charsPerMinute?: number;
1527
+ /**
1528
+ * Locale whose catalog spells the reading-time units. Defaults to the
1529
+ * catalog of the nearest `MejiroI18nProvider`.
1530
+ */
1531
+ locale?: MejiroLocale;
1260
1532
  }
1261
1533
  /** Compact reading stats line. */
1262
- declare function MejiroStats({ chapter, totalPages, elapsedMs, fontLabel, showReadingTime, cpm, readingTimeLocale, }: MejiroStatsProps): ReactNode;
1534
+ declare const MejiroStats: react.ForwardRefExoticComponent<MejiroStatsProps & react.RefAttributes<HTMLSpanElement>>;
1263
1535
 
1264
- /** Props for {@link MejiroToc}. */
1265
- interface MejiroTocProps {
1536
+ /**
1537
+ * Props for {@link MejiroToc}; other `nav` attributes go to the root element,
1538
+ * except `title`, which here is the heading text.
1539
+ */
1540
+ interface MejiroTocProps extends ComponentPropsWithoutRef<'nav'> {
1266
1541
  /** EPUB whose chapter list is rendered. */
1267
1542
  epub: EpubBook;
1268
1543
  /** Current reading anchor; the matching chapter is highlighted. */
1269
1544
  currentAnchor?: ReadingAnchor | null;
1270
1545
  /** Show a search input that filters chapter titles. @defaultValue false */
1271
- searchable?: boolean;
1546
+ showSearch?: boolean;
1272
1547
  /** Heading displayed above the list. Defaults to the active message catalog's `tocTitle`. */
1273
1548
  title?: string;
1274
1549
  /** Placeholder shown in the search input. Defaults to the active message catalog's `tocSearchPlaceholder`. */
1275
1550
  searchPlaceholder?: string;
1276
- /** Called when the user picks a chapter. */
1277
- onSelect?: (chapter: number) => void;
1278
- }
1279
- /**
1280
- * Searchable, current-anchor-aware table of contents. Long-form replacement
1281
- * for {@link MejiroChapterNav}. Supersedes the older `MejiroChapterNav`
1282
- * for new code — the panel variant remains for backwards compatibility.
1283
- */
1284
- declare function MejiroToc({ epub, currentAnchor, searchable, title, searchPlaceholder, onSelect, }: MejiroTocProps): ReactNode;
1285
-
1286
- /** Minimal storage interface required by {@link useAnnotations}. */
1287
- type AnnotationsStorage = MejiroStorage;
1288
- /** Options for {@link useAnnotations}. */
1289
- interface UseAnnotationsOptions {
1290
- /** Storage key — usually scoped per-book (e.g. `mejiro:annotations:${bookId}`). */
1291
- key: string;
1292
- /**
1293
- * Storage backend. Defaults to `window.localStorage` when available.
1294
- * SSR consumers can omit this and the hook will fall back to in-memory.
1295
- * The latest reference is always used, so an object literal recreated on
1296
- * every render is safe — only `key` triggers a re-hydration.
1297
- */
1298
- storage?: AnnotationsStorage;
1299
- /**
1300
- * Debounce delay (ms): the latest value is written this long after the last change. A pending write is flushed on page hide, key change and unmount. @defaultValue 250
1301
- */
1302
- throttleMs?: number;
1303
- /**
1304
- * Called immediately after each mutation (`add` / `remove` / `update` /
1305
- * `clear`) with the new annotation list. Use to mirror state to a server
1306
- * — `storage` only covers the local persistence side. Not invoked on
1307
- * initial hydration or when the `key` changes.
1308
- */
1309
- onChange?: (next: readonly Annotation[]) => void;
1310
- }
1311
- /** Return value of {@link useAnnotations}. */
1312
- interface UseAnnotationsReturn {
1313
- /** Currently saved annotations, sorted by chapter / paragraph / charIndex. */
1314
- annotations: readonly Annotation[];
1315
- /** Add a new annotation. `id` and `createdAt` are auto-filled when omitted. */
1316
- add(input: Omit<Annotation, 'id' | 'createdAt'> & Partial<Pick<Annotation, 'id' | 'createdAt'>>): Annotation;
1317
- /** Remove an annotation by id. */
1318
- remove(id: string): void;
1319
- /** Patch an annotation by id. Pass `undefined` on a field to leave it untouched. */
1320
- update(id: string, patch: Partial<Omit<Annotation, 'id'>>): void;
1321
- /** Remove all annotations. */
1322
- clear(): void;
1551
+ /** Called with the zero-based index of the chapter the user picks. */
1552
+ onChapterSelect?: (chapterIndex: number) => void;
1323
1553
  }
1324
1554
  /**
1325
- * Persistence helper for reader annotations. Returns the saved annotation list
1326
- * plus add / update / remove helpers. Pair with the `annotations` prop on
1327
- * {@link MejiroReader} to render the corresponding highlight rectangles.
1328
- *
1329
- * ```tsx
1330
- * const { annotations, add } = useAnnotations({ key: `mejiro:ann:${bookId}` });
1331
- * const handle = useRef<MejiroReaderHandle>(null);
1332
- *
1333
- * // Save a highlight at the current visible range:
1334
- * const range = handle.current?.getVisibleRange();
1335
- * if (range) add({ chapter: 0, start: range.start, end: range.end, color: 'yellow' });
1336
- *
1337
- * <MejiroReader epub={book} annotations={annotations} ref={handle} />
1338
- * ```
1555
+ * Searchable table of contents that highlights the chapter of the current
1556
+ * reading anchor. For a compact chapter picker, use {@link MejiroChapterNav}.
1339
1557
  */
1340
- declare function useAnnotations(options: UseAnnotationsOptions): UseAnnotationsReturn;
1558
+ declare const MejiroToc: react.ForwardRefExoticComponent<MejiroTocProps & react.RefAttributes<HTMLElement>>;
1341
1559
 
1342
1560
  /** Options for {@link useChapterLayout}. */
1343
1561
  interface UseChapterLayoutOptions {
1562
+ /** The parsed EPUB whose chapter is laid out; `null` lays nothing out. */
1563
+ epub: EpubBook | null;
1564
+ /** Zero-based index of the chapter to lay out. */
1565
+ chapterIndex: number;
1566
+ /** The reading surface the pages are sized against. */
1567
+ surface: RefObject<HTMLElement | null>;
1344
1568
  /** Observe the surface element for size changes and re-flow. @defaultValue true */
1345
1569
  enableResize?: boolean;
1346
1570
  /** Debounce window (ms) applied to size-triggered re-flows. @defaultValue 120 */
1347
- resizeDebounce?: number;
1571
+ resizeDebounceMs?: number;
1348
1572
  /**
1349
1573
  * Resolver for page-geometry overrides forwarded to
1350
- * {@link MejiroBook.computePageSize} — e.g. to shrink the reserved
1574
+ * {@link MejiroBook.fitPageSize} — e.g. to shrink the reserved
1351
1575
  * `gutterOffset` / `headerOffset` so the pages fill their frame. Called on
1352
1576
  * every (re)layout so it may return a value that changes over time.
1353
1577
  */
1354
- pageGeometry?: () => ComputePageSizeOptions | undefined;
1578
+ pageGeometry?: () => FitPageSizeOptions | undefined;
1355
1579
  /**
1356
1580
  * Capture the reading position from the outgoing layout, just before a
1357
1581
  * **reflow** (non-blank) re-layout replaces it. The anchor is held in
@@ -1374,8 +1598,11 @@ interface UseChapterLayoutOptions {
1374
1598
  */
1375
1599
  onError?: (error: Error) => void;
1376
1600
  }
1377
- /** Options for {@link UseChapterLayoutReturn.recompute}. */
1378
- interface RecomputeOptions {
1601
+ /**
1602
+ * Options for the `recompute` of {@link useChapterLayout} and
1603
+ * {@link useManuscriptLayout}.
1604
+ */
1605
+ interface ChapterLayoutRecomputeOptions {
1379
1606
  /**
1380
1607
  * Blank the current layout while the new one is computed. Use for content
1381
1608
  * changes (chapter / book swaps); skip for size or option changes so the
@@ -1384,20 +1611,11 @@ interface RecomputeOptions {
1384
1611
  */
1385
1612
  blank?: boolean;
1386
1613
  }
1387
- /** Page dimensions returned by {@link MejiroBook.computePageSize}. */
1388
- interface PageDimensions {
1389
- /** Width of one page, in CSS pixels. */
1390
- pageWidth: number;
1391
- /** Height of one page, in CSS pixels. */
1392
- pageHeight: number;
1393
- /** Height of the text area inside a page, in CSS pixels. */
1394
- contentHeight: number;
1395
- }
1396
1614
  /** Return value of {@link useChapterLayout}. */
1397
1615
  interface UseChapterLayoutReturn {
1398
1616
  /** Current layout, or `null` before the first computation. */
1399
1617
  layout: ChapterLayout | null;
1400
- /** Page width in pixels. */
1618
+ /** Page width in pixels, as {@link MejiroBook.fitPageSize} measured it. */
1401
1619
  pageWidth: number;
1402
1620
  /** Page height in pixels. */
1403
1621
  pageHeight: number;
@@ -1405,11 +1623,16 @@ interface UseChapterLayoutReturn {
1405
1623
  contentHeight: number;
1406
1624
  /** Most recent layout time (ms). */
1407
1625
  elapsedMs: number;
1626
+ /**
1627
+ * Failure of the most recent layout of the current source, whichever path
1628
+ * started it; `null` once a layout succeeds or the source changes.
1629
+ */
1630
+ error: Error | null;
1408
1631
  /**
1409
1632
  * Force a fresh layout computation. A failure goes to `onError` when one is
1410
1633
  * supplied; otherwise the returned promise rejects.
1411
1634
  */
1412
- recompute: (opts?: RecomputeOptions) => Promise<void>;
1635
+ recompute: (opts?: ChapterLayoutRecomputeOptions) => Promise<void>;
1413
1636
  /**
1414
1637
  * Anchor captured by `capturePosition` before the most recent reflow,
1415
1638
  * awaiting restoration into the new layout. `restorePosition`, when given,
@@ -1429,9 +1652,11 @@ interface UseChapterLayoutReturn {
1429
1652
  *
1430
1653
  * Re-layouts whenever `book`, `epub`, or `chapterIndex` change, against the
1431
1654
  * dimensions of `surface`. The `surface` element is observed with a
1432
- * {@link ResizeObserver}: the first observation runs immediately (so a reader
1433
- * mounted before its container had a final box is still sized correctly on
1434
- * first paint), and later size changes trigger a debounced **full re-layout**.
1655
+ * {@link ResizeObserver}: the first observation of a positive box runs
1656
+ * immediately (so a reader mounted before its container had a final box is
1657
+ * still sized correctly on first paint), and later size changes trigger a
1658
+ * debounced **full re-layout**. While the surface measures zero wide or high,
1659
+ * nothing is measured or laid out.
1435
1660
  *
1436
1661
  * Size and option changes lay the chapter out again rather than calling
1437
1662
  * `ChapterLayout.resize()`, so page geometry is re-derived from the surface and
@@ -1445,23 +1670,36 @@ interface UseChapterLayoutReturn {
1445
1670
  * composable.
1446
1671
  *
1447
1672
  * @param book - The book instance to lay out with.
1448
- * @param epub - The current parsed EPUB.
1449
- * @param chapterIndex - Zero-based chapter index to lay out.
1450
- * @param surface - DOM ref for the reading surface used for page sizing.
1451
- * @param options - Behavior overrides.
1673
+ * @param options - The EPUB, chapter and surface to lay out, plus behavior overrides.
1452
1674
  */
1453
- declare function useChapterLayout(book: MejiroBook, epub: EpubBook | null, chapterIndex: number, surface: RefObject<HTMLElement | null>, options?: UseChapterLayoutOptions): UseChapterLayoutReturn;
1675
+ declare function useChapterLayout(book: MejiroBook, options: UseChapterLayoutOptions): UseChapterLayoutReturn;
1454
1676
 
1455
1677
  /** Options for {@link useEditableEpub}. */
1456
1678
  interface UseEditableEpubOptions {
1457
- /** URL fetched and loaded on mount. */
1458
- defaultUrl?: string;
1679
+ /**
1680
+ * URL to load. Fetched on mount and again whenever it changes; clearing it
1681
+ * abandons a load still in flight and keeps the current editor.
1682
+ */
1683
+ url?: string;
1459
1684
  /** Called after a successful load. */
1460
1685
  onLoad?: (editor: EditableEpub) => void;
1461
- /** Called when a load fails. */
1686
+ /**
1687
+ * Called when a load or an export fails. A `MejiroError` is passed as
1688
+ * thrown; a failed export also rejects the promise `exportEpub` returned.
1689
+ */
1462
1690
  onError?: (error: Error) => void;
1463
- /** Called after export completes. */
1464
- onExport?: (buffer: ArrayBuffer) => void;
1691
+ /** Called with the EPUB bytes after an export completes. */
1692
+ onExport?: (bytes: Uint8Array<ArrayBuffer>) => void;
1693
+ /**
1694
+ * Extra options merged into the `fetch` call when loading by URL. Useful
1695
+ * for sending bearer tokens or cookies (`credentials: 'include'`).
1696
+ */
1697
+ fetchOptions?: RequestInit;
1698
+ /**
1699
+ * Custom EPUB fetcher used in place of the global `fetch`. Resolves to the
1700
+ * raw bytes. Overrides {@link UseEditableEpubOptions.fetchOptions} when set.
1701
+ */
1702
+ fetchEpub?: (url: string, options: FetchEpubOptions) => Promise<BinaryInput>;
1465
1703
  /**
1466
1704
  * Archive resource limits applied while opening an EPUB. Raise them for
1467
1705
  * trusted, image-heavy books; tighten them for a public drop zone. Omitted
@@ -1476,131 +1714,101 @@ interface UseEditableEpubOptions {
1476
1714
  }
1477
1715
  /** Return value of {@link useEditableEpub}. */
1478
1716
  interface UseEditableEpubReturn {
1479
- /** Loaded editor, or `null` before any load. */
1717
+ /**
1718
+ * Loaded editing session, or `null` before any load. Edits made on it
1719
+ * directly are picked up like the hook's own: the hook subscribes to it and
1720
+ * advances `revision` on every change.
1721
+ */
1480
1722
  editor: EditableEpub | null;
1481
1723
  /** The editor's live document, or `null` before any load. */
1482
- book: EditableEpubBook | null;
1483
- /** Snapshot of `book` re-cloned on every edit, for feeding a preview reader. */
1484
- previewBook: EditableEpubBook | null;
1724
+ epub: EditableEpubBook | null;
1725
+ /**
1726
+ * The document projected onto the reader's {@link EpubBook} shape, rebuilt
1727
+ * on every change, for feeding a preview reader.
1728
+ */
1729
+ previewEpub: EpubBook | null;
1485
1730
  /** Whether a load is in progress. */
1486
1731
  loading: boolean;
1487
1732
  /** Whether an export is in progress. */
1488
1733
  exporting: boolean;
1489
- /** Last load error, if any. */
1734
+ /** Last load or export error, if any. */
1490
1735
  error: Error | null;
1491
- /** Counter bumped on every load, edit, undo and redo. */
1736
+ /** Counter advanced on every load and every change to the document. */
1492
1737
  revision: number;
1493
1738
  /** Undo / redo availability, or `null` before any load. */
1494
- history: {
1495
- canUndo: boolean;
1496
- canRedo: boolean;
1497
- depth: number;
1498
- redoDepth: number;
1499
- } | null;
1500
- /** Paragraph targeted by the editing commands. */
1739
+ history: EditHistoryState | null;
1740
+ /** Block targeted by the editing commands. */
1501
1741
  selection: EditableEpubSelection;
1502
- /** The paragraph at `selection`, or `null` when there is none. */
1503
- selectedParagraph: AnnotatedParagraph | null;
1504
- /** Moves the selection, clamped to the loaded book. */
1742
+ /** The paragraph block at `selection`, or `null` when no paragraph is selected. */
1743
+ selectedParagraph: EditableParagraphBlock | null;
1744
+ /** Moves the selection, clamped to the loaded document. */
1505
1745
  setSelection: (selection: EditableEpubSelection) => void;
1506
- /** Loads an EPUB from an in-memory buffer. */
1507
- loadBuffer: (buffer: ArrayBuffer) => Promise<EditableEpub | null>;
1746
+ /**
1747
+ * Loads an EPUB from in-memory bytes. A `Uint8Array` is read through its own
1748
+ * view and is neither retained nor modified.
1749
+ */
1750
+ loadBuffer: (bytes: BinaryInput) => Promise<EditableEpub | null>;
1508
1751
  /** Loads an EPUB from a {@link File}. */
1509
1752
  loadFile: (file: File) => Promise<EditableEpub | null>;
1510
- /** Fetches and loads an EPUB; a non-2xx response is reported as an error. */
1753
+ /** Fetches and loads an EPUB; a non-2xx response is reported as `fetch-failed`. */
1511
1754
  loadUrl: (url: string) => Promise<EditableEpub | null>;
1512
- /** Replaces the selected paragraph's text and, optionally, its inline annotations. */
1513
- updateParagraph: (text: string, inlineAnnotations?: readonly InlineAnnotation[]) => void;
1514
- /** Replaces the selected paragraph's inline annotations. */
1755
+ /**
1756
+ * Applies `patch` to the selected paragraph, as
1757
+ * {@link EditableEpub.updateParagraph} does. Does nothing when no block is
1758
+ * selected.
1759
+ */
1760
+ updateParagraph: (patch: ParagraphPatch) => void;
1761
+ /** Replaces the selected paragraph's inline annotations. Does nothing when no block is selected. */
1515
1762
  setInlineAnnotations: (inlineAnnotations: readonly InlineAnnotation[]) => void;
1516
- /** Inserts an image into the selected chapter. */
1517
- addImage: (image: AddImageInput | EditableEpubImage) => void;
1763
+ /**
1764
+ * Inserts an image into the selected chapter, as {@link EditableEpub.addImage}
1765
+ * does, and returns the stored block; `null` before any load.
1766
+ */
1767
+ addImage: (image: AddImageInput) => EditableImageBlock | null;
1518
1768
  /** Reverts the last edit. Returns `false` when there is nothing to undo. */
1519
1769
  undo: () => boolean;
1520
1770
  /** Re-applies the last undone edit. Returns `false` when there is nothing to redo. */
1521
1771
  redo: () => boolean;
1522
- /** Packages the edited document as an EPUB buffer. */
1523
- exportEpub: (options?: EpubExportOptions) => Promise<ArrayBuffer | null>;
1524
- }
1525
- /** Headless editable-EPUB state for custom proofreading/editor UIs. */
1526
- declare function useEditableEpub(options?: UseEditableEpubOptions): UseEditableEpubReturn;
1527
-
1528
- /** Options for {@link useEpub}. */
1529
- interface UseEpubOptions {
1530
- /** URL fetched on mount. */
1531
- defaultUrl?: string;
1532
- /** Called after a successful load. */
1533
- onLoad?: (book: EpubBook) => void;
1534
- /** Called when a load fails, including a non-2xx URL response. */
1535
- onError?: (error: Error) => void;
1536
- /**
1537
- * Extra options merged into the `fetch` call when loading by URL. Useful
1538
- * for sending bearer tokens or cookies (`credentials: 'include'`).
1539
- */
1540
- fetchOptions?: RequestInit;
1541
1772
  /**
1542
- * Custom EPUB fetcher used in place of the global `fetch`. Returns the
1543
- * raw `ArrayBuffer`. Overrides {@link fetchOptions} when set — common
1544
- * for hosts that already own an auth-aware HTTP client.
1545
- */
1546
- fetchEpub?: (url: string) => Promise<ArrayBuffer>;
1547
- /**
1548
- * Archive resource limits applied while opening an EPUB. Raise them for
1549
- * trusted, image-heavy books; tighten them for a public drop zone. Omitted
1550
- * fields keep their `DEFAULT_EPUB_PARSE_LIMITS` value.
1773
+ * Packages the edited document as EPUB bytes; `null` before any load. A
1774
+ * failure is stored in `error`, passed to `onError` and rejects.
1551
1775
  */
1552
- limits?: Partial<EpubParseLimits>;
1553
- }
1554
- /** Return value of {@link useEpub}. */
1555
- interface UseEpubReturn {
1556
- /** Current parsed EPUB, or `null` before any load. */
1557
- epub: EpubBook | null;
1558
- /** Whether a load is in progress. */
1559
- loading: boolean;
1560
- /** Last load error, if any. */
1561
- error: Error | null;
1562
- /** Parse from an in-memory buffer. */
1563
- loadBuffer: (buffer: ArrayBuffer) => Promise<EpubBook | null>;
1564
- /** Parse from a {@link File}. */
1565
- loadFile: (file: File) => Promise<EpubBook | null>;
1566
- /** Fetch a URL and parse the response. */
1567
- loadUrl: (url: string) => Promise<EpubBook | null>;
1568
- /** Replace the current EPUB without going through the parser. */
1569
- setEpub: (book: EpubBook | null) => void;
1776
+ exportEpub: (options?: EditableEpubExportOptions) => Promise<Uint8Array<ArrayBuffer> | null>;
1570
1777
  }
1571
1778
  /**
1572
- * React hook that parses EPUB files and exposes loading/error state plus
1573
- * convenience loaders for buffers, files, and URLs.
1779
+ * Headless editable-EPUB state for custom proofreading/editor UIs.
1780
+ *
1781
+ * One load runs at a time: starting another, changing `url` or unmounting
1782
+ * aborts the one in flight — its fetch and parse included — and its result is
1783
+ * dropped.
1574
1784
  */
1575
- declare function useEpub(options?: UseEpubOptions): UseEpubReturn;
1785
+ declare function useEditableEpub(options?: UseEditableEpubOptions): UseEditableEpubReturn;
1576
1786
 
1577
- /** One chapter of the manuscript draft the hook keeps in React state. */
1578
- interface EpubProjectChapterDraft {
1579
- /** Stable chapter identifier. */
1580
- id: string;
1581
- /** Chapter title. */
1582
- title: string;
1583
- /** Chapter body in the manuscript notation. */
1584
- body: string;
1585
- }
1586
1787
  /** Options for {@link useEpubProject}. */
1587
1788
  interface UseEpubProjectOptions {
1588
- /** Initial package metadata, merged over the hook's Japanese defaults. */
1589
- metadata?: Partial<EpubProjectMetadata>;
1590
- /** Initial chapter drafts. A single generated chapter is used when empty. */
1591
- chapters?: EpubProjectChapterDraft[];
1789
+ /**
1790
+ * Package metadata on mount. The title defaults to `''` and the language to
1791
+ * `'ja'`; a missing or blank identifier is generated.
1792
+ */
1793
+ initialMetadata?: Partial<EpubProjectMetadata>;
1794
+ /**
1795
+ * Chapters on mount. When absent or empty, one chapter is generated from
1796
+ * {@link UseEpubProjectOptions.defaultChapterTitle} /
1797
+ * {@link UseEpubProjectOptions.defaultChapterBody}.
1798
+ */
1799
+ initialChapters?: readonly ManuscriptChapter$1[];
1592
1800
  /** Preview rebuild debounce in milliseconds. @defaultValue 250 */
1593
1801
  debounceMs?: number;
1594
1802
  /**
1595
- * Initial cover asset. Pass `{ href, url }` to keep the bytes remote until
1596
- * export, or `{ href, data }` to embed them straight away.
1803
+ * Cover on mount. Pass `{ url, mediaType }` to keep the bytes remote until
1804
+ * export, or `{ data, mediaType }` to embed them straight away.
1597
1805
  */
1598
- cover?: EpubProjectAsset;
1806
+ initialCover?: CoverInput;
1599
1807
  /**
1600
- * Initial non-cover assets (illustrations, extra stylesheets). Same
1601
- * `data` / `url` choice as {@link UseEpubProjectOptions.cover}.
1808
+ * Non-cover assets on mount (illustrations, extra stylesheets). Same
1809
+ * `data` / `url` choice as {@link UseEpubProjectOptions.initialCover}.
1602
1810
  */
1603
- assets?: EpubProjectAsset[];
1811
+ initialAssets?: readonly EpubProjectAsset[];
1604
1812
  /**
1605
1813
  * Resolves URL-only project assets into bytes when the preview or export
1606
1814
  * pipeline materializes them. Forwarded to `project.export()`. Register the
@@ -1609,63 +1817,76 @@ interface UseEpubProjectOptions {
1609
1817
  * provide auth headers here.
1610
1818
  */
1611
1819
  assetResolver?: AssetResolver;
1612
- /** Called with each successfully rebuilt preview book. */
1613
- onPreview?: (book: EpubBook) => void;
1820
+ /** Called with each successfully rebuilt preview EPUB. */
1821
+ onPreview?: (epub: EpubBook) => void;
1614
1822
  /** Called with the EPUB bytes produced by {@link UseEpubProjectReturn.exportEpub}. */
1615
- onExport?: (buffer: ArrayBuffer) => void;
1616
- /** Creates the default title for a generated chapter. */
1823
+ onExport?: (bytes: Uint8Array<ArrayBuffer>) => void;
1824
+ /**
1825
+ * Called when an export fails; the promise `exportEpub` returned rejects
1826
+ * too. A `MejiroError` is passed as thrown. Preview failures go to
1827
+ * {@link UseEpubProjectReturn.previewError} instead.
1828
+ */
1829
+ onError?: (error: Error) => void;
1830
+ /** Creates the title of a generated chapter. @defaultValue `() => ''` */
1617
1831
  defaultChapterTitle?: (index: number) => string;
1618
- /** Creates the default body for a generated chapter. */
1832
+ /** Creates the body of a generated chapter. @defaultValue `() => ''` */
1619
1833
  defaultChapterBody?: (index: number) => string;
1620
1834
  }
1621
1835
  /** State and actions returned by {@link useEpubProject}. */
1622
1836
  interface UseEpubProjectReturn {
1623
1837
  /** Current book metadata. */
1624
1838
  metadata: EpubProjectMetadata;
1625
- /** Current chapter drafts, in reading order. */
1626
- chapters: EpubProjectChapterDraft[];
1839
+ /** Current chapters, in reading order. */
1840
+ chapters: readonly ManuscriptChapter$1[];
1627
1841
  /** Index of the chapter being edited. */
1628
- selectedChapter: number;
1629
- /** The chapter at `selectedChapter`, or `null` when there is none. */
1630
- currentChapter: EpubProjectChapterDraft | null;
1631
- /** Current cover asset, or `null` when the project has no cover. */
1632
- cover: EpubProjectAsset | null;
1842
+ selectedChapterIndex: number;
1843
+ /** The chapter at `selectedChapterIndex`, or `null` when there is none. */
1844
+ currentChapter: ManuscriptChapter$1 | null;
1845
+ /** Current cover, or `null` when the project has no cover. */
1846
+ cover: CoverInput | null;
1633
1847
  /** Current non-cover assets, in registration order. */
1634
- assets: EpubProjectAsset[];
1848
+ assets: readonly EpubProjectAsset[];
1635
1849
  /** Parsed EPUB built from the current project, refreshed after each debounced change. */
1636
- previewBook: EpubBook | null;
1850
+ previewEpub: EpubBook | null;
1637
1851
  /** Last preview build failure, if any. */
1638
1852
  previewError: Error | null;
1639
1853
  /** Whether a preview build is pending or running. */
1640
1854
  previewing: boolean;
1855
+ /** Whether an export is in progress. */
1856
+ exporting: boolean;
1857
+ /** Last export failure, if any; cleared when an export starts. */
1858
+ error: Error | null;
1641
1859
  /**
1642
1860
  * Merges `patch` into the metadata. A missing or blank `identifier` keeps the
1643
1861
  * current one.
1644
1862
  */
1645
- setMetadata: (patch: Partial<EpubProjectMetadata>) => void;
1863
+ updateMetadata: (patch: Partial<EpubProjectMetadata>) => void;
1646
1864
  /** Replaces every chapter; an empty list becomes one generated chapter. */
1647
- setChapters: (chapters: EpubProjectChapterDraft[]) => void;
1865
+ setChapters: (chapters: readonly ManuscriptChapter$1[]) => void;
1648
1866
  /** Selects a chapter, clamped to the chapter range. */
1649
- setSelectedChapter: (index: number) => void;
1867
+ setSelectedChapterIndex: (index: number) => void;
1650
1868
  /**
1651
- * Replaces the cover asset, or drops it when passed `null`. The new cover is
1869
+ * Replaces the cover, or drops it when passed `null`. The new cover is
1652
1870
  * reflected by both the debounced preview and {@link UseEpubProjectReturn.exportEpub}.
1653
1871
  */
1654
- setCover: (asset: EpubProjectAsset | null) => void;
1872
+ setCover: (cover: CoverInput | null) => void;
1655
1873
  /**
1656
1874
  * Replaces the non-cover asset list. Assets are registered on every rebuilt
1657
1875
  * project, so URL-only entries reach `assetResolver` at export time.
1658
1876
  */
1659
- setAssets: (assets: EpubProjectAsset[]) => void;
1877
+ setAssets: (assets: readonly EpubProjectAsset[]) => void;
1660
1878
  /** Merges `patch` into the chapter at `index`. */
1661
- patchChapter: (index: number, patch: Partial<EpubProjectChapterDraft>) => void;
1662
- /** Appends a chapter (generated defaults fill omitted fields) and selects it. */
1663
- addChapter: (chapter?: Partial<EpubProjectChapterDraft>) => void;
1879
+ updateChapter: (index: number, patch: Partial<ManuscriptChapter$1>) => void;
1880
+ /**
1881
+ * Appends a chapter and selects it. Omitted fields are generated: a unique
1882
+ * id, and the title and body of `defaultChapterTitle` / `defaultChapterBody`.
1883
+ */
1884
+ addChapter: (chapter?: Partial<ManuscriptChapter$1>) => void;
1664
1885
  /**
1665
- * Removes the chapter at `index` (the selected one by default); the last chapter is never
1666
- * removed.
1886
+ * Removes the chapter at `index`; the last remaining chapter is never
1887
+ * removed. Pass `selectedChapterIndex` to remove the selected one.
1667
1888
  */
1668
- removeChapter: (index?: number) => void;
1889
+ removeChapter: (index: number) => void;
1669
1890
  /**
1670
1891
  * Moves a chapter from `from` to `to`. An out-of-range `from` selects no
1671
1892
  * chapter and leaves the list untouched; `to` is clamped to the list bounds —
@@ -1674,49 +1895,64 @@ interface UseEpubProjectReturn {
1674
1895
  reorderChapters: (from: number, to: number) => void;
1675
1896
  /** Builds an `EpubProject` from the current state. */
1676
1897
  buildProject: () => EpubProject;
1677
- /** Packages the current project as an EPUB buffer. */
1678
- exportEpub: () => Promise<ArrayBuffer>;
1898
+ /**
1899
+ * Packages the current project as EPUB bytes. A failure is stored in
1900
+ * `error`, passed to `onError` and rejects.
1901
+ */
1902
+ exportEpub: () => Promise<Uint8Array<ArrayBuffer>>;
1679
1903
  }
1680
1904
  /** Headless manuscript-to-EPUB project state for custom authoring UIs. */
1681
1905
  declare function useEpubProject(options?: UseEpubProjectOptions): UseEpubProjectReturn;
1682
1906
 
1683
1907
  /** Options for {@link useManuscriptDraft}. */
1684
- interface UseManuscriptDraftOptions<TAutosave = ManuscriptEditorChapter[]> {
1685
- /** Initial chapters. Defaults to a single empty chapter. */
1686
- initialChapters?: ManuscriptEditorChapter[];
1908
+ interface UseManuscriptDraftOptions<TAutosave = ManuscriptChapter$1[]> {
1909
+ /**
1910
+ * Initial chapters, read on mount. Defaults to a single chapter with an
1911
+ * empty title and body.
1912
+ */
1913
+ initialChapters?: readonly ManuscriptChapter$1[];
1687
1914
  /**
1688
- * Called when the draft changes (debounced). Use to persist to
1689
- * localStorage, IndexedDB, or upload to a server.
1915
+ * Called when the draft changes (debounced). This is the persistence
1916
+ * channel of the hook, and it may be async: return a promise to have the
1917
+ * next save wait for it and to have a rejection land in `autosaveError`.
1918
+ * Use it to write to localStorage or IndexedDB, or to upload to a server.
1690
1919
  */
1691
1920
  onAutosave?: (draft: TAutosave) => void | Promise<void>;
1692
1921
  /** Maps chapters to the autosave payload. Defaults to the chapter array. */
1693
- autosavePayload?: (chapters: ManuscriptEditorChapter[]) => TAutosave;
1694
- /** Extra key that triggers autosave when non-chapter metadata changes. */
1695
- autosaveKey?: string;
1922
+ autosavePayload?: (chapters: readonly ManuscriptChapter$1[]) => TAutosave;
1923
+ /**
1924
+ * Values outside the chapters that the autosave payload depends on (a
1925
+ * title, an author). A change to any of them, compared with `Object.is`,
1926
+ * schedules an autosave like a chapter edit does.
1927
+ */
1928
+ autosaveDeps?: readonly unknown[];
1696
1929
  /** Debounce delay in milliseconds. @defaultValue 800 */
1697
- autosaveDelay?: number;
1698
- /** Creates the default title for a generated chapter. */
1930
+ autosaveDebounceMs?: number;
1931
+ /** Creates the title of a generated chapter. @defaultValue `() => ''` */
1699
1932
  defaultChapterTitle?: (index: number) => string;
1700
- /** Creates the default body for a generated chapter. */
1933
+ /** Creates the body of a generated chapter. @defaultValue `() => ''` */
1701
1934
  defaultChapterBody?: (index: number) => string;
1702
1935
  }
1703
1936
  /** Return value of {@link useManuscriptDraft}. */
1704
1937
  interface UseManuscriptDraftReturn {
1705
1938
  /** Current chapters, in reading order. */
1706
- chapters: ManuscriptEditorChapter[];
1939
+ chapters: readonly ManuscriptChapter$1[];
1707
1940
  /** Index of the chapter currently being edited. */
1708
- selected: number;
1941
+ selectedChapterIndex: number;
1709
1942
  /** Selects a chapter, clamped to the chapter range. */
1710
- setSelected(index: number): void;
1943
+ setSelectedChapterIndex(index: number): void;
1711
1944
  /**
1712
1945
  * Replaces every chapter; an empty list becomes one generated chapter. The
1713
1946
  * selected chapter is kept by id when it survives.
1714
1947
  */
1715
- setChapters(chapters: ManuscriptEditorChapter[]): void;
1948
+ setChapters(chapters: readonly ManuscriptChapter$1[]): void;
1716
1949
  /** Merges `patch` into the chapter at `index`. */
1717
- patchChapter(index: number, patch: Partial<ManuscriptEditorChapter>): void;
1718
- /** Appends a chapter (generated defaults fill omitted fields) and selects it. */
1719
- addChapter(chapter?: Partial<ManuscriptEditorChapter>): void;
1950
+ updateChapter(index: number, patch: Partial<ManuscriptChapter$1>): void;
1951
+ /**
1952
+ * Appends a chapter and selects it. Omitted fields are generated: a unique
1953
+ * id, and the title and body of `defaultChapterTitle` / `defaultChapterBody`.
1954
+ */
1955
+ addChapter(chapter?: Partial<ManuscriptChapter$1>): void;
1720
1956
  /** Removes the chapter at `index`; the last remaining chapter is never removed. */
1721
1957
  removeChapter(index: number): void;
1722
1958
  /** Moves the chapter at `from` to `to`, keeping the same chapter selected. */
@@ -1733,16 +1969,20 @@ interface UseManuscriptDraftReturn {
1733
1969
  * patching individual chapters, plus a debounced autosave hook that fires
1734
1970
  * `onAutosave` whenever the chapter list settles.
1735
1971
  */
1736
- declare function useManuscriptDraft<TAutosave = ManuscriptEditorChapter[]>(options?: UseManuscriptDraftOptions<TAutosave>): UseManuscriptDraftReturn;
1972
+ declare function useManuscriptDraft<TAutosave = ManuscriptChapter$1[]>(options?: UseManuscriptDraftOptions<TAutosave>): UseManuscriptDraftReturn;
1737
1973
 
1738
1974
  /** Options for {@link useManuscriptLayout}. */
1739
1975
  interface UseManuscriptLayoutOptions {
1976
+ /** The chapter to lay out; `null` lays nothing out. */
1977
+ chapter: ManuscriptChapter | null;
1978
+ /** The reading surface the pages are sized against. */
1979
+ surface: RefObject<HTMLElement | null>;
1740
1980
  /** Manuscript notation dialect. @defaultValue `'mejiro'` */
1741
1981
  dialect?: ManuscriptDialect;
1742
1982
  /** Observe the surface element for size changes and re-flow. @defaultValue true */
1743
1983
  enableResize?: boolean;
1744
1984
  /** Debounce window (ms) applied to size-triggered re-flows. @defaultValue 120 */
1745
- resizeDebounce?: number;
1985
+ resizeDebounceMs?: number;
1746
1986
  /**
1747
1987
  * Capture the reading position from the outgoing layout, just before a
1748
1988
  * **reflow** (non-blank) re-layout replaces it. The anchor is held in
@@ -1765,30 +2005,11 @@ interface UseManuscriptLayoutOptions {
1765
2005
  */
1766
2006
  onError?: (error: Error) => void;
1767
2007
  }
1768
- /** Options for {@link UseManuscriptLayoutReturn.recompute}. */
1769
- interface ManuscriptRecomputeOptions {
1770
- /**
1771
- * Blank the current layout while the new one is computed. Use for content
1772
- * changes (chapter swaps); skip for size changes so the previous spread stays
1773
- * visible until the re-flow completes (no flicker).
1774
- * @defaultValue true
1775
- */
1776
- blank?: boolean;
1777
- }
1778
- /** Page dimensions returned by {@link MejiroBook.computePageSize}. */
1779
- interface ManuscriptPageDimensions {
1780
- /** Width of one page, in CSS pixels. */
1781
- pageWidth: number;
1782
- /** Height of one page, in CSS pixels. */
1783
- pageHeight: number;
1784
- /** Height of the text area inside a page, in CSS pixels. */
1785
- contentHeight: number;
1786
- }
1787
2008
  /** Return value of {@link useManuscriptLayout}. */
1788
2009
  interface UseManuscriptLayoutReturn {
1789
2010
  /** Current layout, or `null` before the first computation. */
1790
2011
  layout: ChapterLayout | null;
1791
- /** Page width in pixels. */
2012
+ /** Page width in pixels, as {@link MejiroBook.fitPageSize} measured it. */
1792
2013
  pageWidth: number;
1793
2014
  /** Page height in pixels. */
1794
2015
  pageHeight: number;
@@ -1796,11 +2017,16 @@ interface UseManuscriptLayoutReturn {
1796
2017
  contentHeight: number;
1797
2018
  /** Most recent layout time (ms). */
1798
2019
  elapsedMs: number;
2020
+ /**
2021
+ * Failure of the most recent layout of the current source, whichever path
2022
+ * started it; `null` once a layout succeeds or the source changes.
2023
+ */
2024
+ error: Error | null;
1799
2025
  /**
1800
2026
  * Force a fresh layout computation. A failure goes to `onError` when one is
1801
2027
  * supplied; otherwise the returned promise rejects.
1802
2028
  */
1803
- recompute: (opts?: ManuscriptRecomputeOptions) => Promise<void>;
2029
+ recompute: (opts?: ChapterLayoutRecomputeOptions) => Promise<void>;
1804
2030
  /**
1805
2031
  * Anchor captured by `capturePosition` before the most recent reflow,
1806
2032
  * awaiting restoration into the new layout. `restorePosition`, when given,
@@ -1820,7 +2046,8 @@ interface UseManuscriptLayoutReturn {
1820
2046
  * Re-layouts whenever the chapter's id, title or body, `dialect`, or `book`
1821
2047
  * change; a new chapter object with the same content does not. The surface is
1822
2048
  * observed with {@link ResizeObserver}; size changes trigger a full re-layout
1823
- * so pagination stays correct after non-trivial container changes.
2049
+ * so pagination stays correct after non-trivial container changes. While the
2050
+ * surface measures zero wide or high, nothing is measured or laid out.
1824
2051
  *
1825
2052
  * A size-driven re-layout keeps the previous layout on screen while the new one
1826
2053
  * is computed (no blank flash) and yields a new {@link ChapterLayout} object,
@@ -1830,8 +2057,11 @@ interface UseManuscriptLayoutReturn {
1830
2057
  * {@link UseManuscriptLayoutOptions.restorePosition} and
1831
2058
  * {@link UseManuscriptLayoutReturn.pendingRestore}, identically in the Vue
1832
2059
  * composable.
2060
+ *
2061
+ * @param book - The book instance to lay out with.
2062
+ * @param options - The chapter and surface to lay out, plus behavior overrides.
1833
2063
  */
1834
- declare function useManuscriptLayout(book: MejiroBook, chapter: ManuscriptChapter | null, surface: RefObject<HTMLElement | null>, options?: UseManuscriptLayoutOptions): UseManuscriptLayoutReturn;
2064
+ declare function useManuscriptLayout(book: MejiroBook, options: UseManuscriptLayoutOptions): UseManuscriptLayoutReturn;
1835
2065
 
1836
2066
  /** Options for {@link useMejiroBook}. */
1837
2067
  interface UseMejiroBookOptions {
@@ -1847,7 +2077,7 @@ interface UseMejiroBookOptions {
1847
2077
  /**
1848
2078
  * Called when applying options to the book fails. When supplied, failures are
1849
2079
  * reported here and the promise returned by
1850
- * {@link UseMejiroBookReturn.setOptions} resolves instead of rejecting, so
2080
+ * {@link UseMejiroBookReturn.updateOptions} resolves instead of rejecting, so
1851
2081
  * fire-and-forget callers cannot leave an unhandled rejection behind.
1852
2082
  */
1853
2083
  onError?: (error: Error) => void;
@@ -1862,26 +2092,31 @@ interface UseMejiroBookReturn {
1862
2092
  */
1863
2093
  options: Readonly<BookOptions>;
1864
2094
  /**
1865
- * Update options on the underlying book and the snapshot. The snapshot is
1866
- * updated synchronously; the returned promise resolves once
1867
- * {@link MejiroBook.setOptions} has propagated font / size changes (which
2095
+ * Merges `patch` into the options of the underlying book and the snapshot.
2096
+ * The snapshot is updated synchronously; the returned promise resolves once
2097
+ * {@link MejiroBook.updateOptions} has propagated font / size changes (which
1868
2098
  * complete only after the font has loaded).
1869
2099
  */
1870
- setOptions: (next: Partial<BookOptions>) => Promise<void>;
2100
+ updateOptions: (patch: BookOptionsPatch) => Promise<void>;
1871
2101
  }
1872
2102
  /**
1873
2103
  * React hook that owns a {@link MejiroBook} instance and exposes its
1874
- * options as React state. The book is created once on mount; subsequent
1875
- * option changes must go through {@link UseMejiroBookReturn.setOptions}
1876
- * (e.g. via the `MejiroReader` imperative handle).
2104
+ * options as React state.
1877
2105
  *
1878
- * @param initial - Initial book options (passed to the {@link MejiroBook} constructor).
1879
- * @param options - Behavior overrides (debouncing, error reporting).
2106
+ * The book is created once on mount from `options`. Afterwards, a change to
2107
+ * the content of a key {@link MejiroBook.updateOptions} accepts — compared by
2108
+ * value, so an object literal recreated every render is fine — is applied like
2109
+ * an {@link UseMejiroBookReturn.updateOptions} call; without `onError`, a
2110
+ * failure of such an application goes to `console.warn`. A change to a
2111
+ * construction-time key (`strictFontCheck`, `analyzer`, `pageSize`, …) is
2112
+ * ignored and the book is not recreated; give the component a new React `key`
2113
+ * to start over with a new book.
2114
+ *
2115
+ * @param options - Book options; read in full on mount, and for their runtime keys on every render.
2116
+ * @param hookOptions - Behavior overrides (debouncing, error reporting).
1880
2117
  */
1881
- declare function useMejiroBook(initial: MejiroBookOptions, options?: UseMejiroBookOptions): UseMejiroBookReturn;
2118
+ declare function useMejiroBook(options: BookOptions, hookOptions?: UseMejiroBookOptions): UseMejiroBookReturn;
1882
2119
 
1883
- /** Minimal storage interface required by {@link useReadingPosition}. */
1884
- type ReadingPositionStorage = MejiroStorage;
1885
2120
  /** Options for {@link useReadingPosition}. */
1886
2121
  interface UseReadingPositionOptions {
1887
2122
  /** Storage key — usually scoped per-book (e.g. `mejiro:position:${bookId}`). */
@@ -1890,24 +2125,34 @@ interface UseReadingPositionOptions {
1890
2125
  * Storage backend. Defaults to `window.localStorage` when available.
1891
2126
  * SSR consumers can omit this and the hook will fall back to in-memory.
1892
2127
  */
1893
- storage?: ReadingPositionStorage;
2128
+ storage?: MejiroStorage;
1894
2129
  /**
1895
- * Debounce delay (ms): the latest value is written this long after the last change. A pending write is flushed on page hide, key change and unmount. @defaultValue 250
2130
+ * Debounce delay (ms): the latest value is written this long after the last
2131
+ * change. A pending write is flushed on page hide, key change and unmount.
2132
+ * @defaultValue 250
1896
2133
  */
1897
- throttleMs?: number;
2134
+ debounceMs?: number;
1898
2135
  /**
1899
2136
  * Called immediately after `save()` (with the new anchor) or `clear()`
1900
2137
  * (with `null`). Use to mirror the position to a server alongside the
1901
2138
  * local `storage`. Not invoked on initial hydration or `key` changes.
1902
2139
  */
1903
- onChange?: (next: ReadingPositionValue | null) => void;
2140
+ onPositionChange?: (next: ReadingAnchor$1 | null) => void;
2141
+ /**
2142
+ * Receives a storage failure: `storage-read-failed` or `storage-corrupt` when
2143
+ * hydrating (the position starts as `null`, and the stored value is left
2144
+ * alone until the next `save()`), `storage-write-failed` when a write or
2145
+ * removal throws (the in-memory position is kept). Without it the error goes
2146
+ * to `console.warn`.
2147
+ */
2148
+ onError?: (error: MejiroError) => void;
1904
2149
  }
1905
2150
  /** Return value of {@link useReadingPosition}. */
1906
2151
  interface UseReadingPositionReturn {
1907
2152
  /** Most recently persisted position, or `null` if none. */
1908
- position: ReadingPositionValue | null;
1909
- /** Persist a new position (debounced by `throttleMs`). */
1910
- save(next: ReadingPositionValue): void;
2153
+ position: ReadingAnchor$1 | null;
2154
+ /** Persist a new position (debounced by `debounceMs`). */
2155
+ save(next: ReadingAnchor$1): void;
1911
2156
  /** Remove the persisted position. */
1912
2157
  clear(): void;
1913
2158
  }
@@ -1934,73 +2179,11 @@ interface UseReadingPositionReturn {
1934
2179
  * }, [save]);
1935
2180
  * ```
1936
2181
  *
1937
- * Legacy `{ chapter, spreadIdx }` JSON from v0.4 is migrated automatically:
1938
- * the chapter is preserved, `spreadIdx` is dropped (it does not survive
1939
- * reflow), and the position is treated as the start of the chapter.
2182
+ * Every stored layout {@link parseReadingPosition} reads is accepted; one
2183
+ * that names only a chapter and a spread resumes at the start of the chapter,
2184
+ * because a spread index does not survive reflow.
1940
2185
  */
1941
2186
  declare function useReadingPosition(options: UseReadingPositionOptions): UseReadingPositionReturn;
1942
2187
 
1943
- /** Options for {@link useSpread}. */
1944
- interface UseSpreadOptions {
1945
- /** Enable ArrowLeft/ArrowRight navigation. @defaultValue true */
1946
- enableKeyboard?: boolean;
1947
- /** Page-turn animation duration in ms. @defaultValue 180 */
1948
- turnDuration?: number;
1949
- /**
1950
- * Show one page at a time: the navigation index then counts pages instead
1951
- * of two-page spreads. Toggling it converts the index so the page on
1952
- * screen stays visible. @defaultValue false
1953
- */
1954
- single?: boolean;
1955
- /** Called when the spread index changes (after the turn animation midpoint). */
1956
- onChange?: (spreadIdx: number) => void;
1957
- }
1958
- /** Return value of {@link useSpread}. */
1959
- interface UseSpreadReturn {
1960
- /** Current navigation index: a page index in single mode, a spread index otherwise. */
1961
- spreadIdx: number;
1962
- /** Layout spread holding the visible page(s), or `null` until the layout is ready. */
1963
- spread: SpreadResult | null;
1964
- /** First page shown, in reading order. */
1965
- firstPage: number;
1966
- /** Index of {@link UseSpreadReturn.spread} in the layout (`ChapterLayout.getSpread`). */
1967
- layoutSpreadIdx: number;
1968
- /** Side of {@link UseSpreadReturn.spread} shown in single mode; `null` in double mode. */
1969
- singleSide: 'right' | 'left' | null;
1970
- /** Total number of pages, as of {@link UseSpreadReturn.spread}. */
1971
- totalPages: number;
1972
- /** Number of navigation positions: pages in single mode, two-page spreads otherwise. */
1973
- totalSpreads: number;
1974
- /** Whether the turn animation is currently in flight. */
1975
- turning: boolean;
1976
- /** Advance one position (page or spread) forward. */
1977
- next: () => void;
1978
- /** Go back one position. */
1979
- prev: () => void;
1980
- /** Jump to an arbitrary navigation index (clamped). */
1981
- goTo: (index: number) => void;
1982
- /**
1983
- * Set the navigation index immediately, with no page-turn animation, clamped
1984
- * to `[0, totalSpreads − 1]`. Use to restore a reading position after a reflow
1985
- * re-layout (where an animated {@link goTo} would briefly flash spread 0).
1986
- */
1987
- setSpread: (index: number) => void;
1988
- /** Navigation index that shows the given page index. */
1989
- indexOfPage: (pageIdx: number) => number;
1990
- /**
1991
- * Anchor at the start of the text shown at navigation index `index`. A page an
1992
- * image blocks entirely holds no text, so the anchor then comes from the next
1993
- * page that does, even past `index`; `null` when no page from there on holds text.
1994
- */
1995
- anchorAt: (index: number) => InChapterAnchor | null;
1996
- /** Manually refresh `spread` from `layout` at the current index. */
1997
- refresh: () => void;
1998
- }
1999
- /**
2000
- * React hook that tracks the current spread index for a chapter layout
2001
- * and exposes navigation helpers with an optional page-turn animation.
2002
- */
2003
- declare function useSpread(layout: ChapterLayout | null, options?: UseSpreadOptions): UseSpreadReturn;
2004
-
2005
- export { MejiroChapterNav, MejiroDropZone, MejiroEditor, MejiroI18nProvider, MejiroImageOverlay, MejiroManuscriptEditor, MejiroNotationHighlighter, MejiroPage, MejiroPageIndicator, MejiroPageView, MejiroReader, MejiroScrollView, MejiroSelectionLayer, MejiroSettingsPanel, MejiroShelf, MejiroSpread, MejiroStats, MejiroToc, format, resolveMessages, useAnnotations, useChapterLayout, useEditableEpub, useEpub, useEpubProject, useI18n, useImageOverlay, useLibrary, useManuscriptDraft, useManuscriptLayout, useMejiroBook, useMultiImageOverlay, useReadingPosition, useSpread };
2006
- export type { AnnotationsStorage, EpubProjectChapterDraft, ImageRect, ManuscriptAutosaveDraft, ManuscriptEditorChapter, ManuscriptPageDimensions, ManuscriptPreviewProps, ManuscriptRecomputeOptions, MejiroChapterNavMode, MejiroChapterNavProps, MejiroChapterNavVariant, MejiroDropZoneProps, MejiroEditorProps, MejiroExportPolicy, MejiroImageOverlayProps, MejiroManuscriptEditorProps, MejiroNotationHighlighterProps, MejiroPageIndicatorProps, MejiroPageProps, MejiroPageViewProps, MejiroReaderCommonProps, MejiroReaderControlledProps, MejiroReaderEventMap, MejiroReaderFileProps, MejiroReaderFit, MejiroReaderHandle, MejiroReaderManuscriptProps, MejiroReaderMode, MejiroReaderProps, MejiroReaderSettingsSlot, MejiroReaderUrlProps, MejiroScrollViewProps, MejiroSelectionLayerProps, MejiroSettingsPanelProps, MejiroShelfProps, MejiroSpreadMode, MejiroSpreadProps, MejiroStatsProps, MejiroTheme, MejiroThemeName, MejiroTocProps, MultiImageItem, PageDimensions, ReadingPosition, ReadingPositionStorage, RecomputeOptions, UseAnnotationsOptions, UseAnnotationsReturn, UseChapterLayoutOptions, UseChapterLayoutReturn, UseEditableEpubOptions, UseEditableEpubReturn, UseEpubOptions, UseEpubProjectOptions, UseEpubProjectReturn, UseEpubReturn, UseI18nOptions, UseImageOverlayOptions, UseImageOverlayReturn, UseLibraryOptions, UseLibraryReturn, UseManuscriptDraftOptions, UseManuscriptDraftReturn, UseManuscriptLayoutOptions, UseManuscriptLayoutReturn, UseMejiroBookOptions, UseMejiroBookReturn, UseMultiImageOverlayOptions, UseMultiImageOverlayReturn, UseReadingPositionOptions, UseReadingPositionReturn, UseSpreadOptions, UseSpreadReturn, VolumeInfo };
2188
+ export { MejiroChapterNav, MejiroDropZone, MejiroEditor, MejiroI18nProvider, MejiroImagePlaceholder, MejiroManuscriptEditor, MejiroNotationHighlighter, MejiroPage, MejiroPageIndicator, MejiroPageView, MejiroReader, MejiroScrollView, MejiroSelectionLayer, MejiroSettingsPanel, MejiroShelf, MejiroSpread, MejiroStats, MejiroToc, useAnnotations, useChapterLayout, useEditableEpub, useEpub, useEpubProject, useI18n, useImageOverlay, useLibrary, useManuscriptDraft, useManuscriptLayout, useMejiroBook, useReadingPosition, useSpread };
2189
+ export type { ChapterLayoutRecomputeOptions, ExportPolicy, FetchEpubOptions, GoToOptions, ManuscriptAutosaveDraft, ManuscriptPreviewProps, MejiroChapterNavMode, MejiroChapterNavProps, MejiroChapterNavVariant, MejiroDropZoneProps, MejiroEditorProps, MejiroI18nProviderProps, MejiroImagePlaceholderProps, MejiroManuscriptEditorProps, MejiroNotationHighlighterProps, MejiroPageIndicatorProps, MejiroPageProps, MejiroPageViewProps, MejiroReaderChapterFinishedEvent, MejiroReaderCommonProps, MejiroReaderEventMap, MejiroReaderFit, MejiroReaderHandle, MejiroReaderManuscriptProps, MejiroReaderMode, MejiroReaderParsedProps, MejiroReaderPickerProps, MejiroReaderProps, MejiroReaderSettingsSlot, MejiroReaderSpreadIndexChangeEvent, MejiroReaderTurnEndEvent, MejiroReaderTurnStartEvent, MejiroReaderUrlProps, MejiroScrollViewProps, MejiroSelectionLayerProps, MejiroSettingsPanelProps, MejiroShelfProps, MejiroSpreadMode, MejiroSpreadProps, MejiroStatsProps, MejiroTheme, MejiroThemeName, MejiroTocProps, MultiImageItem, ReaderAnnotation, ReadingAnchorRange, ResolvedSpreadMode, SelectionRect, SpreadPosition, UseAnnotationsOptions, UseAnnotationsReturn, UseChapterLayoutOptions, UseChapterLayoutReturn, UseEditableEpubOptions, UseEditableEpubReturn, UseEpubOptions, UseEpubProjectOptions, UseEpubProjectReturn, UseEpubReturn, UseI18nOptions, UseImageOverlayOptions, UseImageOverlayReturn, UseLibraryOptions, UseLibraryReturn, UseManuscriptDraftOptions, UseManuscriptDraftReturn, UseManuscriptLayoutOptions, UseManuscriptLayoutReturn, UseMejiroBookOptions, UseMejiroBookReturn, UseReadingPositionOptions, UseReadingPositionReturn, UseSpreadOptions, UseSpreadReturn, VolumeInfo };