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