@nypl/web-reader 5.0.0-alpha.0 → 5.0.0

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/README.md CHANGED
@@ -123,6 +123,18 @@ There are two different injectables props you can pass to the web reader.
123
123
 
124
124
  Your app can provide both props or only one. The reader will decide which one to load into the iframe based on the book format defined in the webpub manifest.
125
125
 
126
+ ## Required CSS for the PDF Reader (Text & Annotation Layers)
127
+
128
+ When rendering PDFs the library relies on `react-pdf`'s text and annotation layer styles.
129
+
130
+ To avoid importing global CSS from inside distributed JS in Next.js applications, you must import global CSS only from the app root (`pages/_app.js` / `pages/_app.tsx` or `app/layout.tsx`). After installing `@nypl/web-reader`, import the following CSS from your application root:
131
+
132
+ ```js
133
+ // pages/_app.tsx or app/layout.tsx
134
+ import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
135
+ import 'react-pdf/dist/esm/Page/TextLayer.css';
136
+ ```
137
+
126
138
  ## Other Injectables
127
139
 
128
140
  You can import and inject other files into the `<WebReader />` to customize behavior. For example, in Open eBooks, we import some [custom JavaScript](https://github.com/NYPL/ereading-clients/blob/staging/apps/oew/src/components/theme-ui/WebReader.tsx#L65) to disable right clicking & copying copywritten content.
package/dist/index.js CHANGED
@@ -3515,10 +3515,7 @@ function createMultiStyleConfigHelpers(parts) {
3515
3515
  }
3516
3516
 
3517
3517
  // src/ui/theme/components/tabs.ts
3518
- var {
3519
- defineMultiStyleConfig,
3520
- definePartsStyle
3521
- } = createMultiStyleConfigHelpers(["root", "tab", "tablist"]);
3518
+ var { defineMultiStyleConfig, definePartsStyle } = createMultiStyleConfigHelpers(["root", "tab", "tablist"]);
3522
3519
  var getTabsStyle = (getColor2) => defineMultiStyleConfig({
3523
3520
  variants: {
3524
3521
  custom: definePartsStyle({
@@ -6129,14 +6126,7 @@ function Header(props) {
6129
6126
  var _a, _b, _c, _d;
6130
6127
  const [, toggleFullscreenHook] = useFullscreen();
6131
6128
  const [isFullscreen, setIsFullScreen] = (0, import_react84.useState)(false);
6132
- const {
6133
- navigator,
6134
- manifest,
6135
- type,
6136
- containerRef,
6137
- currentPage,
6138
- totalPages
6139
- } = props;
6129
+ const { navigator, manifest, type, containerRef, currentPage, totalPages } = props;
6140
6130
  const isAtStart = (_a = props.state) == null ? void 0 : _a.atStart;
6141
6131
  const isAtEnd = (_b = props.state) == null ? void 0 : _b.atEnd;
6142
6132
  const iconFill = useColorModeValue_default(
@@ -6756,12 +6746,8 @@ function setReflowableCss(iframeHtml, settings) {
6756
6746
  }
6757
6747
  function setFixedCss(iframeDocument, iframeContainer) {
6758
6748
  if (!iframeContainer) return;
6759
- let { contentWidth, contentHeight } = extractContentViewportSize(
6760
- iframeDocument
6761
- );
6762
- const { containerWidth, containerHeight } = extractContentContainerSize(
6763
- iframeContainer
6764
- );
6749
+ let { contentWidth, contentHeight } = extractContentViewportSize(iframeDocument);
6750
+ const { containerWidth, containerHeight } = extractContentContainerSize(iframeContainer);
6765
6751
  contentWidth = contentWidth != null ? contentWidth : containerWidth;
6766
6752
  contentHeight = contentHeight != null ? contentHeight : containerHeight;
6767
6753
  const scale = Math.min(
@@ -7328,9 +7314,7 @@ function useResource(manifest, state2, getContent, injectables, dispatch) {
7328
7314
  if (element) document2 == null ? void 0 : document2.head.appendChild(element);
7329
7315
  }
7330
7316
  injectJS(document2.body);
7331
- const iframeContainer = window.document.querySelector(
7332
- 'main [role="progressbar"]'
7333
- );
7317
+ const iframeContainer = window.document.querySelector('main [role="progressbar"]');
7334
7318
  const readerSettings = {
7335
7319
  colorMode: state2.settings.colorMode,
7336
7320
  fontSize: state2.settings.fontSize,
@@ -7695,129 +7679,6 @@ var ChakraPage = (0, import_react96.chakra)(
7695
7679
  );
7696
7680
  var ChakraPage_default = ChakraPage;
7697
7681
 
7698
- // src/PdfReader/ScrollPage.tsx
7699
- var import_react98 = __toESM(require("react"));
7700
- var import_react_intersection_observer = require("react-intersection-observer");
7701
- var Placeholder = ({ width, height, pageNumber }) => {
7702
- return /* @__PURE__ */ import_react98.default.createElement(
7703
- "div",
7704
- {
7705
- "data-page-number": pageNumber,
7706
- style: { width, height }
7707
- }
7708
- );
7709
- };
7710
- var ScrollPage = ({
7711
- scale,
7712
- pageNumber,
7713
- width,
7714
- height,
7715
- onLoadSuccess,
7716
- placeholderHeight,
7717
- placeholderWidth,
7718
- allowInView,
7719
- onInView,
7720
- fitMode,
7721
- rotate
7722
- }) => {
7723
- const { ref: loadRef, inView: loadInView } = (0, import_react_intersection_observer.useInView)({
7724
- threshold: 0,
7725
- triggerOnce: true
7726
- });
7727
- const { ref: visibilityRef, entry } = (0, import_react_intersection_observer.useInView)({
7728
- threshold: Array.from({ length: 11 }, (_, i) => i * 0.1),
7729
- triggerOnce: false
7730
- });
7731
- const setRefs = import_react98.default.useCallback(
7732
- (el) => {
7733
- if (typeof loadRef === "function") loadRef(el);
7734
- if (typeof visibilityRef === "function") visibilityRef(el);
7735
- },
7736
- [loadRef, visibilityRef]
7737
- );
7738
- const handleLoadSuccess = import_react98.default.useCallback(
7739
- (page) => {
7740
- onLoadSuccess(page);
7741
- },
7742
- [onLoadSuccess]
7743
- );
7744
- import_react98.default.useEffect(() => {
7745
- if (onInView && entry) {
7746
- onInView(pageNumber, entry.intersectionRatio || 0);
7747
- }
7748
- }, [entry, onInView, pageNumber]);
7749
- return /* @__PURE__ */ import_react98.default.createElement("div", { ref: setRefs }, loadInView ? /* @__PURE__ */ import_react98.default.createElement(
7750
- ChakraPage_default,
7751
- {
7752
- "data-page-number": pageNumber,
7753
- pageNumber,
7754
- scale,
7755
- width,
7756
- height,
7757
- onLoadSuccess: handleLoadSuccess,
7758
- fitMode,
7759
- rotate
7760
- }
7761
- ) : /* @__PURE__ */ import_react98.default.createElement(
7762
- Placeholder,
7763
- {
7764
- width: placeholderWidth,
7765
- height: placeholderHeight,
7766
- pageNumber
7767
- }
7768
- ));
7769
- };
7770
- var ScrollPage_default = ScrollPage;
7771
-
7772
- // src/PdfReader/useMeasure.tsx
7773
- var React49 = __toESM(require("react"));
7774
- var DEFAULT_DIMENSION = {
7775
- x: 0,
7776
- y: 0,
7777
- width: 0,
7778
- height: 0,
7779
- top: 0,
7780
- left: 0,
7781
- bottom: 0,
7782
- right: 0
7783
- };
7784
- function useMeasure() {
7785
- const [element, ref] = React49.useState(null);
7786
- const [rect, setRect] = React49.useState(DEFAULT_DIMENSION);
7787
- const observer = React49.useMemo(
7788
- () => new window.ResizeObserver(
7789
- (entries) => {
7790
- if (entries[0]) {
7791
- const {
7792
- x,
7793
- y,
7794
- width,
7795
- height,
7796
- top,
7797
- left,
7798
- bottom,
7799
- right
7800
- } = entries[0].contentRect;
7801
- setRect({ x, y, width, height, top, left, bottom, right });
7802
- }
7803
- }
7804
- ),
7805
- []
7806
- );
7807
- React49.useLayoutEffect(() => {
7808
- if (!element) return;
7809
- observer.observe(element);
7810
- return () => {
7811
- observer.disconnect();
7812
- };
7813
- }, [element, observer]);
7814
- return [ref, rect];
7815
- }
7816
-
7817
- // src/PdfReader/index.tsx
7818
- var import_AnnotationLayer = require("react-pdf/dist/Page/AnnotationLayer.css");
7819
- var import_TextLayer = require("react-pdf/dist/Page/TextLayer.css");
7820
-
7821
7682
  // src/PdfReader/lib.ts
7822
7683
  var SCALE_STEP = 0.1;
7823
7684
  var START_QUERY = "start";
@@ -8049,6 +7910,116 @@ function isStartOfResource(pageNumber, resourceHref) {
8049
7910
  return pageNumber === (startPage != null ? startPage : 1);
8050
7911
  }
8051
7912
 
7913
+ // src/PdfReader/ScrollPage.tsx
7914
+ var import_react98 = __toESM(require("react"));
7915
+ var import_react_intersection_observer = require("react-intersection-observer");
7916
+ var Placeholder = ({ width, height, pageNumber }) => {
7917
+ return /* @__PURE__ */ import_react98.default.createElement(
7918
+ "div",
7919
+ {
7920
+ "data-page-number": pageNumber,
7921
+ style: { width, height }
7922
+ }
7923
+ );
7924
+ };
7925
+ var ScrollPage = ({
7926
+ scale,
7927
+ pageNumber,
7928
+ width,
7929
+ height,
7930
+ onLoadSuccess,
7931
+ placeholderHeight,
7932
+ placeholderWidth,
7933
+ allowInView,
7934
+ onInView,
7935
+ fitMode,
7936
+ rotate
7937
+ }) => {
7938
+ const { ref: loadRef, inView: loadInView } = (0, import_react_intersection_observer.useInView)({
7939
+ threshold: 0,
7940
+ triggerOnce: true
7941
+ });
7942
+ const { ref: visibilityRef, entry } = (0, import_react_intersection_observer.useInView)({
7943
+ threshold: Array.from({ length: 11 }, (_, i) => i * 0.1),
7944
+ triggerOnce: false
7945
+ });
7946
+ const setRefs = import_react98.default.useCallback(
7947
+ (el) => {
7948
+ if (typeof loadRef === "function") loadRef(el);
7949
+ if (typeof visibilityRef === "function") visibilityRef(el);
7950
+ },
7951
+ [loadRef, visibilityRef]
7952
+ );
7953
+ const handleLoadSuccess = import_react98.default.useCallback(
7954
+ (page) => {
7955
+ onLoadSuccess(page);
7956
+ },
7957
+ [onLoadSuccess]
7958
+ );
7959
+ import_react98.default.useEffect(() => {
7960
+ if (allowInView && onInView && entry) {
7961
+ onInView(pageNumber, entry.intersectionRatio || 0);
7962
+ }
7963
+ }, [allowInView, entry, onInView, pageNumber]);
7964
+ return /* @__PURE__ */ import_react98.default.createElement("div", { ref: setRefs }, loadInView ? /* @__PURE__ */ import_react98.default.createElement(
7965
+ ChakraPage_default,
7966
+ {
7967
+ "data-page-number": pageNumber,
7968
+ pageNumber,
7969
+ scale,
7970
+ width,
7971
+ height,
7972
+ onLoadSuccess: handleLoadSuccess,
7973
+ fitMode,
7974
+ rotate
7975
+ }
7976
+ ) : /* @__PURE__ */ import_react98.default.createElement(
7977
+ Placeholder,
7978
+ {
7979
+ width: placeholderWidth,
7980
+ height: placeholderHeight,
7981
+ pageNumber
7982
+ }
7983
+ ));
7984
+ };
7985
+ var ScrollPage_default = ScrollPage;
7986
+
7987
+ // src/PdfReader/useMeasure.tsx
7988
+ var React49 = __toESM(require("react"));
7989
+ var DEFAULT_DIMENSION = {
7990
+ x: 0,
7991
+ y: 0,
7992
+ width: 0,
7993
+ height: 0,
7994
+ top: 0,
7995
+ left: 0,
7996
+ bottom: 0,
7997
+ right: 0
7998
+ };
7999
+ function useMeasure() {
8000
+ const [element, ref] = React49.useState(null);
8001
+ const [rect, setRect] = React49.useState(DEFAULT_DIMENSION);
8002
+ const observer = React49.useMemo(
8003
+ () => new window.ResizeObserver(
8004
+ (entries) => {
8005
+ if (entries[0]) {
8006
+ const { x, y, width, height, top, left, bottom, right } = entries[0].contentRect;
8007
+ setRect({ x, y, width, height, top, left, bottom, right });
8008
+ }
8009
+ }
8010
+ ),
8011
+ []
8012
+ );
8013
+ React49.useLayoutEffect(() => {
8014
+ if (!element) return;
8015
+ observer.observe(element);
8016
+ return () => {
8017
+ observer.disconnect();
8018
+ };
8019
+ }, [element, observer]);
8020
+ return [ref, rect];
8021
+ }
8022
+
8052
8023
  // src/PdfReader/index.tsx
8053
8024
  function usePdfReader(args) {
8054
8025
  var _a, _b, _c, _d, _e, _f;
@@ -8190,6 +8161,10 @@ function usePdfReader(args) {
8190
8161
  var _a2;
8191
8162
  if (!((_a2 = state2.settings) == null ? void 0 : _a2.isScrolling)) return;
8192
8163
  if (!state2.rendered) return;
8164
+ if (scrollState.current.isInViewUpdate) {
8165
+ scrollState.current.isInViewUpdate = false;
8166
+ return;
8167
+ }
8193
8168
  process.nextTick(() => {
8194
8169
  const page = document.querySelector(
8195
8170
  `[data-page-number="${state2.pageNumber}"]`
@@ -8234,29 +8209,38 @@ function usePdfReader(args) {
8234
8209
  const setFitMode = React50.useCallback((mode) => {
8235
8210
  dispatch({ type: "SET_FIT_MODE", fitMode: mode });
8236
8211
  }, []);
8237
- const intersectionRatios = React50.useRef({});
8238
- const lastMostVisiblePage = React50.useRef(state2.pageNumber);
8212
+ const scrollState = React50.useRef({
8213
+ ratios: /* @__PURE__ */ new Map(),
8214
+ lastVisiblepage: state2.pageNumber,
8215
+ hasScrolled: false,
8216
+ isInViewUpdate: false
8217
+ });
8239
8218
  const onInView = React50.useCallback(
8240
8219
  (pageNum, ratio) => {
8241
8220
  var _a2;
8221
+ const currentScrollState = scrollState.current;
8242
8222
  if (!((_a2 = state2.settings) == null ? void 0 : _a2.isScrolling)) return;
8243
- intersectionRatios.current[pageNum] = ratio;
8244
- Object.keys(intersectionRatios.current).forEach((key) => {
8245
- if (intersectionRatios.current[Number(key)] === 0) {
8246
- delete intersectionRatios.current[Number(key)];
8247
- }
8248
- });
8223
+ currentScrollState.ratios.set(pageNum, ratio);
8224
+ if (!currentScrollState.hasScrolled && state2.pageNumber === 1) {
8225
+ const container = document.querySelector(
8226
+ `#${MAIN_CONTENT_ID} .react-pdf__Document`
8227
+ );
8228
+ if (container && container.scrollTop > 0)
8229
+ currentScrollState.hasScrolled = true;
8230
+ else return;
8231
+ }
8232
+ let mostVisiblePage = currentScrollState.lastVisiblepage;
8249
8233
  let maxRatio = -1;
8250
- let mostVisiblePage = state2.pageNumber;
8251
- for (const [page, r] of Object.entries(intersectionRatios.current)) {
8234
+ currentScrollState.ratios.forEach((r, p) => {
8252
8235
  if (r > maxRatio) {
8253
8236
  maxRatio = r;
8254
- mostVisiblePage = Number(page);
8237
+ mostVisiblePage = p;
8255
8238
  }
8256
- }
8257
- if (mostVisiblePage !== lastMostVisiblePage.current) {
8258
- lastMostVisiblePage.current = mostVisiblePage;
8239
+ });
8240
+ if (mostVisiblePage !== currentScrollState.lastVisiblepage) {
8241
+ currentScrollState.lastVisiblepage = mostVisiblePage;
8259
8242
  if (state2.pageNumber !== mostVisiblePage) {
8243
+ currentScrollState.isInViewUpdate = true;
8260
8244
  dispatch({ type: "PAGE_IN_VIEW", page: mostVisiblePage });
8261
8245
  }
8262
8246
  }
@@ -8355,7 +8339,7 @@ function usePdfReader(args) {
8355
8339
  scale: state2.scale,
8356
8340
  pageNumber: index + 1,
8357
8341
  onLoadSuccess: onRenderSuccess,
8358
- allowInView: !isFetching,
8342
+ allowInView: state2.rendered,
8359
8343
  onInView,
8360
8344
  fitMode: state2.fitMode,
8361
8345
  rotate: (_a2 = state2.rotation) != null ? _a2 : 0