@somecat/epub-reader 0.1.6 → 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));