@somecat/epub-reader 0.1.5 → 0.1.7

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
@@ -3,33 +3,52 @@ import 'foliate-js/view.js';
3
3
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
4
4
 
5
5
  // src/react/EBookReader.tsx
6
- var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => `
6
+ var getContentCSS = (fontSize, isDark, lineHeight, letterSpacing, extraCSS) => {
7
+ const scale = fontSize / 100;
8
+ return `
7
9
  @namespace epub "http://www.idpf.org/2007/ops";
8
- html {
10
+ :root:root {
9
11
  color-scheme: ${isDark ? "dark" : "light"} !important;
10
12
  }
11
- body {
13
+ :root:root body {
12
14
  background-color: transparent !important;
13
15
  color: ${isDark ? "#e0e0e0" : "black"} !important;
14
16
  font-size: ${fontSize}% !important;
15
17
  line-height: ${lineHeight} !important;
16
18
  letter-spacing: ${letterSpacing}em !important;
19
+ -webkit-text-size-adjust: 100% !important;
20
+ text-size-adjust: 100% !important;
17
21
  }
18
- p {
22
+
23
+ :root:root body :is(p, li, blockquote) {
19
24
  line-height: inherit !important;
25
+ }
26
+
27
+ :root:root body p {
20
28
  margin-bottom: 1em;
21
29
  }
22
- a {
30
+
31
+ :root:root body a {
23
32
  color: ${isDark ? "#64b5f6" : "#2563eb"} !important;
24
33
  }
25
- img {
34
+
35
+ :root:root body img {
26
36
  max-width: 100%;
27
37
  height: auto;
28
38
  object-fit: contain;
29
39
  ${isDark ? "filter: brightness(0.8) contrast(1.2);" : ""}
30
40
  }
41
+
42
+ @supports (zoom: 1) {
43
+ :root:root body[data-epub-reader-force-zoom='true'] {
44
+ zoom: ${scale};
45
+ font-size: 100% !important;
46
+ }
47
+ }
48
+
31
49
  ${extraCSS ?? ""}
32
50
  `;
51
+ };
33
52
  function createEBookReader(container, options = {}) {
34
53
  if (!container) throw new Error("container is required");
35
54
  if (!customElements.get("foliate-view")) throw new Error("foliate-view is not defined");
@@ -53,6 +72,8 @@ function createEBookReader(container, options = {}) {
53
72
  let lineHeight = initialLineHeight;
54
73
  let letterSpacing = initialLetterSpacing;
55
74
  let searchToken = 0;
75
+ let activeDoc = null;
76
+ let forceZoomEnabled = false;
56
77
  container.innerHTML = "";
57
78
  const viewer = document.createElement("foliate-view");
58
79
  viewer.style.display = "block";
@@ -60,21 +81,59 @@ function createEBookReader(container, options = {}) {
60
81
  viewer.style.height = "100%";
61
82
  viewer.setAttribute("margin", "48");
62
83
  viewer.setAttribute("gap", "0.07");
63
- const applyStyles = () => {
84
+ const pickSampleEl = (doc) => {
85
+ const candidates = doc.querySelectorAll("p, li, blockquote, span, div");
86
+ for (const el of candidates) {
87
+ const text = el.textContent?.trim();
88
+ if (!text) continue;
89
+ if (text.length < 24) continue;
90
+ return el;
91
+ }
92
+ return doc.body;
93
+ };
94
+ const readFontSizePx = (doc) => {
95
+ const el = pickSampleEl(doc);
96
+ if (!el) return null;
97
+ const px = Number.parseFloat(getComputedStyle(el).fontSize);
98
+ return Number.isFinite(px) ? px : null;
99
+ };
100
+ const applyForceZoomIfNeeded = () => {
101
+ if (!forceZoomEnabled) return;
102
+ if (!activeDoc?.body) return;
103
+ activeDoc.body.setAttribute("data-epub-reader-force-zoom", "true");
104
+ };
105
+ const applyStyles = (check) => {
64
106
  if (destroyed) return;
65
107
  if (!viewer.renderer?.setStyles) return;
108
+ applyForceZoomIfNeeded();
66
109
  viewer.renderer.setStyles(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
67
110
  requestAnimationFrame(() => {
68
111
  setTimeout(() => {
69
112
  if (destroyed) return;
70
113
  viewer.renderer?.render?.();
71
114
  viewer.renderer?.expand?.();
115
+ if (!check) return;
116
+ if (forceZoomEnabled) return;
117
+ if (!activeDoc) return;
118
+ const { beforePx, beforeFontSize, afterFontSize } = check;
119
+ const afterPx = readFontSizePx(activeDoc);
120
+ if (beforePx == null || afterPx == null) return;
121
+ const isMeaningfulChange = Math.abs(afterFontSize - beforeFontSize) >= 10;
122
+ const isIneffective = Math.abs(afterPx - beforePx) < 0.5;
123
+ if (isMeaningfulChange && isIneffective) {
124
+ forceZoomEnabled = true;
125
+ applyForceZoomIfNeeded();
126
+ viewer.renderer?.setStyles?.(getContentCSS(fontSize, darkMode, lineHeight, letterSpacing, extraContentCSS));
127
+ viewer.renderer?.render?.();
128
+ viewer.renderer?.expand?.();
129
+ }
72
130
  }, 50);
73
131
  });
74
132
  };
75
133
  const handleLoad = (e) => {
76
- applyStyles();
77
134
  const detail = e.detail;
135
+ activeDoc = detail?.doc ?? null;
136
+ applyStyles();
78
137
  if (detail?.doc) onContentLoad?.(detail.doc);
79
138
  };
80
139
  const handleRelocate = (e) => {
@@ -91,6 +150,8 @@ function createEBookReader(container, options = {}) {
91
150
  try {
92
151
  viewer.clearSearch?.();
93
152
  searchToken++;
153
+ activeDoc = null;
154
+ forceZoomEnabled = false;
94
155
  await viewer.open?.(file);
95
156
  const nextToc = viewer.book?.toc ?? [];
96
157
  toc = nextToc;
@@ -136,8 +197,10 @@ function createEBookReader(container, options = {}) {
136
197
  },
137
198
  setFontSize(nextFontSize) {
138
199
  const safe = Math.min(300, Math.max(50, nextFontSize));
200
+ const beforeFontSize = fontSize;
201
+ const beforePx = activeDoc && !forceZoomEnabled ? readFontSizePx(activeDoc) : null;
139
202
  fontSize = safe;
140
- applyStyles();
203
+ applyStyles({ beforePx, beforeFontSize, afterFontSize: safe });
141
204
  },
142
205
  setLineHeight(nextLineHeight) {
143
206
  const safe = Math.min(3, Math.max(1, nextLineHeight));
@@ -257,6 +320,7 @@ var icons = {
257
320
  x: '<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
258
321
  type: '<path d="M4 7V4h16v3M9 20h6M12 4v16" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>',
259
322
  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"/>',
323
+ progress: '<g transform="scale(0.0234375)"><path fill="currentColor" d="M835.79 610.26a25.38 25.38 0 1 1 0-50.77h129.77a25.38 25.38 0 0 1 0 50.77zM58.23 606.63a25.38 25.38 0 1 1 0-50.77h64.87a25.38 25.38 0 1 1 0 50.77z"/><path fill="currentColor" d="M479.73 264.12C627.34 264.12 747 383.78 747 531.39S627.34 798.66 479.73 798.66 212.46 679 212.46 531.39s119.66-267.27 267.27-267.27m0-46a312.43 312.43 0 1 0 122 24.64 311.35 311.35 0 0 0-122-24.64z"/></g>',
260
324
  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"/>'
261
325
  };
262
326
  var SvgIcon = ({ name, size = 24, color = "currentColor", className }) => {
@@ -509,7 +573,7 @@ var MobileUI = ({
509
573
  onTouchEnd: handleTouchEnd,
510
574
  onTouchCancel: handleTouchEnd,
511
575
  title: "\u8FDB\u5EA6",
512
- children: /* @__PURE__ */ jsx(SvgIcon, { name: "sliders" })
576
+ children: /* @__PURE__ */ jsx(SvgIcon, { name: "progress" })
513
577
  }
514
578
  ),
515
579
  /* @__PURE__ */ jsx(
@@ -579,7 +643,7 @@ var MobileUI = ({
579
643
  }
580
644
  }
581
645
  ),
582
- /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__btn", onClick: () => onSearch(search.query), disabled: status !== "ready", children: "\u641C\u7D22" })
646
+ /* @__PURE__ */ jsx("button", { type: "button", className: "epub-reader__btn epub-reader__btn--wide", onClick: () => onSearch(search.query), disabled: status !== "ready", children: "\u641C\u7D22" })
583
647
  ] }),
584
648
  /* @__PURE__ */ jsxs("div", { className: "epub-reader__checks", children: [
585
649
  /* @__PURE__ */ jsxs("label", { className: "epub-reader__check", children: [
@@ -632,6 +696,39 @@ var MobileUI = ({
632
696
  /* @__PURE__ */ jsx("span", { className: "epub-reader__status", children: status === "error" ? errorText || "\u9519\u8BEF" : status === "opening" ? "\u6B63\u5728\u6253\u5F00\u2026" : "" }),
633
697
  sectionLabel ? /* @__PURE__ */ jsx("span", { children: sectionLabel }) : null
634
698
  ] }),
699
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__mprogress", children: [
700
+ /* @__PURE__ */ jsx(
701
+ "input",
702
+ {
703
+ className: "epub-reader__range",
704
+ type: "range",
705
+ min: 0,
706
+ max: 100,
707
+ step: 1,
708
+ value: displayedPercent,
709
+ style: {
710
+ background: `linear-gradient(to right, var(--epub-reader-range-fill) 0%, var(--epub-reader-range-fill) ${displayedPercent}%, var(--epub-reader-range-track) ${displayedPercent}%, var(--epub-reader-range-track) 100%)`
711
+ },
712
+ onChange: (e) => {
713
+ onSeekStart();
714
+ onSeekChange(Number(e.target.value));
715
+ },
716
+ onPointerUp: (e) => {
717
+ const v = Number(e.target.value);
718
+ onSeekEnd(v);
719
+ },
720
+ onKeyUp: (e) => {
721
+ if (e.key !== "Enter") return;
722
+ const v = Number(e.target.value);
723
+ onSeekCommit(v);
724
+ }
725
+ }
726
+ ),
727
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__mprogress-percent", children: [
728
+ displayedPercent,
729
+ "%"
730
+ ] })
731
+ ] }),
635
732
  /* @__PURE__ */ jsxs("div", { className: "epub-reader__mnav", children: [
636
733
  /* @__PURE__ */ jsx(
637
734
  "button",
@@ -685,36 +782,6 @@ var MobileUI = ({
685
782
  children: /* @__PURE__ */ jsx(SvgIcon, { name: "chevrons-right" })
686
783
  }
687
784
  )
688
- ] }),
689
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__mprogress", children: [
690
- /* @__PURE__ */ jsx(
691
- "input",
692
- {
693
- className: "epub-reader__range",
694
- type: "range",
695
- min: 0,
696
- max: 100,
697
- step: 1,
698
- value: displayedPercent,
699
- onChange: (e) => {
700
- onSeekStart();
701
- onSeekChange(Number(e.target.value));
702
- },
703
- onPointerUp: (e) => {
704
- const v = Number(e.target.value);
705
- onSeekEnd(v);
706
- },
707
- onKeyUp: (e) => {
708
- if (e.key !== "Enter") return;
709
- const v = Number(e.target.value);
710
- onSeekCommit(v);
711
- }
712
- }
713
- ),
714
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__mprogress-percent", children: [
715
- displayedPercent,
716
- "%"
717
- ] })
718
785
  ] })
719
786
  ] }) : null,
720
787
  activePanel === "settings" ? /* @__PURE__ */ jsxs("div", { className: "epub-reader__msettings", children: [
@@ -758,59 +825,61 @@ var MobileUI = ({
758
825
  ] }),
759
826
  /* @__PURE__ */ jsx("div", { className: "epub-reader__mfont-a is-big", children: "A" })
760
827
  ] }),
761
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__msetting", children: [
762
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__msetting-head", children: [
763
- /* @__PURE__ */ jsx("div", { className: "epub-reader__msetting-label", children: "\u884C\u9AD8" }),
764
- /* @__PURE__ */ jsx("div", { className: "epub-reader__msetting-value", children: lineHeight.toFixed(2) })
828
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__msettings-row", children: [
829
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__msetting", children: [
830
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__msetting-head", children: [
831
+ /* @__PURE__ */ jsx("div", { className: "epub-reader__msetting-label", children: "\u884C\u9AD8" }),
832
+ /* @__PURE__ */ jsx("div", { className: "epub-reader__msetting-value", children: lineHeight.toFixed(2) })
833
+ ] }),
834
+ /* @__PURE__ */ jsx(
835
+ "input",
836
+ {
837
+ className: "epub-reader__range",
838
+ type: "range",
839
+ min: 1,
840
+ max: 3,
841
+ step: 0.05,
842
+ value: lineHeight,
843
+ "aria-label": "\u884C\u9AD8",
844
+ onChange: (e) => onLineHeightChange(Number(e.target.value))
845
+ }
846
+ )
765
847
  ] }),
766
- /* @__PURE__ */ jsx(
767
- "input",
768
- {
769
- className: "epub-reader__range",
770
- type: "range",
771
- min: 1,
772
- max: 3,
773
- step: 0.05,
774
- value: lineHeight,
775
- "aria-label": "\u884C\u9AD8",
776
- onChange: (e) => onLineHeightChange(Number(e.target.value))
777
- }
778
- )
779
- ] }),
780
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__msetting", children: [
781
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__msetting-head", children: [
782
- /* @__PURE__ */ jsx("div", { className: "epub-reader__msetting-label", children: "\u5B57\u95F4\u8DDD" }),
783
- /* @__PURE__ */ jsxs("div", { className: "epub-reader__msetting-value", children: [
784
- letterSpacing.toFixed(2),
785
- "em"
786
- ] })
848
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__msetting", children: [
849
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__msetting-head", children: [
850
+ /* @__PURE__ */ jsx("div", { className: "epub-reader__msetting-label", children: "\u5B57\u95F4\u8DDD" }),
851
+ /* @__PURE__ */ jsxs("div", { className: "epub-reader__msetting-value", children: [
852
+ letterSpacing.toFixed(2),
853
+ "em"
854
+ ] })
855
+ ] }),
856
+ /* @__PURE__ */ jsx(
857
+ "input",
858
+ {
859
+ className: "epub-reader__range",
860
+ type: "range",
861
+ min: 0,
862
+ max: 0.3,
863
+ step: 0.01,
864
+ value: letterSpacing,
865
+ "aria-label": "\u5B57\u95F4\u8DDD",
866
+ onChange: (e) => onLetterSpacingChange(Number(e.target.value))
867
+ }
868
+ )
787
869
  ] }),
788
870
  /* @__PURE__ */ jsx(
789
- "input",
871
+ "button",
790
872
  {
791
- className: "epub-reader__range",
792
- type: "range",
793
- min: 0,
794
- max: 0.3,
795
- step: 0.01,
796
- value: letterSpacing,
797
- "aria-label": "\u5B57\u95F4\u8DDD",
798
- onChange: (e) => onLetterSpacingChange(Number(e.target.value))
873
+ type: "button",
874
+ className: "epub-reader__btn",
875
+ onClick: () => onToggleDarkMode(!darkMode),
876
+ "aria-pressed": darkMode,
877
+ "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",
878
+ title: darkMode ? "\u5207\u6362\u5230\u4EAE\u8272" : "\u5207\u6362\u5230\u6697\u9ED1",
879
+ children: /* @__PURE__ */ jsx(SvgIcon, { name: darkMode ? "sun" : "moon" })
799
880
  }
800
881
  )
801
- ] }),
802
- /* @__PURE__ */ jsx(
803
- "button",
804
- {
805
- type: "button",
806
- className: "epub-reader__btn",
807
- onClick: () => onToggleDarkMode(!darkMode),
808
- "aria-pressed": darkMode,
809
- "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",
810
- title: darkMode ? "\u5207\u6362\u5230\u4EAE\u8272" : "\u5207\u6362\u5230\u6697\u9ED1",
811
- children: /* @__PURE__ */ jsx(SvgIcon, { name: darkMode ? "sun" : "moon" })
812
- }
813
- )
882
+ ] })
814
883
  ] }) : null
815
884
  ] })
816
885
  ]