@somecat/epub-reader 0.1.1 → 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/vue.cjs CHANGED
@@ -3,503 +3,7 @@
3
3
  var vue = require('vue');
4
4
  require('foliate-js/view.js');
5
5
 
6
- // src/styles/epub-reader.cssText.ts
7
- var epubReaderCssText = `
8
- :root {
9
- --epub-reader-panel-bg: #ffffff;
10
- --epub-reader-panel-fg: #1f2937;
11
- --epub-reader-border: rgba(0, 0, 0, 0.12);
12
- --epub-reader-accent: #2563eb;
13
- --epub-reader-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
14
- --epub-reader-radius: 10px;
15
- --epub-reader-bottom-bar-height: 40px;
16
- }
17
-
18
- .epub-reader-icon {
19
- display: inline-block;
20
- vertical-align: middle;
21
- }
22
-
23
- .epub-reader[data-theme='dark'] {
24
- --epub-reader-panel-bg: #141414;
25
- --epub-reader-panel-fg: #e5e7eb;
26
- --epub-reader-border: rgba(255, 255, 255, 0.14);
27
- --epub-reader-accent: #60a5fa;
28
- --epub-reader-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
29
- }
30
-
31
- .epub-reader {
32
- position: relative;
33
- width: 100%;
34
- height: 100%;
35
- outline: none;
36
- overflow: hidden;
37
- }
38
-
39
- .epub-reader__viewer {
40
- width: 100%;
41
- height: calc(100% - var(--epub-reader-bottom-bar-height));
42
- background: var(--epub-reader-panel-bg);
43
- transition: background-color 0.2s ease;
44
- }
45
-
46
- .epub-reader__toolbar {
47
- position: absolute;
48
- top: 16px;
49
- right: 16px;
50
- z-index: 10;
51
- display: flex;
52
- flex-direction: column;
53
- gap: 10px;
54
- }
55
-
56
- .epub-reader[data-layout='wide'] .epub-reader__toolbar {
57
- top: 50%;
58
- transform: translateY(-50%);
59
- }
60
-
61
- .epub-reader__panel {
62
- width: 88px;
63
- padding: 8px;
64
- border: 1px solid var(--epub-reader-border);
65
- border-radius: var(--epub-reader-radius);
66
- background: color-mix(in srgb, var(--epub-reader-panel-bg) 92%, transparent);
67
- color: var(--epub-reader-panel-fg);
68
- box-shadow: var(--epub-reader-shadow);
69
- backdrop-filter: blur(10px);
70
- display: flex;
71
- flex-direction: column;
72
- gap: 6px;
73
- align-items: stretch;
74
- }
75
-
76
- .epub-reader__btn {
77
- appearance: none;
78
- border: 1px solid var(--epub-reader-border);
79
- background: transparent;
80
- color: inherit;
81
- padding: 6px 8px;
82
- border-radius: 8px;
83
- font-size: 12px;
84
- line-height: 1;
85
- cursor: pointer;
86
- user-select: none;
87
- }
88
-
89
- .epub-reader__btn:disabled {
90
- opacity: 0.6;
91
- cursor: not-allowed;
92
- }
93
-
94
- .epub-reader__btn:hover:not(:disabled) {
95
- border-color: color-mix(in srgb, var(--epub-reader-accent) 60%, var(--epub-reader-border));
96
- }
97
-
98
- .epub-reader__divider {
99
- height: 1px;
100
- background: var(--epub-reader-border);
101
- margin: 2px 0;
102
- }
103
-
104
- .epub-reader__font {
105
- text-align: center;
106
- font-size: 12px;
107
- font-weight: 700;
108
- color: var(--epub-reader-accent);
109
- padding: 2px 0;
110
- }
111
-
112
- .epub-reader__overlay {
113
- position: absolute;
114
- inset: 0;
115
- background: rgba(0, 0, 0, 0.35);
116
- z-index: 20;
117
- }
118
-
119
- .epub-reader__drawer {
120
- position: absolute;
121
- top: 0;
122
- bottom: 0;
123
- left: 0;
124
- width: 320px;
125
- background: var(--epub-reader-panel-bg);
126
- color: var(--epub-reader-panel-fg);
127
- border-right: 1px solid var(--epub-reader-border);
128
- transform: translateX(-100%);
129
- transition: transform 0.2s ease;
130
- z-index: 30;
131
- display: flex;
132
- flex-direction: column;
133
- }
134
-
135
- .epub-reader__drawer.right {
136
- left: auto;
137
- right: 0;
138
- border-right: none;
139
- border-left: 1px solid var(--epub-reader-border);
140
- transform: translateX(100%);
141
- }
142
-
143
- .epub-reader__drawer.is-open {
144
- transform: translateX(0);
145
- }
146
-
147
- .epub-reader__drawer-header {
148
- display: flex;
149
- align-items: center;
150
- justify-content: space-between;
151
- gap: 8px;
152
- padding: 12px;
153
- border-bottom: 1px solid var(--epub-reader-border);
154
- }
155
-
156
- .epub-reader__drawer-title {
157
- font-size: 14px;
158
- font-weight: 700;
159
- }
160
-
161
- .epub-reader__drawer-body {
162
- padding: 12px;
163
- overflow: auto;
164
- flex: 1;
165
- }
166
-
167
- .epub-reader__empty {
168
- padding: 20px 8px;
169
- opacity: 0.7;
170
- text-align: center;
171
- font-size: 12px;
172
- }
173
-
174
- .epub-reader__toc-list {
175
- list-style: none;
176
- padding: 0;
177
- margin: 0;
178
- display: flex;
179
- flex-direction: column;
180
- gap: 4px;
181
- }
182
-
183
- .epub-reader__toc-item {
184
- margin: 0;
185
- }
186
-
187
- .epub-reader__toc-btn {
188
- width: 100%;
189
- text-align: left;
190
- padding: 8px 10px;
191
- border-radius: 8px;
192
- border: 1px solid var(--epub-reader-border);
193
- background: transparent;
194
- color: inherit;
195
- cursor: pointer;
196
- }
197
-
198
- .epub-reader__toc-btn:hover {
199
- border-color: color-mix(in srgb, var(--epub-reader-accent) 60%, var(--epub-reader-border));
200
- }
201
-
202
- .epub-reader__toc-details {
203
- border-radius: 8px;
204
- }
205
-
206
- .epub-reader__toc-summary {
207
- padding: 8px 10px;
208
- border-radius: 8px;
209
- cursor: pointer;
210
- border: 1px solid var(--epub-reader-border);
211
- user-select: none;
212
- }
213
-
214
- .epub-reader__toc-details[open] > .epub-reader__toc-summary {
215
- border-color: color-mix(in srgb, var(--epub-reader-accent) 60%, var(--epub-reader-border));
216
- }
217
-
218
- .epub-reader__toc-details > .epub-reader__toc-list {
219
- padding-left: 12px;
220
- margin-top: 6px;
221
- }
222
-
223
- .epub-reader__field {
224
- display: flex;
225
- align-items: center;
226
- gap: 8px;
227
- }
228
-
229
- .epub-reader__input {
230
- width: 100%;
231
- padding: 8px 10px;
232
- border-radius: 8px;
233
- border: 1px solid var(--epub-reader-border);
234
- background: transparent;
235
- color: inherit;
236
- outline: none;
237
- }
238
-
239
- .epub-reader__checks {
240
- display: flex;
241
- flex-wrap: wrap;
242
- gap: 10px;
243
- margin: 10px 0;
244
- font-size: 12px;
245
- }
246
-
247
- .epub-reader__check {
248
- display: inline-flex;
249
- align-items: center;
250
- gap: 6px;
251
- user-select: none;
252
- }
253
-
254
- .epub-reader__meta {
255
- display: flex;
256
- align-items: center;
257
- justify-content: space-between;
258
- gap: 8px;
259
- font-size: 12px;
260
- opacity: 0.9;
261
- margin: 6px 0 10px;
262
- }
263
-
264
- .epub-reader__link {
265
- appearance: none;
266
- border: none;
267
- background: transparent;
268
- color: var(--epub-reader-accent);
269
- cursor: pointer;
270
- padding: 0;
271
- }
272
-
273
- .epub-reader__search-list {
274
- list-style: none;
275
- padding: 0;
276
- margin: 0;
277
- display: flex;
278
- flex-direction: column;
279
- gap: 6px;
280
- }
281
-
282
- .epub-reader__search-item {
283
- margin: 0;
284
- }
285
-
286
- .epub-reader__search-btn {
287
- width: 100%;
288
- text-align: left;
289
- padding: 10px;
290
- border-radius: 10px;
291
- border: 1px solid var(--epub-reader-border);
292
- background: transparent;
293
- color: inherit;
294
- cursor: pointer;
295
- display: flex;
296
- flex-direction: column;
297
- gap: 6px;
298
- }
299
-
300
- .epub-reader__search-btn:hover {
301
- border-color: color-mix(in srgb, var(--epub-reader-accent) 60%, var(--epub-reader-border));
302
- }
303
-
304
- .epub-reader__search-label {
305
- font-size: 12px;
306
- opacity: 0.7;
307
- }
308
-
309
- .epub-reader__search-excerpt {
310
- font-size: 12px;
311
- line-height: 1.4;
312
- word-break: break-word;
313
- }
314
-
315
- .epub-reader__bottom {
316
- position: absolute;
317
- left: 0;
318
- right: 0;
319
- bottom: 0;
320
- height: var(--epub-reader-bottom-bar-height);
321
- border-top: 1px solid var(--epub-reader-border);
322
- background: color-mix(in srgb, var(--epub-reader-panel-bg) 92%, transparent);
323
- display: flex;
324
- align-items: center;
325
- justify-content: space-between;
326
- gap: 10px;
327
- padding: 0 12px;
328
- z-index: 10;
329
- backdrop-filter: blur(10px);
330
- }
331
-
332
- .epub-reader__bottom-left {
333
- display: flex;
334
- align-items: center;
335
- gap: 8px;
336
- min-width: 0;
337
- }
338
-
339
- .epub-reader__status {
340
- font-size: 12px;
341
- opacity: 0.8;
342
- white-space: nowrap;
343
- }
344
-
345
- .epub-reader__section {
346
- font-size: 12px;
347
- font-weight: 600;
348
- white-space: nowrap;
349
- overflow: hidden;
350
- text-overflow: ellipsis;
351
- max-width: 280px;
352
- }
353
-
354
- .epub-reader__bottom-right {
355
- display: flex;
356
- align-items: center;
357
- gap: 10px;
358
- min-width: 260px;
359
- }
360
-
361
- .epub-reader__range {
362
- width: 260px;
363
- }
364
-
365
- .epub-reader__percent {
366
- font-size: 12px;
367
- font-variant-numeric: tabular-nums;
368
- min-width: 42px;
369
- text-align: right;
370
- }
371
-
372
- .epub-reader[data-layout='mobile'] .epub-reader__viewer {
373
- height: 100%;
374
- touch-action: pan-x;
375
- }
376
-
377
- .epub-reader[data-layout='mobile'] .epub-reader__toolbar,
378
- .epub-reader[data-layout='mobile'] .epub-reader__bottom,
379
- .epub-reader[data-layout='mobile'] .epub-reader__drawer,
380
- .epub-reader[data-layout='mobile'] .epub-reader__overlay {
381
- display: none;
382
- }
383
-
384
- .epub-reader__mbar {
385
- position: absolute;
386
- left: 10px;
387
- right: 10px;
388
- bottom: 0;
389
- z-index: 15;
390
- display: flex;
391
- align-items: center;
392
- gap: 8px;
393
- padding: 10px;
394
- border: 1px solid var(--epub-reader-border);
395
- border-radius: calc(var(--epub-reader-radius) + 6px);
396
- background: color-mix(in srgb, var(--epub-reader-panel-bg) 92%, transparent);
397
- color: var(--epub-reader-panel-fg);
398
- box-shadow: var(--epub-reader-shadow);
399
- backdrop-filter: blur(10px);
400
- transform: translateY(calc(100% + 10px));
401
- transition: transform 0.2s ease;
402
- }
403
-
404
- .epub-reader__mbar.is-visible {
405
- transform: translateY(-4px);
406
- }
407
-
408
- .epub-reader__mbar .epub-reader__btn {
409
- flex: 1 1 0;
410
- text-align: center;
411
- padding: 10px 8px;
412
- font-size: 13px;
413
- }
414
-
415
- .epub-reader__moverlay {
416
- position: absolute;
417
- inset: 0;
418
- background: rgba(0, 0, 0, 0.35);
419
- z-index: 20;
420
- }
421
-
422
- .epub-reader__msheet {
423
- position: absolute;
424
- left: 0;
425
- right: 0;
426
- bottom: 0;
427
- height: 60%;
428
- max-height: calc(100% - 70px);
429
- background: var(--epub-reader-panel-bg);
430
- color: var(--epub-reader-panel-fg);
431
- border-top: 1px solid var(--epub-reader-border);
432
- border-top-left-radius: calc(var(--epub-reader-radius) + 10px);
433
- border-top-right-radius: calc(var(--epub-reader-radius) + 10px);
434
- transform: translateY(100%);
435
- transition: transform 0.25s ease;
436
- z-index: 30;
437
- display: flex;
438
- flex-direction: column;
439
- pointer-events: none;
440
- }
441
-
442
- .epub-reader__msheet.is-open {
443
- transform: translateY(0);
444
- pointer-events: auto;
445
- }
446
-
447
- .epub-reader__msheet-header {
448
- display: flex;
449
- align-items: center;
450
- justify-content: space-between;
451
- gap: 8px;
452
- padding: 12px 12px 10px;
453
- border-bottom: 1px solid var(--epub-reader-border);
454
- }
455
-
456
- .epub-reader__msheet-title {
457
- font-size: 14px;
458
- font-weight: 800;
459
- }
460
-
461
- .epub-reader__msheet-body {
462
- padding: 12px;
463
- overflow: auto;
464
- flex: 1;
465
- }
466
-
467
- .epub-reader__mprogress {
468
- display: flex;
469
- flex-direction: column;
470
- gap: 10px;
471
- margin-top: 10px;
472
- }
473
-
474
- .epub-reader__mprogress .epub-reader__range {
475
- width: 100%;
476
- }
477
-
478
- .epub-reader__mprogress-percent {
479
- text-align: center;
480
- font-weight: 800;
481
- color: var(--epub-reader-accent);
482
- }
483
-
484
- .epub-reader__mfont {
485
- display: flex;
486
- align-items: center;
487
- justify-content: center;
488
- gap: 12px;
489
- }
490
-
491
- `;
492
-
493
- // src/styles/ensureStyle.ts
494
- var STYLE_ELEMENT_ID = "somecat-epub-reader-style";
495
- var ensureEpubReaderStyle = () => {
496
- if (typeof document === "undefined") return;
497
- if (document.getElementById(STYLE_ELEMENT_ID)) return;
498
- const style = document.createElement("style");
499
- style.id = STYLE_ELEMENT_ID;
500
- style.textContent = epubReaderCssText;
501
- (document.head ?? document.documentElement).append(style);
502
- };
6
+ // src/vue/EBookReaderVue.ts
503
7
  var getContentCSS = (fontSize, isDark, extraCSS) => `
504
8
  @namespace epub "http://www.idpf.org/2007/ops";
505
9
  html {
@@ -723,7 +227,8 @@ var icons = {
723
227
  minus: '<path d="M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
724
228
  x: '<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
725
229
  type: '<path d="M4 7V4h16v3M9 20h6M12 4v16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
726
- 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"/>'
230
+ 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"/>',
231
+ 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"/>'
727
232
  };
728
233
 
729
234
  // unplugin-vue:/plugin-vue/export-helper
@@ -964,7 +469,7 @@ function _sfc_render3(_ctx, _cache, $props, $setup, $data, $options) {
964
469
  return vue.openBlock(), vue.createElementBlock("div", _hoisted_13, [vue.createElementVNode("div", _hoisted_22, [vue.createElementVNode(
965
470
  "span",
966
471
  _hoisted_32,
967
- vue.toDisplayString($props.status === "error" ? $props.errorText || "\u9519\u8BEF" : $props.status === "opening" ? "\u6B63\u5728\u6253\u5F00\u2026" : "\u5C31\u7EEA"),
472
+ vue.toDisplayString($props.status === "error" ? $props.errorText || "\u9519\u8BEF" : $props.status === "opening" ? "\u6B63\u5728\u6253\u5F00\u2026" : ""),
968
473
  1
969
474
  /* TEXT */
970
475
  ), $props.sectionLabel ? (vue.openBlock(), vue.createElementBlock(
@@ -1338,6 +843,9 @@ function _sfc_render7(_ctx, _cache, $props, $setup, $data, $options) {
1338
843
  ])], 10, _hoisted_17);
1339
844
  }
1340
845
  var SearchDrawer_default = /* @__PURE__ */ export_helper_default(_sfc_main7, [["render", _sfc_render7], ["__file", "D:\\Project\\Demo\\EBook\\plugin\\ebook-reader\\src\\vue\\components\\SearchDrawer.vue"]]);
846
+ var fontThumbSize = 34;
847
+ var fontMin = 10;
848
+ var fontMax = 40;
1341
849
  var _sfc_main8 = /* @__PURE__ */ vue.defineComponent({
1342
850
  __name: "MobileUI",
1343
851
  props: {
@@ -1426,19 +934,144 @@ var _sfc_main8 = /* @__PURE__ */ vue.defineComponent({
1426
934
  return "\u641C\u7D22";
1427
935
  case "progress":
1428
936
  return "\u8FDB\u5EA6";
1429
- case "theme":
1430
- return "\u660E\u6697";
1431
- case "font":
1432
- return "\u5B57\u53F7";
937
+ case "settings":
938
+ return "\u8BBE\u7F6E";
1433
939
  default:
1434
940
  return "";
1435
941
  }
1436
942
  });
943
+ const clamp2 = (n, min, max) => Math.min(max, Math.max(min, n));
944
+ const displayedFontSize = vue.computed(() => clamp2(Math.round(props.fontSize / 5), 10, 40));
945
+ const fontSliderValue = vue.ref(displayedFontSize.value);
946
+ const fontSliderWrapRef = vue.ref(null);
947
+ const fontSliderWidth = vue.ref(0);
948
+ const isFontDragging = vue.ref(false);
949
+ let fontRo = null;
950
+ let fontDebounceTimer = null;
951
+ vue.watch(displayedFontSize, (v) => {
952
+ fontSliderValue.value = v;
953
+ });
954
+ const teardownFontResize = () => {
955
+ if (fontRo) {
956
+ fontRo.disconnect();
957
+ fontRo = null;
958
+ }
959
+ };
960
+ const setupFontResize = async () => {
961
+ await vue.nextTick();
962
+ const el = fontSliderWrapRef.value;
963
+ if (!el) return;
964
+ const update = () => {
965
+ fontSliderWidth.value = el.getBoundingClientRect().width;
966
+ };
967
+ update();
968
+ teardownFontResize();
969
+ fontRo = new ResizeObserver(() => update());
970
+ fontRo.observe(el);
971
+ };
972
+ vue.watch(() => props.activePanel, (p) => {
973
+ if (p === "settings") void setupFontResize();
974
+ else teardownFontResize();
975
+ }, { immediate: true });
976
+ vue.onBeforeUnmount(() => teardownFontResize());
977
+ const fontProgressPercent = vue.computed(() => (fontSliderValue.value - fontMin) / (fontMax - fontMin) * 100);
978
+ const fontThumbLeft = vue.computed(() => {
979
+ if (!fontSliderWidth.value) return 0;
980
+ const percent = (fontSliderValue.value - fontMin) / (fontMax - fontMin);
981
+ const half = fontThumbSize / 2;
982
+ return Math.min(fontSliderWidth.value - half, Math.max(half, half + percent * (fontSliderWidth.value - fontThumbSize)));
983
+ });
984
+ const flushFontSize = () => {
985
+ if (fontDebounceTimer) {
986
+ clearTimeout(fontDebounceTimer);
987
+ fontDebounceTimer = null;
988
+ }
989
+ emit("changeFontSize", fontSliderValue.value * 5);
990
+ };
991
+ const scheduleFontSize = (next) => {
992
+ if (fontDebounceTimer) clearTimeout(fontDebounceTimer);
993
+ fontDebounceTimer = window.setTimeout(() => {
994
+ fontDebounceTimer = null;
995
+ emit("changeFontSize", next * 5);
996
+ }, 80);
997
+ };
998
+ const handleFontSliderInput = (e) => {
999
+ const next = Number(e.target.value);
1000
+ fontSliderValue.value = next;
1001
+ scheduleFontSize(next);
1002
+ };
1003
+ vue.onBeforeUnmount(() => {
1004
+ if (fontDebounceTimer) clearTimeout(fontDebounceTimer);
1005
+ });
1437
1006
  const updateSearchOption = (key, value) => {
1438
1007
  emit("update:searchOptions", { [key]: value });
1439
1008
  };
1440
1009
  const tooltip = vue.ref(null);
1441
1010
  let timer = null;
1011
+ const ignoreToggle = vue.ref(false);
1012
+ const markIgnoreToggle = () => {
1013
+ ignoreToggle.value = true;
1014
+ window.setTimeout(() => {
1015
+ ignoreToggle.value = false;
1016
+ }, 350);
1017
+ };
1018
+ const clearTooltip = () => {
1019
+ if (timer) {
1020
+ clearTimeout(timer);
1021
+ timer = null;
1022
+ }
1023
+ tooltip.value = null;
1024
+ };
1025
+ const closePanelSafe = () => {
1026
+ emit("closePanel");
1027
+ clearTooltip();
1028
+ const el = document.activeElement;
1029
+ if (el && el instanceof HTMLElement) el.blur();
1030
+ };
1031
+ const togglePanelSafe = (panel) => {
1032
+ if (ignoreToggle.value) return;
1033
+ emit("togglePanel", panel);
1034
+ };
1035
+ const sheetRef = vue.ref(null);
1036
+ const dragRef = {
1037
+ startY: 0,
1038
+ currentY: 0,
1039
+ isDragging: false
1040
+ };
1041
+ const handleHeaderTouchStart = (e) => {
1042
+ clearTooltip();
1043
+ dragRef.startY = e.touches[0].clientY;
1044
+ dragRef.isDragging = true;
1045
+ if (sheetRef.value) {
1046
+ sheetRef.value.style.transition = "none";
1047
+ }
1048
+ };
1049
+ const handleHeaderTouchMove = (e) => {
1050
+ if (!dragRef.isDragging) return;
1051
+ e.preventDefault();
1052
+ const deltaY = e.touches[0].clientY - dragRef.startY;
1053
+ if (deltaY > 0 && sheetRef.value) {
1054
+ sheetRef.value.style.transform = `translateY(${deltaY}px)`;
1055
+ dragRef.currentY = deltaY;
1056
+ }
1057
+ };
1058
+ const handleHeaderTouchEnd = () => {
1059
+ if (!dragRef.isDragging) return;
1060
+ dragRef.isDragging = false;
1061
+ if (sheetRef.value) {
1062
+ sheetRef.value.style.transition = "";
1063
+ if (dragRef.currentY > 80) {
1064
+ markIgnoreToggle();
1065
+ closePanelSafe();
1066
+ setTimeout(() => {
1067
+ if (sheetRef.value) sheetRef.value.style.transform = "";
1068
+ }, 300);
1069
+ } else {
1070
+ sheetRef.value.style.transform = "";
1071
+ }
1072
+ }
1073
+ dragRef.currentY = 0;
1074
+ };
1442
1075
  const handleTouchStart = (e, text) => {
1443
1076
  const target = e.currentTarget;
1444
1077
  const rect = target.getBoundingClientRect();
@@ -1450,16 +1083,40 @@ var _sfc_main8 = /* @__PURE__ */ vue.defineComponent({
1450
1083
  }, 500);
1451
1084
  };
1452
1085
  const handleTouchEnd = () => {
1453
- if (timer) {
1454
- clearTimeout(timer);
1455
- timer = null;
1456
- }
1457
- tooltip.value = null;
1086
+ clearTooltip();
1458
1087
  };
1459
1088
  const __returned__ = {
1460
1089
  props,
1461
1090
  emit,
1462
1091
  mobileTitle,
1092
+ clamp: clamp2,
1093
+ displayedFontSize,
1094
+ fontSliderValue,
1095
+ fontSliderWrapRef,
1096
+ fontSliderWidth,
1097
+ isFontDragging,
1098
+ fontThumbSize,
1099
+ fontMin,
1100
+ fontMax,
1101
+ get fontRo() {
1102
+ return fontRo;
1103
+ },
1104
+ set fontRo(v) {
1105
+ fontRo = v;
1106
+ },
1107
+ get fontDebounceTimer() {
1108
+ return fontDebounceTimer;
1109
+ },
1110
+ set fontDebounceTimer(v) {
1111
+ fontDebounceTimer = v;
1112
+ },
1113
+ teardownFontResize,
1114
+ setupFontResize,
1115
+ fontProgressPercent,
1116
+ fontThumbLeft,
1117
+ flushFontSize,
1118
+ scheduleFontSize,
1119
+ handleFontSliderInput,
1463
1120
  updateSearchOption,
1464
1121
  tooltip,
1465
1122
  get timer() {
@@ -1468,6 +1125,16 @@ var _sfc_main8 = /* @__PURE__ */ vue.defineComponent({
1468
1125
  set timer(v) {
1469
1126
  timer = v;
1470
1127
  },
1128
+ ignoreToggle,
1129
+ markIgnoreToggle,
1130
+ clearTooltip,
1131
+ closePanelSafe,
1132
+ togglePanelSafe,
1133
+ sheetRef,
1134
+ dragRef,
1135
+ handleHeaderTouchStart,
1136
+ handleHeaderTouchMove,
1137
+ handleHeaderTouchEnd,
1471
1138
  handleTouchStart,
1472
1139
  handleTouchEnd,
1473
1140
  TocTree: TocTree_default,
@@ -1481,13 +1148,16 @@ var _sfc_main8 = /* @__PURE__ */ vue.defineComponent({
1481
1148
  return __returned__;
1482
1149
  }
1483
1150
  });
1484
- var _hoisted_18 = ["aria-pressed"];
1151
+ var _hoisted_18 = { class: "epub-reader__mbar-left" };
1485
1152
  var _hoisted_27 = ["aria-pressed"];
1486
1153
  var _hoisted_37 = ["aria-pressed"];
1487
1154
  var _hoisted_47 = ["aria-pressed"];
1488
1155
  var _hoisted_53 = ["aria-pressed"];
1489
- var _hoisted_63 = ["aria-hidden"];
1490
- var _hoisted_73 = { class: "epub-reader__msheet-header" };
1156
+ var _hoisted_63 = {
1157
+ key: 1,
1158
+ class: "epub-reader__mbar-right"
1159
+ };
1160
+ var _hoisted_73 = ["aria-hidden"];
1491
1161
  var _hoisted_82 = { class: "epub-reader__msheet-title" };
1492
1162
  var _hoisted_92 = { class: "epub-reader__msheet-body" };
1493
1163
  var _hoisted_102 = {
@@ -1517,10 +1187,16 @@ var _hoisted_272 = { class: "epub-reader__mprogress" };
1517
1187
  var _hoisted_28 = ["value"];
1518
1188
  var _hoisted_29 = { class: "epub-reader__mprogress-percent" };
1519
1189
  var _hoisted_30 = {
1520
- key: 4,
1521
- class: "epub-reader__mfont"
1190
+ key: 3,
1191
+ class: "epub-reader__msettings"
1522
1192
  };
1523
- var _hoisted_31 = { class: "epub-reader__font" };
1193
+ var _hoisted_31 = { class: "epub-reader__mfont-range" };
1194
+ var _hoisted_322 = ["value"];
1195
+ var _hoisted_332 = [
1196
+ "aria-pressed",
1197
+ "aria-label",
1198
+ "title"
1199
+ ];
1524
1200
  function _sfc_render8(_ctx, _cache, $props, $setup, $data, $options) {
1525
1201
  return vue.openBlock(), vue.createElementBlock("div", null, [
1526
1202
  vue.createElementVNode(
@@ -1551,56 +1227,49 @@ function _sfc_render8(_ctx, _cache, $props, $setup, $data, $options) {
1551
1227
  5
1552
1228
  /* TEXT, STYLE */
1553
1229
  )) : vue.createCommentVNode("v-if", true),
1554
- vue.createElementVNode("button", {
1555
- type: "button",
1556
- class: "epub-reader__btn",
1557
- "aria-pressed": $props.activePanel === "menu",
1558
- onClick: _cache[0] || (_cache[0] = ($event) => $setup.emit("togglePanel", "menu")),
1559
- onTouchstart: _cache[1] || (_cache[1] = (e) => $setup.handleTouchStart(e, "\u76EE\u5F55")),
1560
- onTouchend: $setup.handleTouchEnd,
1561
- onTouchcancel: $setup.handleTouchEnd,
1562
- title: "\u76EE\u5F55"
1563
- }, [vue.createVNode($setup["SvgIcon"], { name: "list" })], 40, _hoisted_18),
1564
- vue.createElementVNode("button", {
1565
- type: "button",
1566
- class: "epub-reader__btn",
1567
- "aria-pressed": $props.activePanel === "search",
1568
- onClick: _cache[2] || (_cache[2] = ($event) => $setup.emit("togglePanel", "search")),
1569
- onTouchstart: _cache[3] || (_cache[3] = (e) => $setup.handleTouchStart(e, "\u641C\u7D22")),
1570
- onTouchend: $setup.handleTouchEnd,
1571
- onTouchcancel: $setup.handleTouchEnd,
1572
- title: "\u641C\u7D22"
1573
- }, [vue.createVNode($setup["SvgIcon"], { name: "search" })], 40, _hoisted_27),
1574
- vue.createElementVNode("button", {
1575
- type: "button",
1576
- class: "epub-reader__btn",
1577
- "aria-pressed": $props.activePanel === "progress",
1578
- onClick: _cache[4] || (_cache[4] = ($event) => $setup.emit("togglePanel", "progress")),
1579
- onTouchstart: _cache[5] || (_cache[5] = (e) => $setup.handleTouchStart(e, "\u8FDB\u5EA6")),
1580
- onTouchend: $setup.handleTouchEnd,
1581
- onTouchcancel: $setup.handleTouchEnd,
1582
- title: "\u8FDB\u5EA6"
1583
- }, [vue.createVNode($setup["SvgIcon"], { name: "sliders" })], 40, _hoisted_37),
1584
- vue.createElementVNode("button", {
1585
- type: "button",
1586
- class: "epub-reader__btn",
1587
- "aria-pressed": $props.activePanel === "theme",
1588
- onClick: _cache[6] || (_cache[6] = ($event) => $setup.emit("togglePanel", "theme")),
1589
- onTouchstart: _cache[7] || (_cache[7] = (e) => $setup.handleTouchStart(e, "\u660E\u6697")),
1590
- onTouchend: $setup.handleTouchEnd,
1591
- onTouchcancel: $setup.handleTouchEnd,
1592
- title: "\u660E\u6697"
1593
- }, [vue.createVNode($setup["SvgIcon"], { name: "sun" })], 40, _hoisted_47),
1594
- vue.createElementVNode("button", {
1595
- type: "button",
1596
- class: "epub-reader__btn",
1597
- "aria-pressed": $props.activePanel === "font",
1598
- onClick: _cache[8] || (_cache[8] = ($event) => $setup.emit("togglePanel", "font")),
1599
- onTouchstart: _cache[9] || (_cache[9] = (e) => $setup.handleTouchStart(e, "\u5B57\u53F7")),
1600
- onTouchend: $setup.handleTouchEnd,
1601
- onTouchcancel: $setup.handleTouchEnd,
1602
- title: "\u5B57\u53F7"
1603
- }, [vue.createVNode($setup["SvgIcon"], { name: "type" })], 40, _hoisted_53)
1230
+ vue.createElementVNode("div", _hoisted_18, [
1231
+ vue.createElementVNode("button", {
1232
+ type: "button",
1233
+ class: "epub-reader__btn",
1234
+ "aria-pressed": $props.activePanel === "menu",
1235
+ onClick: _cache[0] || (_cache[0] = ($event) => $setup.togglePanelSafe("menu")),
1236
+ onTouchstart: _cache[1] || (_cache[1] = (e) => $setup.handleTouchStart(e, "\u76EE\u5F55")),
1237
+ onTouchend: $setup.handleTouchEnd,
1238
+ onTouchcancel: $setup.handleTouchEnd,
1239
+ title: "\u76EE\u5F55"
1240
+ }, [vue.createVNode($setup["SvgIcon"], { name: "list" })], 40, _hoisted_27),
1241
+ vue.createElementVNode("button", {
1242
+ type: "button",
1243
+ class: "epub-reader__btn",
1244
+ "aria-pressed": $props.activePanel === "search",
1245
+ onClick: _cache[2] || (_cache[2] = ($event) => $setup.togglePanelSafe("search")),
1246
+ onTouchstart: _cache[3] || (_cache[3] = (e) => $setup.handleTouchStart(e, "\u641C\u7D22")),
1247
+ onTouchend: $setup.handleTouchEnd,
1248
+ onTouchcancel: $setup.handleTouchEnd,
1249
+ title: "\u641C\u7D22"
1250
+ }, [vue.createVNode($setup["SvgIcon"], { name: "search" })], 40, _hoisted_37),
1251
+ vue.createElementVNode("button", {
1252
+ type: "button",
1253
+ class: "epub-reader__btn",
1254
+ "aria-pressed": $props.activePanel === "progress",
1255
+ onClick: _cache[4] || (_cache[4] = ($event) => $setup.togglePanelSafe("progress")),
1256
+ onTouchstart: _cache[5] || (_cache[5] = (e) => $setup.handleTouchStart(e, "\u8FDB\u5EA6")),
1257
+ onTouchend: $setup.handleTouchEnd,
1258
+ onTouchcancel: $setup.handleTouchEnd,
1259
+ title: "\u8FDB\u5EA6"
1260
+ }, [vue.createVNode($setup["SvgIcon"], { name: "sliders" })], 40, _hoisted_47),
1261
+ vue.createElementVNode("button", {
1262
+ type: "button",
1263
+ class: "epub-reader__btn",
1264
+ "aria-pressed": $props.activePanel === "settings",
1265
+ onClick: _cache[6] || (_cache[6] = ($event) => $setup.togglePanelSafe("settings")),
1266
+ onTouchstart: _cache[7] || (_cache[7] = (e) => $setup.handleTouchStart(e, "\u8BBE\u7F6E")),
1267
+ onTouchend: $setup.handleTouchEnd,
1268
+ onTouchcancel: $setup.handleTouchEnd,
1269
+ title: "\u8BBE\u7F6E"
1270
+ }, [vue.createVNode($setup["SvgIcon"], { name: "settings" })], 40, _hoisted_53)
1271
+ ]),
1272
+ _ctx.$slots.toolbarRight ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_63, [vue.renderSlot(_ctx.$slots, "toolbarRight")])) : vue.createCommentVNode("v-if", true)
1604
1273
  ],
1605
1274
  2
1606
1275
  /* CLASS */
@@ -1608,31 +1277,43 @@ function _sfc_render8(_ctx, _cache, $props, $setup, $data, $options) {
1608
1277
  $props.activePanel ? (vue.openBlock(), vue.createElementBlock("div", {
1609
1278
  key: 0,
1610
1279
  class: "epub-reader__moverlay",
1611
- onClick: _cache[10] || (_cache[10] = ($event) => $setup.emit("closePanel"))
1280
+ onClick: $setup.closePanelSafe
1612
1281
  })) : vue.createCommentVNode("v-if", true),
1613
1282
  vue.createElementVNode("div", {
1283
+ ref: "sheetRef",
1614
1284
  class: vue.normalizeClass(["epub-reader__msheet", { "is-open": $props.activePanel }]),
1615
1285
  "aria-hidden": !$props.activePanel
1616
- }, [vue.createElementVNode("div", _hoisted_73, [vue.createElementVNode(
1286
+ }, [vue.createElementVNode(
1617
1287
  "div",
1618
- _hoisted_82,
1619
- vue.toDisplayString($setup.mobileTitle),
1620
- 1
1621
- /* TEXT */
1622
- ), vue.createElementVNode("button", {
1623
- type: "button",
1624
- class: "epub-reader__btn",
1625
- onClick: _cache[11] || (_cache[11] = ($event) => $setup.emit("closePanel"))
1626
- }, [vue.createVNode($setup["SvgIcon"], { name: "x" })])]), vue.createElementVNode("div", _hoisted_92, [
1288
+ {
1289
+ class: "epub-reader__msheet-header",
1290
+ onTouchstart: $setup.handleHeaderTouchStart,
1291
+ onTouchmove: $setup.handleHeaderTouchMove,
1292
+ onTouchend: $setup.handleHeaderTouchEnd
1293
+ },
1294
+ [vue.createElementVNode(
1295
+ "div",
1296
+ _hoisted_82,
1297
+ vue.toDisplayString($setup.mobileTitle),
1298
+ 1
1299
+ /* TEXT */
1300
+ ), vue.createElementVNode("button", {
1301
+ type: "button",
1302
+ class: "epub-reader__btn",
1303
+ onClick: $setup.closePanelSafe
1304
+ }, [vue.createVNode($setup["SvgIcon"], { name: "x" })])],
1305
+ 32
1306
+ /* NEED_HYDRATION */
1307
+ ), vue.createElementVNode("div", _hoisted_92, [
1627
1308
  $props.activePanel === "menu" ? (vue.openBlock(), vue.createElementBlock(
1628
1309
  vue.Fragment,
1629
1310
  { key: 0 },
1630
1311
  [$props.toc.length ? (vue.openBlock(), vue.createBlock($setup["TocTree"], {
1631
1312
  key: 0,
1632
1313
  items: $props.toc,
1633
- onSelect: _cache[12] || (_cache[12] = (href) => {
1314
+ onSelect: _cache[8] || (_cache[8] = (href) => {
1634
1315
  $setup.emit("tocSelect", href);
1635
- $setup.emit("closePanel");
1316
+ $setup.closePanelSafe();
1636
1317
  })
1637
1318
  }, null, 8, ["items"])) : (vue.openBlock(), vue.createElementBlock("div", _hoisted_102, "\u672A\u627E\u5230\u76EE\u5F55"))],
1638
1319
  64
@@ -1647,24 +1328,24 @@ function _sfc_render8(_ctx, _cache, $props, $setup, $data, $options) {
1647
1328
  placeholder: "\u8F93\u5165\u5173\u952E\u8BCD",
1648
1329
  value: $props.searchQuery,
1649
1330
  disabled: $props.status !== "ready",
1650
- onInput: _cache[13] || (_cache[13] = (e) => {
1331
+ onInput: _cache[9] || (_cache[9] = (e) => {
1651
1332
  const v = e.target.value;
1652
1333
  $setup.emit("update:searchQuery", v);
1653
1334
  if (!v.trim()) $setup.emit("search", "");
1654
1335
  }),
1655
- onKeydown: _cache[14] || (_cache[14] = vue.withKeys(($event) => $setup.emit("search", $props.searchQuery), ["enter"]))
1336
+ onKeydown: _cache[10] || (_cache[10] = vue.withKeys(($event) => $setup.emit("search", $props.searchQuery), ["enter"]))
1656
1337
  }, null, 40, _hoisted_123), vue.createElementVNode("button", {
1657
1338
  type: "button",
1658
1339
  class: "epub-reader__btn",
1659
1340
  disabled: $props.status !== "ready",
1660
- onClick: _cache[15] || (_cache[15] = ($event) => $setup.emit("search", $props.searchQuery))
1341
+ onClick: _cache[11] || (_cache[11] = ($event) => $setup.emit("search", $props.searchQuery))
1661
1342
  }, " \u641C\u7D22 ", 8, _hoisted_133)]),
1662
1343
  vue.createElementVNode("div", _hoisted_143, [
1663
1344
  vue.createElementVNode("label", _hoisted_153, [vue.createElementVNode("input", {
1664
1345
  type: "checkbox",
1665
1346
  checked: Boolean($props.searchOptions.matchCase),
1666
- onChange: _cache[16] || (_cache[16] = (e) => $setup.updateSearchOption("matchCase", e.target.checked))
1667
- }, null, 40, _hoisted_163), _cache[27] || (_cache[27] = vue.createTextVNode(
1347
+ onChange: _cache[12] || (_cache[12] = (e) => $setup.updateSearchOption("matchCase", e.target.checked))
1348
+ }, null, 40, _hoisted_163), _cache[26] || (_cache[26] = vue.createTextVNode(
1668
1349
  " \u533A\u5206\u5927\u5C0F\u5199 ",
1669
1350
  -1
1670
1351
  /* CACHED */
@@ -1672,8 +1353,8 @@ function _sfc_render8(_ctx, _cache, $props, $setup, $data, $options) {
1672
1353
  vue.createElementVNode("label", _hoisted_172, [vue.createElementVNode("input", {
1673
1354
  type: "checkbox",
1674
1355
  checked: Boolean($props.searchOptions.wholeWords),
1675
- onChange: _cache[17] || (_cache[17] = (e) => $setup.updateSearchOption("wholeWords", e.target.checked))
1676
- }, null, 40, _hoisted_182), _cache[28] || (_cache[28] = vue.createTextVNode(
1356
+ onChange: _cache[13] || (_cache[13] = (e) => $setup.updateSearchOption("wholeWords", e.target.checked))
1357
+ }, null, 40, _hoisted_182), _cache[27] || (_cache[27] = vue.createTextVNode(
1677
1358
  " \u5168\u8BCD\u5339\u914D ",
1678
1359
  -1
1679
1360
  /* CACHED */
@@ -1681,8 +1362,8 @@ function _sfc_render8(_ctx, _cache, $props, $setup, $data, $options) {
1681
1362
  vue.createElementVNode("label", _hoisted_19, [vue.createElementVNode("input", {
1682
1363
  type: "checkbox",
1683
1364
  checked: Boolean($props.searchOptions.matchDiacritics),
1684
- onChange: _cache[18] || (_cache[18] = (e) => $setup.updateSearchOption("matchDiacritics", e.target.checked))
1685
- }, null, 40, _hoisted_20), _cache[29] || (_cache[29] = vue.createTextVNode(
1365
+ onChange: _cache[14] || (_cache[14] = (e) => $setup.updateSearchOption("matchDiacritics", e.target.checked))
1366
+ }, null, 40, _hoisted_20), _cache[28] || (_cache[28] = vue.createTextVNode(
1686
1367
  " \u533A\u5206\u53D8\u97F3 ",
1687
1368
  -1
1688
1369
  /* CACHED */
@@ -1701,13 +1382,13 @@ function _sfc_render8(_ctx, _cache, $props, $setup, $data, $options) {
1701
1382
  key: 1,
1702
1383
  type: "button",
1703
1384
  class: "epub-reader__link",
1704
- onClick: _cache[19] || (_cache[19] = ($event) => $setup.emit("cancelSearch"))
1385
+ onClick: _cache[15] || (_cache[15] = ($event) => $setup.emit("cancelSearch"))
1705
1386
  }, " \u53D6\u6D88 ")) : vue.createCommentVNode("v-if", true)
1706
1387
  ]),
1707
1388
  $props.searchResults.length ? (vue.openBlock(), vue.createBlock($setup["SearchResultList"], {
1708
1389
  key: 0,
1709
1390
  results: $props.searchResults,
1710
- onSelect: _cache[20] || (_cache[20] = (cfi) => $setup.emit("searchResultSelect", cfi))
1391
+ onSelect: _cache[16] || (_cache[16] = (cfi) => $setup.emit("searchResultSelect", cfi))
1711
1392
  }, null, 8, ["results"])) : (vue.openBlock(), vue.createElementBlock(
1712
1393
  "div",
1713
1394
  _hoisted_232,
@@ -1725,7 +1406,7 @@ function _sfc_render8(_ctx, _cache, $props, $setup, $data, $options) {
1725
1406
  [vue.createElementVNode("div", _hoisted_242, [vue.createElementVNode(
1726
1407
  "span",
1727
1408
  _hoisted_252,
1728
- vue.toDisplayString($props.status === "error" ? $props.errorText || "\u9519\u8BEF" : $props.status === "opening" ? "\u6B63\u5728\u6253\u5F00\u2026" : "\u5C31\u7EEA"),
1409
+ vue.toDisplayString($props.status === "error" ? $props.errorText || "\u9519\u8BEF" : $props.status === "opening" ? "\u6B63\u5728\u6253\u5F00\u2026" : ""),
1729
1410
  1
1730
1411
  /* TEXT */
1731
1412
  ), $props.sectionLabel ? (vue.openBlock(), vue.createElementBlock(
@@ -1741,12 +1422,12 @@ function _sfc_render8(_ctx, _cache, $props, $setup, $data, $options) {
1741
1422
  max: 100,
1742
1423
  step: 1,
1743
1424
  value: $props.displayedPercent,
1744
- onInput: _cache[21] || (_cache[21] = (e) => {
1425
+ onInput: _cache[17] || (_cache[17] = (e) => {
1745
1426
  $setup.emit("seekStart");
1746
1427
  $setup.emit("seekChange", Number(e.target.value));
1747
1428
  }),
1748
- onPointerup: _cache[22] || (_cache[22] = (e) => $setup.emit("seekEnd", Number(e.target.value))),
1749
- onKeyup: _cache[23] || (_cache[23] = vue.withKeys((e) => $setup.emit("seekCommit", Number(e.target.value)), ["enter"]))
1429
+ onPointerup: _cache[18] || (_cache[18] = (e) => $setup.emit("seekEnd", Number(e.target.value))),
1430
+ onKeyup: _cache[19] || (_cache[19] = vue.withKeys((e) => $setup.emit("seekCommit", Number(e.target.value)), ["enter"]))
1750
1431
  }, null, 40, _hoisted_28), vue.createElementVNode(
1751
1432
  "div",
1752
1433
  _hoisted_29,
@@ -1757,38 +1438,77 @@ function _sfc_render8(_ctx, _cache, $props, $setup, $data, $options) {
1757
1438
  64
1758
1439
  /* STABLE_FRAGMENT */
1759
1440
  )) : vue.createCommentVNode("v-if", true),
1760
- $props.activePanel === "theme" ? (vue.openBlock(), vue.createElementBlock(
1761
- "button",
1762
- {
1763
- key: 3,
1764
- type: "button",
1765
- class: "epub-reader__btn",
1766
- onClick: _cache[24] || (_cache[24] = ($event) => $setup.emit("toggleDarkMode", !$props.darkMode))
1767
- },
1768
- vue.toDisplayString($props.darkMode ? "\u5207\u6362\u5230\u4EAE\u8272" : "\u5207\u6362\u5230\u6697\u9ED1"),
1769
- 1
1770
- /* TEXT */
1771
- )) : vue.createCommentVNode("v-if", true),
1772
- $props.activePanel === "font" ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_30, [
1773
- vue.createElementVNode("button", {
1774
- type: "button",
1775
- class: "epub-reader__btn",
1776
- onClick: _cache[25] || (_cache[25] = ($event) => $setup.emit("changeFontSize", $props.fontSize - 10))
1777
- }, " A- "),
1441
+ $props.activePanel === "settings" ? (vue.openBlock(), vue.createElementBlock("div", _hoisted_30, [vue.createElementVNode("div", _hoisted_31, [
1442
+ _cache[29] || (_cache[29] = vue.createElementVNode(
1443
+ "div",
1444
+ { class: "epub-reader__mfont-a is-small" },
1445
+ "A",
1446
+ -1
1447
+ /* CACHED */
1448
+ )),
1778
1449
  vue.createElementVNode(
1779
1450
  "div",
1780
- _hoisted_31,
1781
- vue.toDisplayString($props.fontSize) + "%",
1782
- 1
1783
- /* TEXT */
1451
+ {
1452
+ ref: "fontSliderWrapRef",
1453
+ class: vue.normalizeClass(["epub-reader__mfont-slider", { "is-dragging": $setup.isFontDragging }])
1454
+ },
1455
+ [vue.createElementVNode("input", {
1456
+ class: "epub-reader__range",
1457
+ type: "range",
1458
+ min: $setup.fontMin,
1459
+ max: $setup.fontMax,
1460
+ step: 1,
1461
+ value: $setup.fontSliderValue,
1462
+ style: vue.normalizeStyle({ background: `linear-gradient(to right, var(--epub-reader-range-fill) 0%, var(--epub-reader-range-fill) ${$setup.fontProgressPercent}%, var(--epub-reader-range-track) ${$setup.fontProgressPercent}%, var(--epub-reader-range-track) 100%)` }),
1463
+ "aria-label": "\u5B57\u53F7",
1464
+ onInput: $setup.handleFontSliderInput,
1465
+ onPointerdown: _cache[20] || (_cache[20] = ($event) => $setup.isFontDragging = true),
1466
+ onPointerup: _cache[21] || (_cache[21] = () => {
1467
+ $setup.isFontDragging = false;
1468
+ $setup.flushFontSize();
1469
+ }),
1470
+ onPointercancel: _cache[22] || (_cache[22] = () => {
1471
+ $setup.isFontDragging = false;
1472
+ $setup.flushFontSize();
1473
+ }),
1474
+ onTouchstart: _cache[23] || (_cache[23] = ($event) => $setup.isFontDragging = true),
1475
+ onTouchend: _cache[24] || (_cache[24] = () => {
1476
+ $setup.isFontDragging = false;
1477
+ $setup.flushFontSize();
1478
+ })
1479
+ }, null, 44, _hoisted_322), vue.createElementVNode(
1480
+ "div",
1481
+ {
1482
+ class: "epub-reader__mfont-thumb",
1483
+ style: vue.normalizeStyle({
1484
+ left: `${$setup.fontThumbLeft}px`,
1485
+ width: `${$setup.fontThumbSize}px`,
1486
+ height: `${$setup.fontThumbSize}px`
1487
+ })
1488
+ },
1489
+ vue.toDisplayString($setup.fontSliderValue),
1490
+ 5
1491
+ /* TEXT, STYLE */
1492
+ )],
1493
+ 2
1494
+ /* CLASS */
1784
1495
  ),
1785
- vue.createElementVNode("button", {
1786
- type: "button",
1787
- class: "epub-reader__btn",
1788
- onClick: _cache[26] || (_cache[26] = ($event) => $setup.emit("changeFontSize", $props.fontSize + 10))
1789
- }, " A+ ")
1790
- ])) : vue.createCommentVNode("v-if", true)
1791
- ])], 10, _hoisted_63)
1496
+ _cache[30] || (_cache[30] = vue.createElementVNode(
1497
+ "div",
1498
+ { class: "epub-reader__mfont-a is-big" },
1499
+ "A",
1500
+ -1
1501
+ /* CACHED */
1502
+ ))
1503
+ ]), vue.createElementVNode("button", {
1504
+ type: "button",
1505
+ class: "epub-reader__btn",
1506
+ "aria-pressed": $props.darkMode,
1507
+ "aria-label": $props.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",
1508
+ title: $props.darkMode ? "\u5207\u6362\u5230\u4EAE\u8272" : "\u5207\u6362\u5230\u6697\u9ED1",
1509
+ onClick: _cache[25] || (_cache[25] = ($event) => $setup.emit("toggleDarkMode", !$props.darkMode))
1510
+ }, [vue.createVNode($setup["SvgIcon"], { name: $props.darkMode ? "sun" : "moon" }, null, 8, ["name"])], 8, _hoisted_332)])) : vue.createCommentVNode("v-if", true)
1511
+ ])], 10, _hoisted_73)
1792
1512
  ]);
1793
1513
  }
1794
1514
  var MobileUI_default = /* @__PURE__ */ export_helper_default(_sfc_main8, [["render", _sfc_render8], ["__file", "D:\\Project\\Demo\\EBook\\plugin\\ebook-reader\\src\\vue\\components\\MobileUI.vue"]]);
@@ -1840,7 +1560,7 @@ var EBookReaderVue = vue.defineComponent({
1840
1560
  name: "EBookReaderVue",
1841
1561
  props: EBookReaderVuePropsDef,
1842
1562
  emits: ["ready", "error", "progress", "fontSizeChange", "darkModeChange", "update:fontSize", "update:darkMode"],
1843
- setup(props, { emit, expose }) {
1563
+ setup(props, { emit, expose, slots }) {
1844
1564
  const instance = vue.getCurrentInstance();
1845
1565
  const isPropProvided = (key) => {
1846
1566
  const vnodeProps = instance?.vnode.props;
@@ -1852,11 +1572,13 @@ var EBookReaderVue = vue.defineComponent({
1852
1572
  const reader = vue.ref(null);
1853
1573
  const status = vue.ref("idle");
1854
1574
  const errorText = vue.ref("");
1575
+ const downloadLoading = vue.ref(null);
1855
1576
  const toc = vue.ref([]);
1856
1577
  const tocOpen = vue.ref(false);
1857
1578
  const searchOpen = vue.ref(false);
1858
1579
  const progressInfo = vue.ref(null);
1859
1580
  const isSeeking = vue.ref(false);
1581
+ const isDragging = vue.ref(false);
1860
1582
  const seekPercent = vue.ref(0);
1861
1583
  const layout = vue.ref("default");
1862
1584
  const mobileBarVisible = vue.ref(false);
@@ -1919,6 +1641,7 @@ var EBookReaderVue = vue.defineComponent({
1919
1641
  if (!reader.value) return;
1920
1642
  openController?.abort();
1921
1643
  openController = null;
1644
+ downloadLoading.value = null;
1922
1645
  if (props.file) {
1923
1646
  await openFile(props.file);
1924
1647
  return;
@@ -1927,6 +1650,7 @@ var EBookReaderVue = vue.defineComponent({
1927
1650
  if (!nextUrl) return;
1928
1651
  const controller = new AbortController();
1929
1652
  openController = controller;
1653
+ downloadLoading.value = "download";
1930
1654
  status.value = "opening";
1931
1655
  errorText.value = "";
1932
1656
  toc.value = [];
@@ -1939,12 +1663,16 @@ var EBookReaderVue = vue.defineComponent({
1939
1663
  try {
1940
1664
  const downloaded = await downloadEpubAsFile(nextUrl, controller.signal);
1941
1665
  if (controller.signal.aborted) return;
1666
+ if (openController !== controller) return;
1667
+ downloadLoading.value = "open";
1942
1668
  await openFile(downloaded);
1943
1669
  } catch (e) {
1944
1670
  if (e?.name === "AbortError") return;
1945
1671
  status.value = "error";
1946
1672
  errorText.value = e?.message ? String(e.message) : "\u4E0B\u8F7D\u5931\u8D25";
1947
1673
  emit("error", e);
1674
+ } finally {
1675
+ if (openController === controller) downloadLoading.value = null;
1948
1676
  }
1949
1677
  };
1950
1678
  const runSearch = async (query) => {
@@ -2067,6 +1795,9 @@ var EBookReaderVue = vue.defineComponent({
2067
1795
  onError: (e) => emit("error", e),
2068
1796
  onProgress: (info) => {
2069
1797
  progressInfo.value = info;
1798
+ if (!isDragging.value) {
1799
+ isSeeking.value = false;
1800
+ }
2070
1801
  emit("progress", info);
2071
1802
  },
2072
1803
  onToc: (items) => toc.value = items,
@@ -2101,6 +1832,7 @@ var EBookReaderVue = vue.defineComponent({
2101
1832
  });
2102
1833
  vue.onBeforeUnmount(() => {
2103
1834
  openController?.abort();
1835
+ downloadLoading.value = null;
2104
1836
  const root = rootEl.value;
2105
1837
  if (root) {
2106
1838
  root.removeEventListener("keydown", keydownHandler);
@@ -2152,8 +1884,13 @@ var EBookReaderVue = vue.defineComponent({
2152
1884
  const sectionLabel = progressInfo.value?.tocItem?.label ?? "";
2153
1885
  const isMobile = layout.value === "mobile";
2154
1886
  const viewer = vue.h("div", { class: "epub-reader__viewer", ref: viewerHost });
1887
+ const loading = downloadLoading.value ? vue.h("div", { class: "epub-reader__loading", role: "status", "aria-live": "polite" }, [
1888
+ vue.h("div", { class: "epub-reader__spinner", "aria-hidden": "true" }),
1889
+ vue.h("div", { class: "epub-reader__loading-text" }, downloadLoading.value === "download" ? "\u4E0B\u8F7D\u4E2D\u2026" : "\u52A0\u8F7D\u4E2D\u2026")
1890
+ ]) : null;
2155
1891
  const children = isMobile ? [
2156
1892
  viewer,
1893
+ loading,
2157
1894
  vue.h(MobileUI_default, {
2158
1895
  barVisible: mobileBarVisible.value,
2159
1896
  activePanel: mobilePanel.value,
@@ -2181,21 +1918,27 @@ var EBookReaderVue = vue.defineComponent({
2181
1918
  onSearchResultSelect: (cfi) => {
2182
1919
  if (cfi) reader.value?.goTo(cfi);
2183
1920
  },
2184
- onSeekStart: () => isSeeking.value = true,
1921
+ onSeekStart: () => {
1922
+ isSeeking.value = true;
1923
+ isDragging.value = true;
1924
+ },
2185
1925
  onSeekChange: (v) => seekPercent.value = v,
2186
1926
  onSeekEnd: (v) => {
2187
- isSeeking.value = false;
1927
+ isDragging.value = false;
2188
1928
  reader.value?.goToFraction(v / 100);
2189
1929
  },
2190
1930
  onSeekCommit: (v) => {
2191
- isSeeking.value = false;
1931
+ isDragging.value = false;
2192
1932
  reader.value?.goToFraction(v / 100);
2193
1933
  },
2194
1934
  onToggleDarkMode: setDarkModeInternal,
2195
1935
  onChangeFontSize: setFontSizeInternal
1936
+ }, {
1937
+ toolbarRight: () => slots.mobileToolbarRight?.()
2196
1938
  })
2197
1939
  ] : [
2198
1940
  viewer,
1941
+ loading,
2199
1942
  vue.h(DesktopToolbar_default, {
2200
1943
  darkMode: darkMode(),
2201
1944
  fontSize: fontSize(),
@@ -2239,14 +1982,17 @@ var EBookReaderVue = vue.defineComponent({
2239
1982
  errorText: errorText.value,
2240
1983
  sectionLabel,
2241
1984
  displayedPercent: displayed,
2242
- onSeekStart: () => isSeeking.value = true,
1985
+ onSeekStart: () => {
1986
+ isSeeking.value = true;
1987
+ isDragging.value = true;
1988
+ },
2243
1989
  onSeekChange: (v) => seekPercent.value = v,
2244
1990
  onSeekEnd: (v) => {
2245
- isSeeking.value = false;
1991
+ isDragging.value = false;
2246
1992
  reader.value?.goToFraction(v / 100);
2247
1993
  },
2248
1994
  onSeekCommit: (v) => {
2249
- isSeeking.value = false;
1995
+ isDragging.value = false;
2250
1996
  reader.value?.goToFraction(v / 100);
2251
1997
  }
2252
1998
  })
@@ -2258,6 +2004,7 @@ var EBookReaderVue = vue.defineComponent({
2258
2004
  class: "epub-reader",
2259
2005
  "data-theme": darkMode() ? "dark" : "light",
2260
2006
  "data-layout": layout.value,
2007
+ "aria-busy": downloadLoading.value != null,
2261
2008
  tabindex: 0
2262
2009
  },
2263
2010
  children
@@ -2266,9 +2013,6 @@ var EBookReaderVue = vue.defineComponent({
2266
2013
  }
2267
2014
  });
2268
2015
 
2269
- // src/vue/index.ts
2270
- ensureEpubReaderStyle();
2271
-
2272
2016
  exports.EBookReaderVue = EBookReaderVue;
2273
2017
  //# sourceMappingURL=vue.cjs.map
2274
2018
  //# sourceMappingURL=vue.cjs.map