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