@somecat/epub-reader 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.js CHANGED
@@ -2,503 +2,7 @@ import { forwardRef, useRef, useState, useMemo, useEffect, useCallback, useImper
2
2
  import 'foliate-js/view.js';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
 
5
- // src/styles/epub-reader.cssText.ts
6
- var epubReaderCssText = `
7
- :root {
8
- --epub-reader-panel-bg: #ffffff;
9
- --epub-reader-panel-fg: #1f2937;
10
- --epub-reader-border: rgba(0, 0, 0, 0.12);
11
- --epub-reader-accent: #2563eb;
12
- --epub-reader-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
13
- --epub-reader-radius: 10px;
14
- --epub-reader-bottom-bar-height: 40px;
15
- }
16
-
17
- .epub-reader-icon {
18
- display: inline-block;
19
- vertical-align: middle;
20
- }
21
-
22
- .epub-reader[data-theme='dark'] {
23
- --epub-reader-panel-bg: #141414;
24
- --epub-reader-panel-fg: #e5e7eb;
25
- --epub-reader-border: rgba(255, 255, 255, 0.14);
26
- --epub-reader-accent: #60a5fa;
27
- --epub-reader-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
28
- }
29
-
30
- .epub-reader {
31
- position: relative;
32
- width: 100%;
33
- height: 100%;
34
- outline: none;
35
- overflow: hidden;
36
- }
37
-
38
- .epub-reader__viewer {
39
- width: 100%;
40
- height: calc(100% - var(--epub-reader-bottom-bar-height));
41
- background: var(--epub-reader-panel-bg);
42
- transition: background-color 0.2s ease;
43
- }
44
-
45
- .epub-reader__toolbar {
46
- position: absolute;
47
- top: 16px;
48
- right: 16px;
49
- z-index: 10;
50
- display: flex;
51
- flex-direction: column;
52
- gap: 10px;
53
- }
54
-
55
- .epub-reader[data-layout='wide'] .epub-reader__toolbar {
56
- top: 50%;
57
- transform: translateY(-50%);
58
- }
59
-
60
- .epub-reader__panel {
61
- width: 88px;
62
- padding: 8px;
63
- border: 1px solid var(--epub-reader-border);
64
- border-radius: var(--epub-reader-radius);
65
- background: color-mix(in srgb, var(--epub-reader-panel-bg) 92%, transparent);
66
- color: var(--epub-reader-panel-fg);
67
- box-shadow: var(--epub-reader-shadow);
68
- backdrop-filter: blur(10px);
69
- display: flex;
70
- flex-direction: column;
71
- gap: 6px;
72
- align-items: stretch;
73
- }
74
-
75
- .epub-reader__btn {
76
- appearance: none;
77
- border: 1px solid var(--epub-reader-border);
78
- background: transparent;
79
- color: inherit;
80
- padding: 6px 8px;
81
- border-radius: 8px;
82
- font-size: 12px;
83
- line-height: 1;
84
- cursor: pointer;
85
- user-select: none;
86
- }
87
-
88
- .epub-reader__btn:disabled {
89
- opacity: 0.6;
90
- cursor: not-allowed;
91
- }
92
-
93
- .epub-reader__btn:hover:not(:disabled) {
94
- border-color: color-mix(in srgb, var(--epub-reader-accent) 60%, var(--epub-reader-border));
95
- }
96
-
97
- .epub-reader__divider {
98
- height: 1px;
99
- background: var(--epub-reader-border);
100
- margin: 2px 0;
101
- }
102
-
103
- .epub-reader__font {
104
- text-align: center;
105
- font-size: 12px;
106
- font-weight: 700;
107
- color: var(--epub-reader-accent);
108
- padding: 2px 0;
109
- }
110
-
111
- .epub-reader__overlay {
112
- position: absolute;
113
- inset: 0;
114
- background: rgba(0, 0, 0, 0.35);
115
- z-index: 20;
116
- }
117
-
118
- .epub-reader__drawer {
119
- position: absolute;
120
- top: 0;
121
- bottom: 0;
122
- left: 0;
123
- width: 320px;
124
- background: var(--epub-reader-panel-bg);
125
- color: var(--epub-reader-panel-fg);
126
- border-right: 1px solid var(--epub-reader-border);
127
- transform: translateX(-100%);
128
- transition: transform 0.2s ease;
129
- z-index: 30;
130
- display: flex;
131
- flex-direction: column;
132
- }
133
-
134
- .epub-reader__drawer.right {
135
- left: auto;
136
- right: 0;
137
- border-right: none;
138
- border-left: 1px solid var(--epub-reader-border);
139
- transform: translateX(100%);
140
- }
141
-
142
- .epub-reader__drawer.is-open {
143
- transform: translateX(0);
144
- }
145
-
146
- .epub-reader__drawer-header {
147
- display: flex;
148
- align-items: center;
149
- justify-content: space-between;
150
- gap: 8px;
151
- padding: 12px;
152
- border-bottom: 1px solid var(--epub-reader-border);
153
- }
154
-
155
- .epub-reader__drawer-title {
156
- font-size: 14px;
157
- font-weight: 700;
158
- }
159
-
160
- .epub-reader__drawer-body {
161
- padding: 12px;
162
- overflow: auto;
163
- flex: 1;
164
- }
165
-
166
- .epub-reader__empty {
167
- padding: 20px 8px;
168
- opacity: 0.7;
169
- text-align: center;
170
- font-size: 12px;
171
- }
172
-
173
- .epub-reader__toc-list {
174
- list-style: none;
175
- padding: 0;
176
- margin: 0;
177
- display: flex;
178
- flex-direction: column;
179
- gap: 4px;
180
- }
181
-
182
- .epub-reader__toc-item {
183
- margin: 0;
184
- }
185
-
186
- .epub-reader__toc-btn {
187
- width: 100%;
188
- text-align: left;
189
- padding: 8px 10px;
190
- border-radius: 8px;
191
- border: 1px solid var(--epub-reader-border);
192
- background: transparent;
193
- color: inherit;
194
- cursor: pointer;
195
- }
196
-
197
- .epub-reader__toc-btn:hover {
198
- border-color: color-mix(in srgb, var(--epub-reader-accent) 60%, var(--epub-reader-border));
199
- }
200
-
201
- .epub-reader__toc-details {
202
- border-radius: 8px;
203
- }
204
-
205
- .epub-reader__toc-summary {
206
- padding: 8px 10px;
207
- border-radius: 8px;
208
- cursor: pointer;
209
- border: 1px solid var(--epub-reader-border);
210
- user-select: none;
211
- }
212
-
213
- .epub-reader__toc-details[open] > .epub-reader__toc-summary {
214
- border-color: color-mix(in srgb, var(--epub-reader-accent) 60%, var(--epub-reader-border));
215
- }
216
-
217
- .epub-reader__toc-details > .epub-reader__toc-list {
218
- padding-left: 12px;
219
- margin-top: 6px;
220
- }
221
-
222
- .epub-reader__field {
223
- display: flex;
224
- align-items: center;
225
- gap: 8px;
226
- }
227
-
228
- .epub-reader__input {
229
- width: 100%;
230
- padding: 8px 10px;
231
- border-radius: 8px;
232
- border: 1px solid var(--epub-reader-border);
233
- background: transparent;
234
- color: inherit;
235
- outline: none;
236
- }
237
-
238
- .epub-reader__checks {
239
- display: flex;
240
- flex-wrap: wrap;
241
- gap: 10px;
242
- margin: 10px 0;
243
- font-size: 12px;
244
- }
245
-
246
- .epub-reader__check {
247
- display: inline-flex;
248
- align-items: center;
249
- gap: 6px;
250
- user-select: none;
251
- }
252
-
253
- .epub-reader__meta {
254
- display: flex;
255
- align-items: center;
256
- justify-content: space-between;
257
- gap: 8px;
258
- font-size: 12px;
259
- opacity: 0.9;
260
- margin: 6px 0 10px;
261
- }
262
-
263
- .epub-reader__link {
264
- appearance: none;
265
- border: none;
266
- background: transparent;
267
- color: var(--epub-reader-accent);
268
- cursor: pointer;
269
- padding: 0;
270
- }
271
-
272
- .epub-reader__search-list {
273
- list-style: none;
274
- padding: 0;
275
- margin: 0;
276
- display: flex;
277
- flex-direction: column;
278
- gap: 6px;
279
- }
280
-
281
- .epub-reader__search-item {
282
- margin: 0;
283
- }
284
-
285
- .epub-reader__search-btn {
286
- width: 100%;
287
- text-align: left;
288
- padding: 10px;
289
- border-radius: 10px;
290
- border: 1px solid var(--epub-reader-border);
291
- background: transparent;
292
- color: inherit;
293
- cursor: pointer;
294
- display: flex;
295
- flex-direction: column;
296
- gap: 6px;
297
- }
298
-
299
- .epub-reader__search-btn:hover {
300
- border-color: color-mix(in srgb, var(--epub-reader-accent) 60%, var(--epub-reader-border));
301
- }
302
-
303
- .epub-reader__search-label {
304
- font-size: 12px;
305
- opacity: 0.7;
306
- }
307
-
308
- .epub-reader__search-excerpt {
309
- font-size: 12px;
310
- line-height: 1.4;
311
- word-break: break-word;
312
- }
313
-
314
- .epub-reader__bottom {
315
- position: absolute;
316
- left: 0;
317
- right: 0;
318
- bottom: 0;
319
- height: var(--epub-reader-bottom-bar-height);
320
- border-top: 1px solid var(--epub-reader-border);
321
- background: color-mix(in srgb, var(--epub-reader-panel-bg) 92%, transparent);
322
- display: flex;
323
- align-items: center;
324
- justify-content: space-between;
325
- gap: 10px;
326
- padding: 0 12px;
327
- z-index: 10;
328
- backdrop-filter: blur(10px);
329
- }
330
-
331
- .epub-reader__bottom-left {
332
- display: flex;
333
- align-items: center;
334
- gap: 8px;
335
- min-width: 0;
336
- }
337
-
338
- .epub-reader__status {
339
- font-size: 12px;
340
- opacity: 0.8;
341
- white-space: nowrap;
342
- }
343
-
344
- .epub-reader__section {
345
- font-size: 12px;
346
- font-weight: 600;
347
- white-space: nowrap;
348
- overflow: hidden;
349
- text-overflow: ellipsis;
350
- max-width: 280px;
351
- }
352
-
353
- .epub-reader__bottom-right {
354
- display: flex;
355
- align-items: center;
356
- gap: 10px;
357
- min-width: 260px;
358
- }
359
-
360
- .epub-reader__range {
361
- width: 260px;
362
- }
363
-
364
- .epub-reader__percent {
365
- font-size: 12px;
366
- font-variant-numeric: tabular-nums;
367
- min-width: 42px;
368
- text-align: right;
369
- }
370
-
371
- .epub-reader[data-layout='mobile'] .epub-reader__viewer {
372
- height: 100%;
373
- touch-action: pan-x;
374
- }
375
-
376
- .epub-reader[data-layout='mobile'] .epub-reader__toolbar,
377
- .epub-reader[data-layout='mobile'] .epub-reader__bottom,
378
- .epub-reader[data-layout='mobile'] .epub-reader__drawer,
379
- .epub-reader[data-layout='mobile'] .epub-reader__overlay {
380
- display: none;
381
- }
382
-
383
- .epub-reader__mbar {
384
- position: absolute;
385
- left: 10px;
386
- right: 10px;
387
- bottom: 0;
388
- z-index: 15;
389
- display: flex;
390
- align-items: center;
391
- gap: 8px;
392
- padding: 10px;
393
- border: 1px solid var(--epub-reader-border);
394
- border-radius: calc(var(--epub-reader-radius) + 6px);
395
- background: color-mix(in srgb, var(--epub-reader-panel-bg) 92%, transparent);
396
- color: var(--epub-reader-panel-fg);
397
- box-shadow: var(--epub-reader-shadow);
398
- backdrop-filter: blur(10px);
399
- transform: translateY(calc(100% + 10px));
400
- transition: transform 0.2s ease;
401
- }
402
-
403
- .epub-reader__mbar.is-visible {
404
- transform: translateY(-4px);
405
- }
406
-
407
- .epub-reader__mbar .epub-reader__btn {
408
- flex: 1 1 0;
409
- text-align: center;
410
- padding: 10px 8px;
411
- font-size: 13px;
412
- }
413
-
414
- .epub-reader__moverlay {
415
- position: absolute;
416
- inset: 0;
417
- background: rgba(0, 0, 0, 0.35);
418
- z-index: 20;
419
- }
420
-
421
- .epub-reader__msheet {
422
- position: absolute;
423
- left: 0;
424
- right: 0;
425
- bottom: 0;
426
- height: 60%;
427
- max-height: calc(100% - 70px);
428
- background: var(--epub-reader-panel-bg);
429
- color: var(--epub-reader-panel-fg);
430
- border-top: 1px solid var(--epub-reader-border);
431
- border-top-left-radius: calc(var(--epub-reader-radius) + 10px);
432
- border-top-right-radius: calc(var(--epub-reader-radius) + 10px);
433
- transform: translateY(100%);
434
- transition: transform 0.25s ease;
435
- z-index: 30;
436
- display: flex;
437
- flex-direction: column;
438
- pointer-events: none;
439
- }
440
-
441
- .epub-reader__msheet.is-open {
442
- transform: translateY(0);
443
- pointer-events: auto;
444
- }
445
-
446
- .epub-reader__msheet-header {
447
- display: flex;
448
- align-items: center;
449
- justify-content: space-between;
450
- gap: 8px;
451
- padding: 12px 12px 10px;
452
- border-bottom: 1px solid var(--epub-reader-border);
453
- }
454
-
455
- .epub-reader__msheet-title {
456
- font-size: 14px;
457
- font-weight: 800;
458
- }
459
-
460
- .epub-reader__msheet-body {
461
- padding: 12px;
462
- overflow: auto;
463
- flex: 1;
464
- }
465
-
466
- .epub-reader__mprogress {
467
- display: flex;
468
- flex-direction: column;
469
- gap: 10px;
470
- margin-top: 10px;
471
- }
472
-
473
- .epub-reader__mprogress .epub-reader__range {
474
- width: 100%;
475
- }
476
-
477
- .epub-reader__mprogress-percent {
478
- text-align: center;
479
- font-weight: 800;
480
- color: var(--epub-reader-accent);
481
- }
482
-
483
- .epub-reader__mfont {
484
- display: flex;
485
- align-items: center;
486
- justify-content: center;
487
- gap: 12px;
488
- }
489
-
490
- `;
491
-
492
- // src/styles/ensureStyle.ts
493
- var STYLE_ELEMENT_ID = "somecat-epub-reader-style";
494
- var ensureEpubReaderStyle = () => {
495
- if (typeof document === "undefined") return;
496
- if (document.getElementById(STYLE_ELEMENT_ID)) return;
497
- const style = document.createElement("style");
498
- style.id = STYLE_ELEMENT_ID;
499
- style.textContent = epubReaderCssText;
500
- (document.head ?? document.documentElement).append(style);
501
- };
5
+ // src/react/EBookReader.tsx
502
6
  var getContentCSS = (fontSize, isDark, extraCSS) => `
503
7
  @namespace epub "http://www.idpf.org/2007/ops";
504
8
  html {
@@ -706,7 +210,8 @@ var icons = {
706
210
  minus: '<path d="M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
707
211
  x: '<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
708
212
  type: '<path d="M4 7V4h16v3M9 20h6M12 4v16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
709
- sliders: '<path d="M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M1 14h6M9 8h6M17 16h6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>'
213
+ sliders: '<path d="M4 21v-7M4 10V3M12 21v-9M12 8V3M20 21v-5M20 12V3M1 14h6M9 8h6M17 16h6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
214
+ settings: '<path fill="currentColor" fill-rule="evenodd" d="M12.563 3.2h-1.126l-.645 2.578l-.647.2a6.3 6.3 0 0 0-1.091.452l-.599.317l-2.28-1.368l-.796.797l1.368 2.28l-.317.598a6.3 6.3 0 0 0-.453 1.091l-.199.647l-2.578.645v1.126l2.578.645l.2.647q.173.568.452 1.091l.317.599l-1.368 2.28l.797.796l2.28-1.368l.598.317q.523.278 1.091.453l.647.199l.645 2.578h1.126l.645-2.578l.647-.2a6.3 6.3 0 0 0 1.091-.452l.599-.317l2.28 1.368l.796-.797l-1.368-2.28l.317-.598q.278-.523.453-1.091l.199-.647l2.578-.645v-1.126l-2.578-.645l-.2-.647a6.3 6.3 0 0 0-.452-1.091l-.317-.599l1.368-2.28l-.797-.796l-2.28 1.368l-.598-.317a6.3 6.3 0 0 0-1.091-.453l-.647-.199zm2.945 2.17l1.833-1.1a1 1 0 0 1 1.221.15l1.018 1.018a1 1 0 0 1 .15 1.221l-1.1 1.833q.33.62.54 1.3l2.073.519a1 1 0 0 1 .757.97v1.438a1 1 0 0 1-.757.97l-2.073.519q-.21.68-.54 1.3l1.1 1.833a1 1 0 0 1-.15 1.221l-1.018 1.018a1 1 0 0 1-1.221.15l-1.833-1.1q-.62.33-1.3.54l-.519 2.073a1 1 0 0 1-.97.757h-1.438a1 1 0 0 1-.97-.757l-.519-2.073a7.5 7.5 0 0 1-1.3-.54l-1.833 1.1a1 1 0 0 1-1.221-.15L4.42 18.562a1 1 0 0 1-.15-1.221l1.1-1.833a7.5 7.5 0 0 1-.54-1.3l-2.073-.519A1 1 0 0 1 2 12.72v-1.438a1 1 0 0 1 .757-.97l2.073-.519q.21-.68.54-1.3L4.27 6.66a1 1 0 0 1 .15-1.221L5.438 4.42a1 1 0 0 1 1.221-.15l1.833 1.1q.62-.33 1.3-.54l.519-2.073A1 1 0 0 1 11.28 2h1.438a1 1 0 0 1 .97.757l.519 2.073q.68.21 1.3.54zM12 14.8a2.8 2.8 0 1 0 0-5.6a2.8 2.8 0 0 0 0 5.6m0 1.2a4 4 0 1 1 0-8a4 4 0 0 1 0 8"/>'
710
215
  };
711
216
  var SvgIcon = ({ name, size = 24, color = "currentColor", className }) => {
712
217
  const iconPath = icons[name] || "";
@@ -780,7 +285,7 @@ var DesktopBottomBar = ({
780
285
  }) => {
781
286
  return /* @__PURE__ */ jsxs("div", { className: "epub-reader__bottom", children: [
782
287
  /* @__PURE__ */ jsxs("div", { className: "epub-reader__bottom-left", children: [
783
- /* @__PURE__ */ jsx("span", { className: "epub-reader__status", children: status === "error" ? errorText || "\u9519\u8BEF" : status === "opening" ? "\u6B63\u5728\u6253\u5F00\u2026" : "\u5C31\u7EEA" }),
288
+ /* @__PURE__ */ jsx("span", { className: "epub-reader__status", children: status === "error" ? errorText || "\u9519\u8BEF" : status === "opening" ? "\u6B63\u5728\u6253\u5F00\u2026" : "" }),
784
289
  sectionLabel ? /* @__PURE__ */ jsx("span", { className: "epub-reader__section", children: sectionLabel }) : null
785
290
  ] }),
786
291
  /* @__PURE__ */ jsxs("div", { className: "epub-reader__bottom-right", children: [
@@ -953,6 +458,7 @@ var MobileUI = ({
953
458
  activePanel,
954
459
  onTogglePanel,
955
460
  onClosePanel,
461
+ toolbarRight,
956
462
  toc,
957
463
  search,
958
464
  status,
@@ -974,9 +480,80 @@ var MobileUI = ({
974
480
  onToggleDarkMode,
975
481
  onFontSizeChange
976
482
  }) => {
977
- const mobileTitle = activePanel === "menu" ? "\u76EE\u5F55" : activePanel === "search" ? "\u641C\u7D22" : activePanel === "progress" ? "\u8FDB\u5EA6" : activePanel === "theme" ? "\u660E\u6697" : activePanel === "font" ? "\u5B57\u53F7" : "";
483
+ const mobileTitle = activePanel === "menu" ? "\u76EE\u5F55" : activePanel === "search" ? "\u641C\u7D22" : activePanel === "progress" ? "\u8FDB\u5EA6" : activePanel === "settings" ? "\u8BBE\u7F6E" : "";
484
+ const displayedFontSize = Math.min(40, Math.max(10, Math.round(fontSize / 5)));
485
+ const [fontSliderValue, setFontSliderValue] = useState(displayedFontSize);
486
+ const [isFontDragging, setIsFontDragging] = useState(false);
487
+ const fontDebounceRef = useRef(null);
488
+ const fontPendingRef = useRef(displayedFontSize);
489
+ const fontSliderWrapRef = useRef(null);
490
+ const [fontSliderWidth, setFontSliderWidth] = useState(0);
491
+ const fontThumbSize = 34;
492
+ const fontMin = 10;
493
+ const fontMax = 40;
494
+ useEffect(() => {
495
+ setFontSliderValue(displayedFontSize);
496
+ fontPendingRef.current = displayedFontSize;
497
+ }, [displayedFontSize]);
498
+ useEffect(() => {
499
+ return () => {
500
+ if (fontDebounceRef.current) {
501
+ clearTimeout(fontDebounceRef.current);
502
+ fontDebounceRef.current = null;
503
+ }
504
+ };
505
+ }, []);
506
+ useEffect(() => {
507
+ if (activePanel !== "settings") return;
508
+ const el = fontSliderWrapRef.current;
509
+ if (!el) return;
510
+ const update = () => setFontSliderWidth(el.getBoundingClientRect().width);
511
+ update();
512
+ const ro = new ResizeObserver(() => update());
513
+ ro.observe(el);
514
+ return () => ro.disconnect();
515
+ }, [activePanel]);
516
+ const fontProgressPercent = (fontSliderValue - fontMin) / (fontMax - fontMin) * 100;
517
+ const fontThumbLeft = (() => {
518
+ if (!fontSliderWidth) return 0;
519
+ const percent = (fontSliderValue - fontMin) / (fontMax - fontMin);
520
+ const half = fontThumbSize / 2;
521
+ return Math.min(fontSliderWidth - half, Math.max(half, half + percent * (fontSliderWidth - fontThumbSize)));
522
+ })();
523
+ const flushFontSize = () => {
524
+ if (fontDebounceRef.current) {
525
+ clearTimeout(fontDebounceRef.current);
526
+ fontDebounceRef.current = null;
527
+ }
528
+ onFontSizeChange(fontPendingRef.current * 5);
529
+ };
530
+ const scheduleFontSize = (next) => {
531
+ fontPendingRef.current = next;
532
+ if (fontDebounceRef.current) clearTimeout(fontDebounceRef.current);
533
+ fontDebounceRef.current = window.setTimeout(() => {
534
+ fontDebounceRef.current = null;
535
+ onFontSizeChange(fontPendingRef.current * 5);
536
+ }, 80);
537
+ };
978
538
  const [tooltip, setTooltip] = useState(null);
979
539
  const timerRef = useRef(null);
540
+ const ignoreToggleRef = useRef(false);
541
+ const markIgnoreToggle = () => {
542
+ ignoreToggleRef.current = true;
543
+ window.setTimeout(() => {
544
+ ignoreToggleRef.current = false;
545
+ }, 350);
546
+ };
547
+ const closePanelSafe = () => {
548
+ onClosePanel();
549
+ handleTouchEnd();
550
+ const el = document.activeElement;
551
+ if (el && el instanceof HTMLElement) el.blur();
552
+ };
553
+ const togglePanelSafe = (panel) => {
554
+ if (ignoreToggleRef.current) return;
555
+ onTogglePanel(panel);
556
+ };
980
557
  const handleTouchStart = (e, text) => {
981
558
  const rect = e.currentTarget.getBoundingClientRect();
982
559
  timerRef.current = window.setTimeout(() => {
@@ -993,6 +570,42 @@ var MobileUI = ({
993
570
  }
994
571
  setTooltip(null);
995
572
  };
573
+ const sheetRef = useRef(null);
574
+ const dragRef = useRef({ startY: 0, currentY: 0, isDragging: false });
575
+ const handleHeaderTouchStart = (e) => {
576
+ handleTouchEnd();
577
+ dragRef.current.startY = e.touches[0].clientY;
578
+ dragRef.current.isDragging = true;
579
+ if (sheetRef.current) {
580
+ sheetRef.current.style.transition = "none";
581
+ }
582
+ };
583
+ const handleHeaderTouchMove = (e) => {
584
+ if (!dragRef.current.isDragging) return;
585
+ e.preventDefault();
586
+ const deltaY = e.touches[0].clientY - dragRef.current.startY;
587
+ if (deltaY > 0 && sheetRef.current) {
588
+ sheetRef.current.style.transform = `translateY(${deltaY}px)`;
589
+ dragRef.current.currentY = deltaY;
590
+ }
591
+ };
592
+ const handleHeaderTouchEnd = () => {
593
+ if (!dragRef.current.isDragging) return;
594
+ dragRef.current.isDragging = false;
595
+ if (sheetRef.current) {
596
+ sheetRef.current.style.transition = "";
597
+ if (dragRef.current.currentY > 80) {
598
+ markIgnoreToggle();
599
+ closePanelSafe();
600
+ setTimeout(() => {
601
+ if (sheetRef.current) sheetRef.current.style.transform = "";
602
+ }, 300);
603
+ } else {
604
+ sheetRef.current.style.transform = "";
605
+ }
606
+ }
607
+ dragRef.current.currentY = 0;
608
+ };
996
609
  return /* @__PURE__ */ jsxs(Fragment, { children: [
997
610
  /* @__PURE__ */ jsxs("div", { className: `epub-reader__mbar ${barVisible ? "is-visible" : ""}`, children: [
998
611
  tooltip && /* @__PURE__ */ jsx(
@@ -1017,208 +630,259 @@ var MobileUI = ({
1017
630
  children: tooltip.text
1018
631
  }
1019
632
  ),
1020
- /* @__PURE__ */ jsx(
1021
- "button",
1022
- {
1023
- type: "button",
1024
- className: "epub-reader__btn",
1025
- onClick: () => onTogglePanel("menu"),
1026
- "aria-pressed": activePanel === "menu",
1027
- onTouchStart: (e) => handleTouchStart(e, "\u76EE\u5F55"),
1028
- onTouchEnd: handleTouchEnd,
1029
- onTouchCancel: handleTouchEnd,
1030
- title: "\u76EE\u5F55",
1031
- children: /* @__PURE__ */ jsx(SvgIcon, { name: "list" })
1032
- }
1033
- ),
1034
- /* @__PURE__ */ jsx(
1035
- "button",
1036
- {
1037
- type: "button",
1038
- className: "epub-reader__btn",
1039
- onClick: () => onTogglePanel("search"),
1040
- "aria-pressed": activePanel === "search",
1041
- onTouchStart: (e) => handleTouchStart(e, "\u641C\u7D22"),
1042
- onTouchEnd: handleTouchEnd,
1043
- onTouchCancel: handleTouchEnd,
1044
- title: "\u641C\u7D22",
1045
- children: /* @__PURE__ */ jsx(SvgIcon, { name: "search" })
1046
- }
1047
- ),
1048
- /* @__PURE__ */ jsx(
1049
- "button",
1050
- {
1051
- type: "button",
1052
- className: "epub-reader__btn",
1053
- onClick: () => onTogglePanel("progress"),
1054
- "aria-pressed": activePanel === "progress",
1055
- onTouchStart: (e) => handleTouchStart(e, "\u8FDB\u5EA6"),
1056
- onTouchEnd: handleTouchEnd,
1057
- onTouchCancel: handleTouchEnd,
1058
- title: "\u8FDB\u5EA6",
1059
- children: /* @__PURE__ */ jsx(SvgIcon, { name: "sliders" })
1060
- }
1061
- ),
1062
- /* @__PURE__ */ jsx(
1063
- "button",
1064
- {
1065
- type: "button",
1066
- className: "epub-reader__btn",
1067
- onClick: () => onTogglePanel("theme"),
1068
- "aria-pressed": activePanel === "theme",
1069
- onTouchStart: (e) => handleTouchStart(e, "\u660E\u6697"),
1070
- onTouchEnd: handleTouchEnd,
1071
- onTouchCancel: handleTouchEnd,
1072
- title: "\u660E\u6697",
1073
- children: /* @__PURE__ */ jsx(SvgIcon, { name: "sun" })
1074
- }
1075
- ),
1076
- /* @__PURE__ */ jsx(
1077
- "button",
1078
- {
1079
- type: "button",
1080
- className: "epub-reader__btn",
1081
- onClick: () => onTogglePanel("font"),
1082
- "aria-pressed": activePanel === "font",
1083
- onTouchStart: (e) => handleTouchStart(e, "\u5B57\u53F7"),
1084
- onTouchEnd: handleTouchEnd,
1085
- onTouchCancel: handleTouchEnd,
1086
- title: "\u5B57\u53F7",
1087
- children: /* @__PURE__ */ jsx(SvgIcon, { name: "type" })
1088
- }
1089
- )
1090
- ] }),
1091
- activePanel ? /* @__PURE__ */ jsx("div", { className: "epub-reader__moverlay", onClick: onClosePanel }) : null,
1092
- /* @__PURE__ */ jsxs("div", { className: `epub-reader__msheet ${activePanel ? "is-open" : ""}`, "aria-hidden": !activePanel, children: [
1093
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__msheet-header", children: [
1094
- /* @__PURE__ */ jsx("div", { className: "epub-reader__msheet-title", children: mobileTitle }),
1095
- /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__btn", onClick: onClosePanel, children: /* @__PURE__ */ jsx(SvgIcon, { name: "x" }) })
1096
- ] }),
1097
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__msheet-body", children: [
1098
- activePanel === "menu" ? toc.length ? /* @__PURE__ */ jsx(
1099
- TocTree,
633
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__mbar-left", children: [
634
+ /* @__PURE__ */ jsx(
635
+ "button",
1100
636
  {
1101
- items: toc,
1102
- onSelect: (href) => {
1103
- onTocSelect(href);
1104
- onClosePanel();
1105
- }
637
+ type: "button",
638
+ className: "epub-reader__btn",
639
+ onClick: () => togglePanelSafe("menu"),
640
+ "aria-pressed": activePanel === "menu",
641
+ onTouchStart: (e) => handleTouchStart(e, "\u76EE\u5F55"),
642
+ onTouchEnd: handleTouchEnd,
643
+ onTouchCancel: handleTouchEnd,
644
+ title: "\u76EE\u5F55",
645
+ children: /* @__PURE__ */ jsx(SvgIcon, { name: "list" })
1106
646
  }
1107
- ) : /* @__PURE__ */ jsx("div", { className: "epub-reader__empty", children: "\u672A\u627E\u5230\u76EE\u5F55" }) : null,
1108
- activePanel === "search" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1109
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__field", children: [
1110
- /* @__PURE__ */ jsx(
1111
- "input",
647
+ ),
648
+ /* @__PURE__ */ jsx(
649
+ "button",
650
+ {
651
+ type: "button",
652
+ className: "epub-reader__btn",
653
+ onClick: () => togglePanelSafe("search"),
654
+ "aria-pressed": activePanel === "search",
655
+ onTouchStart: (e) => handleTouchStart(e, "\u641C\u7D22"),
656
+ onTouchEnd: handleTouchEnd,
657
+ onTouchCancel: handleTouchEnd,
658
+ title: "\u641C\u7D22",
659
+ children: /* @__PURE__ */ jsx(SvgIcon, { name: "search" })
660
+ }
661
+ ),
662
+ /* @__PURE__ */ jsx(
663
+ "button",
664
+ {
665
+ type: "button",
666
+ className: "epub-reader__btn",
667
+ onClick: () => togglePanelSafe("progress"),
668
+ "aria-pressed": activePanel === "progress",
669
+ onTouchStart: (e) => handleTouchStart(e, "\u8FDB\u5EA6"),
670
+ onTouchEnd: handleTouchEnd,
671
+ onTouchCancel: handleTouchEnd,
672
+ title: "\u8FDB\u5EA6",
673
+ children: /* @__PURE__ */ jsx(SvgIcon, { name: "sliders" })
674
+ }
675
+ ),
676
+ /* @__PURE__ */ jsx(
677
+ "button",
678
+ {
679
+ type: "button",
680
+ className: "epub-reader__btn",
681
+ onClick: () => togglePanelSafe("settings"),
682
+ "aria-pressed": activePanel === "settings",
683
+ onTouchStart: (e) => handleTouchStart(e, "\u8BBE\u7F6E"),
684
+ onTouchEnd: handleTouchEnd,
685
+ onTouchCancel: handleTouchEnd,
686
+ title: "\u8BBE\u7F6E",
687
+ children: /* @__PURE__ */ jsx(SvgIcon, { name: "settings" })
688
+ }
689
+ )
690
+ ] }),
691
+ toolbarRight ? /* @__PURE__ */ jsx("div", { className: "epub-reader__mbar-right", children: toolbarRight }) : null
692
+ ] }),
693
+ activePanel ? /* @__PURE__ */ jsx("div", { className: "epub-reader__moverlay", onClick: closePanelSafe }) : null,
694
+ /* @__PURE__ */ jsxs(
695
+ "div",
696
+ {
697
+ ref: sheetRef,
698
+ className: `epub-reader__msheet ${activePanel ? "is-open" : ""}`,
699
+ "aria-hidden": !activePanel,
700
+ children: [
701
+ /* @__PURE__ */ jsxs(
702
+ "div",
703
+ {
704
+ className: "epub-reader__msheet-header",
705
+ onTouchStart: handleHeaderTouchStart,
706
+ onTouchMove: handleHeaderTouchMove,
707
+ onTouchEnd: handleHeaderTouchEnd,
708
+ children: [
709
+ /* @__PURE__ */ jsx("div", { className: "epub-reader__msheet-title", children: mobileTitle }),
710
+ /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__btn", onClick: closePanelSafe, children: /* @__PURE__ */ jsx(SvgIcon, { name: "x" }) })
711
+ ]
712
+ }
713
+ ),
714
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__msheet-body", children: [
715
+ activePanel === "menu" ? toc.length ? /* @__PURE__ */ jsx(
716
+ TocTree,
1112
717
  {
1113
- className: "epub-reader__input",
1114
- placeholder: "\u8F93\u5165\u5173\u952E\u8BCD",
1115
- value: search.query,
1116
- onChange: (e) => {
1117
- const v = e.target.value;
1118
- onSearchQueryChange(v);
1119
- if (!v.trim()) onSearch("");
1120
- },
1121
- disabled: status !== "ready",
1122
- onKeyDown: (e) => {
1123
- if (e.key === "Enter") onSearch(search.query);
718
+ items: toc,
719
+ onSelect: (href) => {
720
+ onTocSelect(href);
721
+ closePanelSafe();
1124
722
  }
1125
723
  }
1126
- ),
1127
- /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__btn", onClick: () => onSearch(search.query), disabled: status !== "ready", children: "\u641C\u7D22" })
1128
- ] }),
1129
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__checks", children: [
1130
- /* @__PURE__ */ jsxs("label", { className: "epub-reader__check", children: [
1131
- /* @__PURE__ */ jsx(
1132
- "input",
1133
- {
1134
- type: "checkbox",
1135
- checked: Boolean(search.options.matchCase),
1136
- onChange: (e) => onSearchOptionChange({ matchCase: e.target.checked })
1137
- }
1138
- ),
1139
- "\u533A\u5206\u5927\u5C0F\u5199"
1140
- ] }),
1141
- /* @__PURE__ */ jsxs("label", { className: "epub-reader__check", children: [
724
+ ) : /* @__PURE__ */ jsx("div", { className: "epub-reader__empty", children: "\u672A\u627E\u5230\u76EE\u5F55" }) : null,
725
+ activePanel === "search" ? /* @__PURE__ */ jsxs(Fragment, { children: [
726
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__field", children: [
727
+ /* @__PURE__ */ jsx(
728
+ "input",
729
+ {
730
+ className: "epub-reader__input",
731
+ placeholder: "\u8F93\u5165\u5173\u952E\u8BCD",
732
+ value: search.query,
733
+ onChange: (e) => {
734
+ const v = e.target.value;
735
+ onSearchQueryChange(v);
736
+ if (!v.trim()) onSearch("");
737
+ },
738
+ disabled: status !== "ready",
739
+ onKeyDown: (e) => {
740
+ if (e.key === "Enter") onSearch(search.query);
741
+ }
742
+ }
743
+ ),
744
+ /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__btn", onClick: () => onSearch(search.query), disabled: status !== "ready", children: "\u641C\u7D22" })
745
+ ] }),
746
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__checks", children: [
747
+ /* @__PURE__ */ jsxs("label", { className: "epub-reader__check", children: [
748
+ /* @__PURE__ */ jsx(
749
+ "input",
750
+ {
751
+ type: "checkbox",
752
+ checked: Boolean(search.options.matchCase),
753
+ onChange: (e) => onSearchOptionChange({ matchCase: e.target.checked })
754
+ }
755
+ ),
756
+ "\u533A\u5206\u5927\u5C0F\u5199"
757
+ ] }),
758
+ /* @__PURE__ */ jsxs("label", { className: "epub-reader__check", children: [
759
+ /* @__PURE__ */ jsx(
760
+ "input",
761
+ {
762
+ type: "checkbox",
763
+ checked: Boolean(search.options.wholeWords),
764
+ onChange: (e) => onSearchOptionChange({ wholeWords: e.target.checked })
765
+ }
766
+ ),
767
+ "\u5168\u8BCD\u5339\u914D"
768
+ ] }),
769
+ /* @__PURE__ */ jsxs("label", { className: "epub-reader__check", children: [
770
+ /* @__PURE__ */ jsx(
771
+ "input",
772
+ {
773
+ type: "checkbox",
774
+ checked: Boolean(search.options.matchDiacritics),
775
+ onChange: (e) => onSearchOptionChange({ matchDiacritics: e.target.checked })
776
+ }
777
+ ),
778
+ "\u533A\u5206\u53D8\u97F3"
779
+ ] })
780
+ ] }),
781
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__meta", children: [
782
+ /* @__PURE__ */ jsxs("span", { children: [
783
+ "\u8FDB\u5EA6 ",
784
+ search.progressPercent,
785
+ "%"
786
+ ] }),
787
+ search.searching ? /* @__PURE__ */ jsx("span", { children: "\u641C\u7D22\u4E2D\u2026" }) : null,
788
+ search.searching ? /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__link", onClick: onCancelSearch, children: "\u53D6\u6D88" }) : null
789
+ ] }),
790
+ search.results.length ? /* @__PURE__ */ jsx(SearchResultList, { results: search.results, onSelect: onSearchResultSelect }) : /* @__PURE__ */ jsx("div", { className: "epub-reader__empty", children: search.query.trim() ? "\u65E0\u5339\u914D\u7ED3\u679C" : "\u8BF7\u8F93\u5165\u5173\u952E\u8BCD" })
791
+ ] }) : null,
792
+ activePanel === "progress" ? /* @__PURE__ */ jsxs(Fragment, { children: [
793
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__meta", children: [
794
+ /* @__PURE__ */ jsx("span", { className: "epub-reader__status", children: status === "error" ? errorText || "\u9519\u8BEF" : status === "opening" ? "\u6B63\u5728\u6253\u5F00\u2026" : "" }),
795
+ sectionLabel ? /* @__PURE__ */ jsx("span", { children: sectionLabel }) : null
796
+ ] }),
797
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__mprogress", children: [
798
+ /* @__PURE__ */ jsx(
799
+ "input",
800
+ {
801
+ className: "epub-reader__range",
802
+ type: "range",
803
+ min: 0,
804
+ max: 100,
805
+ step: 1,
806
+ value: displayedPercent,
807
+ onChange: (e) => {
808
+ onSeekStart();
809
+ onSeekChange(Number(e.target.value));
810
+ },
811
+ onPointerUp: (e) => {
812
+ const v = Number(e.target.value);
813
+ onSeekEnd(v);
814
+ },
815
+ onKeyUp: (e) => {
816
+ if (e.key !== "Enter") return;
817
+ const v = Number(e.target.value);
818
+ onSeekCommit(v);
819
+ }
820
+ }
821
+ ),
822
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__mprogress-percent", children: [
823
+ displayedPercent,
824
+ "%"
825
+ ] })
826
+ ] })
827
+ ] }) : null,
828
+ activePanel === "settings" ? /* @__PURE__ */ jsxs("div", { className: "epub-reader__msettings", children: [
829
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__mfont-range", children: [
830
+ /* @__PURE__ */ jsx("div", { className: "epub-reader__mfont-a is-small", children: "A" }),
831
+ /* @__PURE__ */ jsxs("div", { ref: fontSliderWrapRef, className: `epub-reader__mfont-slider ${isFontDragging ? "is-dragging" : ""}`, children: [
832
+ /* @__PURE__ */ jsx(
833
+ "input",
834
+ {
835
+ className: "epub-reader__range",
836
+ type: "range",
837
+ min: fontMin,
838
+ max: fontMax,
839
+ step: 1,
840
+ value: fontSliderValue,
841
+ onChange: (e) => {
842
+ const next = Number(e.target.value);
843
+ setFontSliderValue(next);
844
+ scheduleFontSize(next);
845
+ },
846
+ onPointerDown: () => setIsFontDragging(true),
847
+ onPointerUp: () => {
848
+ setIsFontDragging(false);
849
+ flushFontSize();
850
+ },
851
+ onPointerCancel: () => {
852
+ setIsFontDragging(false);
853
+ flushFontSize();
854
+ },
855
+ onKeyUp: (e) => {
856
+ if (e.key !== "Enter") return;
857
+ flushFontSize();
858
+ },
859
+ style: {
860
+ background: `linear-gradient(to right, var(--epub-reader-range-fill) 0%, var(--epub-reader-range-fill) ${fontProgressPercent}%, var(--epub-reader-range-track) ${fontProgressPercent}%, var(--epub-reader-range-track) 100%)`
861
+ },
862
+ "aria-label": "\u5B57\u53F7"
863
+ }
864
+ ),
865
+ /* @__PURE__ */ jsx("div", { className: "epub-reader__mfont-thumb", style: { left: `${fontThumbLeft}px`, width: `${fontThumbSize}px`, height: `${fontThumbSize}px` }, children: fontSliderValue })
866
+ ] }),
867
+ /* @__PURE__ */ jsx("div", { className: "epub-reader__mfont-a is-big", children: "A" })
868
+ ] }),
1142
869
  /* @__PURE__ */ jsx(
1143
- "input",
870
+ "button",
1144
871
  {
1145
- type: "checkbox",
1146
- checked: Boolean(search.options.wholeWords),
1147
- onChange: (e) => onSearchOptionChange({ wholeWords: e.target.checked })
872
+ type: "button",
873
+ className: "epub-reader__btn",
874
+ onClick: () => onToggleDarkMode(!darkMode),
875
+ "aria-pressed": darkMode,
876
+ "aria-label": darkMode ? "\u6697\u9ED1\u6A21\u5F0F\uFF1A\u5F00\uFF0C\u70B9\u51FB\u5207\u6362\u5230\u4EAE\u8272" : "\u6697\u9ED1\u6A21\u5F0F\uFF1A\u5173\uFF0C\u70B9\u51FB\u5207\u6362\u5230\u6697\u9ED1",
877
+ title: darkMode ? "\u5207\u6362\u5230\u4EAE\u8272" : "\u5207\u6362\u5230\u6697\u9ED1",
878
+ children: /* @__PURE__ */ jsx(SvgIcon, { name: darkMode ? "sun" : "moon" })
1148
879
  }
1149
- ),
1150
- "\u5168\u8BCD\u5339\u914D"
1151
- ] }),
1152
- /* @__PURE__ */ jsxs("label", { className: "epub-reader__check", children: [
1153
- /* @__PURE__ */ jsx(
1154
- "input",
1155
- {
1156
- type: "checkbox",
1157
- checked: Boolean(search.options.matchDiacritics),
1158
- onChange: (e) => onSearchOptionChange({ matchDiacritics: e.target.checked })
1159
- }
1160
- ),
1161
- "\u533A\u5206\u53D8\u97F3"
1162
- ] })
1163
- ] }),
1164
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__meta", children: [
1165
- /* @__PURE__ */ jsxs("span", { children: [
1166
- "\u8FDB\u5EA6 ",
1167
- search.progressPercent,
1168
- "%"
1169
- ] }),
1170
- search.searching ? /* @__PURE__ */ jsx("span", { children: "\u641C\u7D22\u4E2D\u2026" }) : null,
1171
- search.searching ? /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__link", onClick: onCancelSearch, children: "\u53D6\u6D88" }) : null
1172
- ] }),
1173
- search.results.length ? /* @__PURE__ */ jsx(SearchResultList, { results: search.results, onSelect: onSearchResultSelect }) : /* @__PURE__ */ jsx("div", { className: "epub-reader__empty", children: search.query.trim() ? "\u65E0\u5339\u914D\u7ED3\u679C" : "\u8BF7\u8F93\u5165\u5173\u952E\u8BCD" })
1174
- ] }) : null,
1175
- activePanel === "progress" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1176
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__meta", children: [
1177
- /* @__PURE__ */ jsx("span", { className: "epub-reader__status", children: status === "error" ? errorText || "\u9519\u8BEF" : status === "opening" ? "\u6B63\u5728\u6253\u5F00\u2026" : "\u5C31\u7EEA" }),
1178
- sectionLabel ? /* @__PURE__ */ jsx("span", { children: sectionLabel }) : null
1179
- ] }),
1180
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__mprogress", children: [
1181
- /* @__PURE__ */ jsx(
1182
- "input",
1183
- {
1184
- className: "epub-reader__range",
1185
- type: "range",
1186
- min: 0,
1187
- max: 100,
1188
- step: 1,
1189
- value: displayedPercent,
1190
- onChange: (e) => {
1191
- onSeekStart();
1192
- onSeekChange(Number(e.target.value));
1193
- },
1194
- onPointerUp: (e) => {
1195
- const v = Number(e.target.value);
1196
- onSeekEnd(v);
1197
- },
1198
- onKeyUp: (e) => {
1199
- if (e.key !== "Enter") return;
1200
- const v = Number(e.target.value);
1201
- onSeekCommit(v);
1202
- }
1203
- }
1204
- ),
1205
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__mprogress-percent", children: [
1206
- displayedPercent,
1207
- "%"
1208
- ] })
880
+ )
881
+ ] }) : null
1209
882
  ] })
1210
- ] }) : null,
1211
- activePanel === "theme" ? /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__btn", onClick: () => onToggleDarkMode(!darkMode), children: darkMode ? "\u5207\u6362\u5230\u4EAE\u8272" : "\u5207\u6362\u5230\u6697\u9ED1" }) : null,
1212
- activePanel === "font" ? /* @__PURE__ */ jsxs("div", { className: "epub-reader__mfont", children: [
1213
- /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__btn", onClick: () => onFontSizeChange(fontSize - 10), children: "A-" }),
1214
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__font", children: [
1215
- fontSize,
1216
- "%"
1217
- ] }),
1218
- /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__btn", onClick: () => onFontSizeChange(fontSize + 10), children: "A+" })
1219
- ] }) : null
1220
- ] })
1221
- ] })
883
+ ]
884
+ }
885
+ )
1222
886
  ] });
1223
887
  };
1224
888
  var MOBILE_MAX_WIDTH = 768;
@@ -1269,6 +933,7 @@ var EBookReader = forwardRef(function EBookReader2({
1269
933
  fileUrl,
1270
934
  className,
1271
935
  style,
936
+ mobileToolbarRight,
1272
937
  defaultFontSize = 100,
1273
938
  fontSize: controlledFontSize,
1274
939
  onFontSizeChange,
@@ -1292,6 +957,7 @@ var EBookReader = forwardRef(function EBookReader2({
1292
957
  const [progressInfo, setProgressInfo] = useState(null);
1293
958
  const [isSeeking, setIsSeeking] = useState(false);
1294
959
  const [seekPercent, setSeekPercent] = useState(0);
960
+ const [downloadLoading, setDownloadLoading] = useState(null);
1295
961
  const [uncontrolledFontSize, setUncontrolledFontSize] = useState(defaultFontSize);
1296
962
  const [uncontrolledDarkMode, setUncontrolledDarkMode] = useState(defaultDarkMode);
1297
963
  const fontSize = controlledFontSize ?? uncontrolledFontSize;
@@ -1309,6 +975,7 @@ var EBookReader = forwardRef(function EBookReader2({
1309
975
  const layoutRef = useRef(layout);
1310
976
  const boundDocsRef = useRef(/* @__PURE__ */ new WeakSet());
1311
977
  const gestureRef = useRef({ startX: 0, startY: 0, startAt: 0, tracking: false, moved: false, actionTaken: false });
978
+ const isDraggingRef = useRef(false);
1312
979
  const percentage = useMemo(() => Math.round((progressInfo?.fraction ?? 0) * 100), [progressInfo]);
1313
980
  const displayedPercent = isSeeking ? seekPercent : percentage;
1314
981
  const sectionLabel = progressInfo?.tocItem?.label ?? "";
@@ -1498,6 +1165,9 @@ var EBookReader = forwardRef(function EBookReader2({
1498
1165
  onError: (e) => onError?.(e),
1499
1166
  onProgress: (info) => {
1500
1167
  setProgressInfo(info);
1168
+ if (!isDraggingRef.current) {
1169
+ setIsSeeking(false);
1170
+ }
1501
1171
  onProgress?.(info);
1502
1172
  },
1503
1173
  onToc: (items) => setToc(items),
@@ -1536,18 +1206,28 @@ var EBookReader = forwardRef(function EBookReader2({
1536
1206
  if (!nextUrl) return;
1537
1207
  const controller = new AbortController();
1538
1208
  prepareOpen();
1209
+ setDownloadLoading("download");
1210
+ let active = true;
1539
1211
  void (async () => {
1540
1212
  try {
1541
1213
  const downloaded = await downloadEpubAsFile(nextUrl, controller.signal);
1214
+ if (!active) return;
1215
+ setDownloadLoading("open");
1542
1216
  await handleOpenFile(downloaded);
1543
1217
  } catch (e) {
1544
1218
  if (e?.name === "AbortError") return;
1545
1219
  setStatus("error");
1546
1220
  setErrorText(e?.message ? String(e.message) : "\u4E0B\u8F7D\u5931\u8D25");
1547
1221
  onError?.(e);
1222
+ } finally {
1223
+ if (active) setDownloadLoading(null);
1548
1224
  }
1549
1225
  })();
1550
- return () => controller.abort();
1226
+ return () => {
1227
+ active = false;
1228
+ setDownloadLoading(null);
1229
+ controller.abort();
1230
+ };
1551
1231
  }, [file, fileUrl, handleOpenFile, onError, prepareOpen]);
1552
1232
  useEffect(() => {
1553
1233
  readerRef.current?.setDarkMode(darkMode);
@@ -1584,10 +1264,13 @@ var EBookReader = forwardRef(function EBookReader2({
1584
1264
  }),
1585
1265
  []
1586
1266
  );
1587
- const handleSeekStart = useCallback(() => setIsSeeking(true), []);
1267
+ const handleSeekStart = useCallback(() => {
1268
+ setIsSeeking(true);
1269
+ isDraggingRef.current = true;
1270
+ }, []);
1588
1271
  const handleSeekChange = useCallback((v) => setSeekPercent(v), []);
1589
1272
  const handleSeekEnd = useCallback((v) => {
1590
- setIsSeeking(false);
1273
+ isDraggingRef.current = false;
1591
1274
  readerRef.current?.goToFraction(v / 100);
1592
1275
  }, []);
1593
1276
  const handleTocSelect = useCallback((href) => {
@@ -1605,8 +1288,13 @@ var EBookReader = forwardRef(function EBookReader2({
1605
1288
  "data-theme": darkMode ? "dark" : "light",
1606
1289
  "data-layout": layout,
1607
1290
  tabIndex: 0,
1291
+ "aria-busy": downloadLoading != null,
1608
1292
  children: [
1609
1293
  /* @__PURE__ */ jsx("div", { className: "epub-reader__viewer", ref: viewerHostRef }),
1294
+ downloadLoading ? /* @__PURE__ */ jsxs("div", { className: "epub-reader__loading", role: "status", "aria-live": "polite", children: [
1295
+ /* @__PURE__ */ jsx("div", { className: "epub-reader__spinner", "aria-hidden": "true" }),
1296
+ /* @__PURE__ */ jsx("div", { className: "epub-reader__loading-text", children: downloadLoading === "download" ? "\u52A0\u8F7D\u4E2D\u2026" : "\u6E32\u67D3\u4E2D\u2026" })
1297
+ ] }) : null,
1610
1298
  layout === "mobile" ? /* @__PURE__ */ jsx(
1611
1299
  MobileUI,
1612
1300
  {
@@ -1614,6 +1302,7 @@ var EBookReader = forwardRef(function EBookReader2({
1614
1302
  activePanel: mobilePanel,
1615
1303
  onTogglePanel: toggleMobilePanel,
1616
1304
  onClosePanel: closeMobileSheet,
1305
+ toolbarRight: mobileToolbarRight,
1617
1306
  toc,
1618
1307
  search,
1619
1308
  status,
@@ -1694,9 +1383,6 @@ var EBookReader = forwardRef(function EBookReader2({
1694
1383
  );
1695
1384
  });
1696
1385
 
1697
- // src/react/index.ts
1698
- ensureEpubReaderStyle();
1699
-
1700
1386
  export { EBookReader };
1701
1387
  //# sourceMappingURL=react.js.map
1702
1388
  //# sourceMappingURL=react.js.map