@hypercard-ai/hyper-jump 0.1.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/LICENSE +21 -0
- package/README.md +75 -0
- package/dist/index.css +143 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +318 -0
- package/dist/index.js.map +1 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HyperCard.AI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# hyper-jump
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@hypercard-ai/hyper-jump)
|
|
4
|
+
[](https://github.com/hypercard-ai/hyper-jump/actions/workflows/ci.yml)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
A React PDF viewer built for RAG (Retrieval-Augmented Generation). Originally developed as part of [HyperCard.AI](https://hypercard.ai)'s chatbot platform, hyper-jump provides fast page navigation that pairs with RAG citations to deliver an excellent document viewing experience.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Virtualized rendering via `react-window` for smooth viewing of large PDFs
|
|
12
|
+
- Jump-to-page navigation for instant access to cited content
|
|
13
|
+
- Zoom controls with preset levels and automatic fit-to-width
|
|
14
|
+
- Responsive layout that adapts to container size
|
|
15
|
+
- Lightweight, self-contained CSS with no external styling dependencies
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @hypercard-ai/hyper-jump
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { HyperJumpViewer } from "@hypercard-ai/hyper-jump";
|
|
27
|
+
import "@hypercard-ai/hyper-jump/styles.css";
|
|
28
|
+
|
|
29
|
+
function App() {
|
|
30
|
+
return <HyperJumpViewer url="/path/to/document.pdf" />;
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Jump to a specific page
|
|
35
|
+
|
|
36
|
+
Pass a zero-indexed `page` prop to scroll directly to a page:
|
|
37
|
+
|
|
38
|
+
```tsx
|
|
39
|
+
<HyperJumpViewer url="/path/to/document.pdf" page={3} />
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## API
|
|
43
|
+
|
|
44
|
+
### `<HyperJumpViewer />`
|
|
45
|
+
|
|
46
|
+
| Prop | Type | Required | Description |
|
|
47
|
+
| ------ | -------- | -------- | ----------------------------------- |
|
|
48
|
+
| `url` | `string` | Yes | URL or path to the PDF file |
|
|
49
|
+
| `page` | `number` | No | Zero-indexed page to scroll to |
|
|
50
|
+
|
|
51
|
+
### Exports
|
|
52
|
+
|
|
53
|
+
| Export | Type | Description |
|
|
54
|
+
| ----------------------- | --------- | ------------------------------------ |
|
|
55
|
+
| `HyperJumpViewer` | Component | The PDF viewer component |
|
|
56
|
+
| `HyperJumpViewerProps` | Type | Props for the viewer component |
|
|
57
|
+
| `ZoomConfig` | Type | Zoom configuration interface |
|
|
58
|
+
|
|
59
|
+
## Requirements
|
|
60
|
+
|
|
61
|
+
- React 19+
|
|
62
|
+
|
|
63
|
+
## Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm install
|
|
67
|
+
npm run storybook # Start Storybook on port 6006
|
|
68
|
+
npm run test # Run tests
|
|
69
|
+
npm run check # Format and lint with Biome
|
|
70
|
+
npm run build # Build the package
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## License
|
|
74
|
+
|
|
75
|
+
MIT
|
package/dist/index.css
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* src/styles.css */
|
|
2
|
+
.hj-viewer {
|
|
3
|
+
position: relative;
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
height: 100%;
|
|
8
|
+
width: 100%;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
}
|
|
11
|
+
.hj-viewer .react-pdf__Document {
|
|
12
|
+
height: 100%;
|
|
13
|
+
width: 100%;
|
|
14
|
+
}
|
|
15
|
+
.hj-page {
|
|
16
|
+
display: flex;
|
|
17
|
+
align-items: center;
|
|
18
|
+
justify-content: center;
|
|
19
|
+
}
|
|
20
|
+
.hj-loading {
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
background: #fff;
|
|
25
|
+
position: relative;
|
|
26
|
+
}
|
|
27
|
+
.hj-spinner {
|
|
28
|
+
width: 32px;
|
|
29
|
+
height: 32px;
|
|
30
|
+
border: 3px solid #e9ecef;
|
|
31
|
+
border-top-color: #228be6;
|
|
32
|
+
border-radius: 50%;
|
|
33
|
+
animation: hj-spin 0.6s linear infinite;
|
|
34
|
+
}
|
|
35
|
+
@keyframes hj-spin {
|
|
36
|
+
to {
|
|
37
|
+
transform: rotate(360deg);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
.hj-error {
|
|
41
|
+
display: flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
background: #fff;
|
|
45
|
+
color: #495057;
|
|
46
|
+
font-size: 14px;
|
|
47
|
+
}
|
|
48
|
+
.hj-controls {
|
|
49
|
+
position: absolute;
|
|
50
|
+
bottom: 12px;
|
|
51
|
+
left: 12px;
|
|
52
|
+
right: 12px;
|
|
53
|
+
display: flex;
|
|
54
|
+
justify-content: center;
|
|
55
|
+
align-items: center;
|
|
56
|
+
z-index: 10;
|
|
57
|
+
}
|
|
58
|
+
.hj-controls-bar {
|
|
59
|
+
display: flex;
|
|
60
|
+
align-items: center;
|
|
61
|
+
gap: 12px;
|
|
62
|
+
padding: 6px 12px;
|
|
63
|
+
background: #fff;
|
|
64
|
+
border: 1px solid #dee2e6;
|
|
65
|
+
border-radius: 8px;
|
|
66
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
67
|
+
}
|
|
68
|
+
.hj-controls-group {
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: center;
|
|
71
|
+
gap: 6px;
|
|
72
|
+
}
|
|
73
|
+
.hj-divider {
|
|
74
|
+
width: 1px;
|
|
75
|
+
height: 24px;
|
|
76
|
+
background: #dee2e6;
|
|
77
|
+
}
|
|
78
|
+
.hj-btn {
|
|
79
|
+
display: inline-flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
padding: 4px 12px;
|
|
83
|
+
font-size: 13px;
|
|
84
|
+
font-family: inherit;
|
|
85
|
+
line-height: 1.4;
|
|
86
|
+
border: 1px solid #dee2e6;
|
|
87
|
+
border-radius: 4px;
|
|
88
|
+
background: #fff;
|
|
89
|
+
color: #212529;
|
|
90
|
+
cursor: pointer;
|
|
91
|
+
user-select: none;
|
|
92
|
+
white-space: nowrap;
|
|
93
|
+
}
|
|
94
|
+
.hj-btn:hover:not(:disabled) {
|
|
95
|
+
background: #f8f9fa;
|
|
96
|
+
}
|
|
97
|
+
.hj-btn:disabled {
|
|
98
|
+
opacity: 0.5;
|
|
99
|
+
cursor: not-allowed;
|
|
100
|
+
}
|
|
101
|
+
.hj-icon-btn {
|
|
102
|
+
display: inline-flex;
|
|
103
|
+
align-items: center;
|
|
104
|
+
justify-content: center;
|
|
105
|
+
width: 28px;
|
|
106
|
+
height: 28px;
|
|
107
|
+
padding: 0;
|
|
108
|
+
border: 1px solid #dee2e6;
|
|
109
|
+
border-radius: 4px;
|
|
110
|
+
background: #fff;
|
|
111
|
+
color: #212529;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
}
|
|
114
|
+
.hj-icon-btn:hover:not(:disabled) {
|
|
115
|
+
background: #f8f9fa;
|
|
116
|
+
}
|
|
117
|
+
.hj-icon-btn:disabled {
|
|
118
|
+
opacity: 0.5;
|
|
119
|
+
cursor: not-allowed;
|
|
120
|
+
}
|
|
121
|
+
.hj-page-indicator {
|
|
122
|
+
font-size: 13px;
|
|
123
|
+
min-width: 50px;
|
|
124
|
+
text-align: center;
|
|
125
|
+
color: #212529;
|
|
126
|
+
user-select: none;
|
|
127
|
+
}
|
|
128
|
+
.hj-select {
|
|
129
|
+
font-size: 13px;
|
|
130
|
+
font-family: inherit;
|
|
131
|
+
padding: 4px 8px;
|
|
132
|
+
border: 1px solid #dee2e6;
|
|
133
|
+
border-radius: 4px;
|
|
134
|
+
background: #fff;
|
|
135
|
+
color: #212529;
|
|
136
|
+
cursor: pointer;
|
|
137
|
+
min-width: 120px;
|
|
138
|
+
}
|
|
139
|
+
.hj-select:focus {
|
|
140
|
+
outline: 2px solid #228be6;
|
|
141
|
+
outline-offset: -1px;
|
|
142
|
+
}
|
|
143
|
+
/*# sourceMappingURL=index.css.map */
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/styles.css"],"sourcesContent":["/* HyperJump PDF Viewer Styles */\n\n.hj-viewer {\n\tposition: relative;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\theight: 100%;\n\twidth: 100%;\n\toverflow: hidden;\n}\n\n.hj-viewer .react-pdf__Document {\n\theight: 100%;\n\twidth: 100%;\n}\n\n/* Page renderer */\n.hj-page {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n}\n\n/* Loading page */\n.hj-loading {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbackground: #fff;\n\tposition: relative;\n}\n\n.hj-spinner {\n\twidth: 32px;\n\theight: 32px;\n\tborder: 3px solid #e9ecef;\n\tborder-top-color: #228be6;\n\tborder-radius: 50%;\n\tanimation: hj-spin 0.6s linear infinite;\n}\n\n@keyframes hj-spin {\n\tto {\n\t\ttransform: rotate(360deg);\n\t}\n}\n\n/* Error page */\n.hj-error {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbackground: #fff;\n\tcolor: #495057;\n\tfont-size: 14px;\n}\n\n/* Controls */\n.hj-controls {\n\tposition: absolute;\n\tbottom: 12px;\n\tleft: 12px;\n\tright: 12px;\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n\tz-index: 10;\n}\n\n.hj-controls-bar {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 12px;\n\tpadding: 6px 12px;\n\tbackground: #fff;\n\tborder: 1px solid #dee2e6;\n\tborder-radius: 8px;\n\tbox-shadow:\n\t\t0 1px 3px rgba(0, 0, 0, 0.1),\n\t\t0 1px 2px rgba(0, 0, 0, 0.06);\n}\n\n.hj-controls-group {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 6px;\n}\n\n.hj-divider {\n\twidth: 1px;\n\theight: 24px;\n\tbackground: #dee2e6;\n}\n\n.hj-btn {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 4px 12px;\n\tfont-size: 13px;\n\tfont-family: inherit;\n\tline-height: 1.4;\n\tborder: 1px solid #dee2e6;\n\tborder-radius: 4px;\n\tbackground: #fff;\n\tcolor: #212529;\n\tcursor: pointer;\n\tuser-select: none;\n\twhite-space: nowrap;\n}\n\n.hj-btn:hover:not(:disabled) {\n\tbackground: #f8f9fa;\n}\n\n.hj-btn:disabled {\n\topacity: 0.5;\n\tcursor: not-allowed;\n}\n\n.hj-icon-btn {\n\tdisplay: inline-flex;\n\talign-items: center;\n\tjustify-content: center;\n\twidth: 28px;\n\theight: 28px;\n\tpadding: 0;\n\tborder: 1px solid #dee2e6;\n\tborder-radius: 4px;\n\tbackground: #fff;\n\tcolor: #212529;\n\tcursor: pointer;\n}\n\n.hj-icon-btn:hover:not(:disabled) {\n\tbackground: #f8f9fa;\n}\n\n.hj-icon-btn:disabled {\n\topacity: 0.5;\n\tcursor: not-allowed;\n}\n\n.hj-page-indicator {\n\tfont-size: 13px;\n\tmin-width: 50px;\n\ttext-align: center;\n\tcolor: #212529;\n\tuser-select: none;\n}\n\n.hj-select {\n\tfont-size: 13px;\n\tfont-family: inherit;\n\tpadding: 4px 8px;\n\tborder: 1px solid #dee2e6;\n\tborder-radius: 4px;\n\tbackground: #fff;\n\tcolor: #212529;\n\tcursor: pointer;\n\tmin-width: 120px;\n}\n\n.hj-select:focus {\n\toutline: 2px solid #228be6;\n\toutline-offset: -1px;\n}\n"],"mappings":";AAEA,CAAC;AACA,YAAU;AACV,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,UAAQ;AACR,SAAO;AACP,YAAU;AACX;AAEA,CAVC,UAUU,CAAC;AACX,UAAQ;AACR,SAAO;AACR;AAGA,CAAC;AACA,WAAS;AACT,eAAa;AACb,mBAAiB;AAClB;AAGA,CAAC;AACA,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,cAAY;AACZ,YAAU;AACX;AAEA,CAAC;AACA,SAAO;AACP,UAAQ;AACR,UAAQ,IAAI,MAAM;AAClB,oBAAkB;AAClB,iBAAe;AACf,aAAW,QAAQ,KAAK,OAAO;AAChC;AAEA,WAHY;AAIX;AACC,eAAW,OAAO;AACnB;AACD;AAGA,CAAC;AACA,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,cAAY;AACZ,SAAO;AACP,aAAW;AACZ;AAGA,CAAC;AACA,YAAU;AACV,UAAQ;AACR,QAAM;AACN,SAAO;AACP,WAAS;AACT,mBAAiB;AACjB,eAAa;AACb,WAAS;AACV;AAEA,CAAC;AACA,WAAS;AACT,eAAa;AACb,OAAK;AACL,WAAS,IAAI;AACb,cAAY;AACZ,UAAQ,IAAI,MAAM;AAClB,iBAAe;AACf,cACC,EAAE,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,EAC5B,EAAE,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AAC1B;AAEA,CAAC;AACA,WAAS;AACT,eAAa;AACb,OAAK;AACN;AAEA,CAAC;AACA,SAAO;AACP,UAAQ;AACR,cAAY;AACb;AAEA,CAAC;AACA,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,WAAS,IAAI;AACb,aAAW;AACX,eAAa;AACb,eAAa;AACb,UAAQ,IAAI,MAAM;AAClB,iBAAe;AACf,cAAY;AACZ,SAAO;AACP,UAAQ;AACR,eAAa;AACb,eAAa;AACd;AAEA,CAjBC,MAiBM,MAAM,KAAK;AACjB,cAAY;AACb;AAEA,CArBC,MAqBM;AACN,WAAS;AACT,UAAQ;AACT;AAEA,CAAC;AACA,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,SAAO;AACP,UAAQ;AACR,WAAS;AACT,UAAQ,IAAI,MAAM;AAClB,iBAAe;AACf,cAAY;AACZ,SAAO;AACP,UAAQ;AACT;AAEA,CAdC,WAcW,MAAM,KAAK;AACtB,cAAY;AACb;AAEA,CAlBC,WAkBW;AACX,WAAS;AACT,UAAQ;AACT;AAEA,CAAC;AACA,aAAW;AACX,aAAW;AACX,cAAY;AACZ,SAAO;AACP,eAAa;AACd;AAEA,CAAC;AACA,aAAW;AACX,eAAa;AACb,WAAS,IAAI;AACb,UAAQ,IAAI,MAAM;AAClB,iBAAe;AACf,cAAY;AACZ,SAAO;AACP,UAAQ;AACR,aAAW;AACZ;AAEA,CAZC,SAYS;AACT,WAAS,IAAI,MAAM;AACnB,kBAAgB;AACjB;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
type ZoomMode = "automatic" | "page-width" | "manual";
|
|
4
|
+
interface ZoomConfig {
|
|
5
|
+
mode: ZoomMode;
|
|
6
|
+
value: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface HyperJumpViewerProps {
|
|
10
|
+
/** URL of the PDF file to display */
|
|
11
|
+
url: string;
|
|
12
|
+
/** Page number to jump to (0-indexed) */
|
|
13
|
+
page?: number;
|
|
14
|
+
/** Called when the visible page changes (0-indexed) */
|
|
15
|
+
onPageChange?: (page: number) => void;
|
|
16
|
+
}
|
|
17
|
+
declare function HyperJumpViewer(props: HyperJumpViewerProps): react_jsx_runtime.JSX.Element;
|
|
18
|
+
|
|
19
|
+
export { HyperJumpViewer, type HyperJumpViewerProps, type ZoomConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// src/viewer.tsx
|
|
2
|
+
import { useCallback as useCallback2, useEffect, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
3
|
+
import { Document, pdfjs } from "react-pdf";
|
|
4
|
+
import "react-pdf/dist/Page/AnnotationLayer.css";
|
|
5
|
+
import "react-pdf/dist/Page/TextLayer.css";
|
|
6
|
+
import { List } from "react-window";
|
|
7
|
+
|
|
8
|
+
// src/controls.tsx
|
|
9
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
10
|
+
var ZOOM_OPTIONS = [
|
|
11
|
+
{ label: "Auto Width", value: "automatic" },
|
|
12
|
+
{ label: "50%", value: "0.5" },
|
|
13
|
+
{ label: "75%", value: "0.75" },
|
|
14
|
+
{ label: "100%", value: "1" },
|
|
15
|
+
{ label: "125%", value: "1.25" },
|
|
16
|
+
{ label: "150%", value: "1.5" },
|
|
17
|
+
{ label: "200%", value: "2" },
|
|
18
|
+
{ label: "300%", value: "3" },
|
|
19
|
+
{ label: "400%", value: "4" }
|
|
20
|
+
];
|
|
21
|
+
function ChevronLeft() {
|
|
22
|
+
return /* @__PURE__ */ jsxs(
|
|
23
|
+
"svg",
|
|
24
|
+
{
|
|
25
|
+
width: "16",
|
|
26
|
+
height: "16",
|
|
27
|
+
viewBox: "0 0 24 24",
|
|
28
|
+
fill: "none",
|
|
29
|
+
stroke: "currentColor",
|
|
30
|
+
strokeWidth: "2",
|
|
31
|
+
strokeLinecap: "round",
|
|
32
|
+
strokeLinejoin: "round",
|
|
33
|
+
children: [
|
|
34
|
+
/* @__PURE__ */ jsx("title", { children: "Previous Page" }),
|
|
35
|
+
/* @__PURE__ */ jsx("path", { d: "M15 18l-6-6 6-6" })
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
function ChevronRight() {
|
|
41
|
+
return /* @__PURE__ */ jsxs(
|
|
42
|
+
"svg",
|
|
43
|
+
{
|
|
44
|
+
width: "16",
|
|
45
|
+
height: "16",
|
|
46
|
+
viewBox: "0 0 24 24",
|
|
47
|
+
fill: "none",
|
|
48
|
+
stroke: "currentColor",
|
|
49
|
+
strokeWidth: "2",
|
|
50
|
+
strokeLinecap: "round",
|
|
51
|
+
strokeLinejoin: "round",
|
|
52
|
+
children: [
|
|
53
|
+
/* @__PURE__ */ jsx("title", { children: "Next Page" }),
|
|
54
|
+
/* @__PURE__ */ jsx("path", { d: "M9 18l6-6-6 6" })
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
function PDFViewerControls(props) {
|
|
60
|
+
const {
|
|
61
|
+
onChangeZoom,
|
|
62
|
+
pageIndex,
|
|
63
|
+
numPages,
|
|
64
|
+
zoomConfig,
|
|
65
|
+
onNextPage,
|
|
66
|
+
onPrevPage
|
|
67
|
+
} = props;
|
|
68
|
+
const zoomValue = zoomConfig.mode === "automatic" || zoomConfig.mode === "page-width" ? zoomConfig.mode : zoomConfig.value.toString();
|
|
69
|
+
return /* @__PURE__ */ jsx("div", { className: "hj-controls", children: /* @__PURE__ */ jsxs("div", { className: "hj-controls-bar", children: [
|
|
70
|
+
/* @__PURE__ */ jsxs("div", { className: "hj-controls-group", children: [
|
|
71
|
+
/* @__PURE__ */ jsx(
|
|
72
|
+
"button",
|
|
73
|
+
{
|
|
74
|
+
type: "button",
|
|
75
|
+
className: "hj-icon-btn",
|
|
76
|
+
onClick: onPrevPage,
|
|
77
|
+
disabled: pageIndex <= 0,
|
|
78
|
+
"aria-label": "Previous Page",
|
|
79
|
+
children: /* @__PURE__ */ jsx(ChevronLeft, {})
|
|
80
|
+
}
|
|
81
|
+
),
|
|
82
|
+
/* @__PURE__ */ jsxs("span", { className: "hj-page-indicator", children: [
|
|
83
|
+
pageIndex + 1,
|
|
84
|
+
" / ",
|
|
85
|
+
numPages
|
|
86
|
+
] }),
|
|
87
|
+
/* @__PURE__ */ jsx(
|
|
88
|
+
"button",
|
|
89
|
+
{
|
|
90
|
+
type: "button",
|
|
91
|
+
className: "hj-icon-btn",
|
|
92
|
+
onClick: onNextPage,
|
|
93
|
+
disabled: pageIndex >= numPages - 1,
|
|
94
|
+
"aria-label": "Next Page",
|
|
95
|
+
children: /* @__PURE__ */ jsx(ChevronRight, {})
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
] }),
|
|
99
|
+
/* @__PURE__ */ jsx("div", { className: "hj-divider" }),
|
|
100
|
+
/* @__PURE__ */ jsx(
|
|
101
|
+
"select",
|
|
102
|
+
{
|
|
103
|
+
className: "hj-select",
|
|
104
|
+
value: zoomValue,
|
|
105
|
+
onChange: (e) => onChangeZoom(e.target.value),
|
|
106
|
+
"aria-label": "Zoom Level",
|
|
107
|
+
children: ZOOM_OPTIONS.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
] }) });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/constants.ts
|
|
114
|
+
var PAGE_HEIGHT = 842;
|
|
115
|
+
var PAGE_WIDTH = 595;
|
|
116
|
+
|
|
117
|
+
// src/error-page.tsx
|
|
118
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
119
|
+
function PDFErrorPage() {
|
|
120
|
+
return /* @__PURE__ */ jsx2(
|
|
121
|
+
"div",
|
|
122
|
+
{
|
|
123
|
+
className: "hj-error",
|
|
124
|
+
style: { width: PAGE_WIDTH, height: PAGE_HEIGHT },
|
|
125
|
+
children: "Error loading file"
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/loading-page.tsx
|
|
131
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
132
|
+
function PDFLoadingPage() {
|
|
133
|
+
return /* @__PURE__ */ jsx3(
|
|
134
|
+
"div",
|
|
135
|
+
{
|
|
136
|
+
className: "hj-loading",
|
|
137
|
+
style: { width: PAGE_WIDTH, height: PAGE_HEIGHT },
|
|
138
|
+
children: /* @__PURE__ */ jsx3("div", { className: "hj-spinner" })
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/renderer.tsx
|
|
144
|
+
import { Page } from "react-pdf";
|
|
145
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
146
|
+
function PDFPageRenderer(props) {
|
|
147
|
+
const { index, style, scale } = props;
|
|
148
|
+
return /* @__PURE__ */ jsx4("div", { className: "hj-page", style, children: /* @__PURE__ */ jsx4(Page, { pageIndex: index, scale, loading: PDFLoadingPage }) });
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/use-element-size.ts
|
|
152
|
+
import { useCallback, useRef, useState } from "react";
|
|
153
|
+
function useElementSize() {
|
|
154
|
+
const [size, setSize] = useState({ width: 0, height: 0 });
|
|
155
|
+
const observerRef = useRef(null);
|
|
156
|
+
const ref = useCallback((node) => {
|
|
157
|
+
if (observerRef.current) {
|
|
158
|
+
observerRef.current.disconnect();
|
|
159
|
+
observerRef.current = null;
|
|
160
|
+
}
|
|
161
|
+
if (node) {
|
|
162
|
+
const observer = new ResizeObserver(([entry]) => {
|
|
163
|
+
const { width, height } = entry.contentRect;
|
|
164
|
+
setSize((prev) => {
|
|
165
|
+
if (prev.width === width && prev.height === height) return prev;
|
|
166
|
+
return { width, height };
|
|
167
|
+
});
|
|
168
|
+
});
|
|
169
|
+
observer.observe(node);
|
|
170
|
+
observerRef.current = observer;
|
|
171
|
+
}
|
|
172
|
+
}, []);
|
|
173
|
+
return { ref, width: size.width, height: size.height };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// src/utils.ts
|
|
177
|
+
async function getPageDimensions(document, scale) {
|
|
178
|
+
const dims = [];
|
|
179
|
+
for (let i = 1; i <= document.numPages; i++) {
|
|
180
|
+
try {
|
|
181
|
+
const page = await document.getPage(i);
|
|
182
|
+
const viewport = page.getViewport({ scale });
|
|
183
|
+
const { height, width } = viewport;
|
|
184
|
+
dims.push({ height, width });
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error("Failed to get page dimensions", error);
|
|
187
|
+
dims.push({
|
|
188
|
+
width: PAGE_WIDTH,
|
|
189
|
+
height: PAGE_HEIGHT
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return dims;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/viewer.tsx
|
|
197
|
+
import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
198
|
+
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
|
|
199
|
+
"pdfjs-dist/build/pdf.worker.min.mjs",
|
|
200
|
+
import.meta.url
|
|
201
|
+
).toString();
|
|
202
|
+
var PAGE_MARGIN = 12;
|
|
203
|
+
function HyperJumpViewer(props) {
|
|
204
|
+
const { url, page, onPageChange } = props;
|
|
205
|
+
const [document, setDocument] = useState2();
|
|
206
|
+
const [pageIndex, setPageIndex] = useState2(0);
|
|
207
|
+
const [pageDimensions, setPageDimensions] = useState2([]);
|
|
208
|
+
const [zoomConfig, setZoomConfig] = useState2({
|
|
209
|
+
mode: "automatic",
|
|
210
|
+
value: 1
|
|
211
|
+
});
|
|
212
|
+
const scrollPageRef = useRef2(0);
|
|
213
|
+
const { ref: containerRef } = useElementSize();
|
|
214
|
+
const listRef = useRef2(null);
|
|
215
|
+
const numPages = useMemo(() => {
|
|
216
|
+
return document?.numPages || 0;
|
|
217
|
+
}, [document]);
|
|
218
|
+
useEffect(() => {
|
|
219
|
+
if (document) {
|
|
220
|
+
getPageDimensions(document, zoomConfig.value).then((value) => {
|
|
221
|
+
setPageDimensions(value);
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}, [document, zoomConfig]);
|
|
225
|
+
const scrollToPage = useCallback2((index) => {
|
|
226
|
+
listRef.current?.scrollToRow({ index, align: "start" });
|
|
227
|
+
setPageIndex(index);
|
|
228
|
+
}, []);
|
|
229
|
+
const onLoadSuccess = useCallback2((response) => {
|
|
230
|
+
setDocument(response);
|
|
231
|
+
}, []);
|
|
232
|
+
useEffect(() => {
|
|
233
|
+
if (page !== void 0 && pageDimensions.length === numPages && numPages > 0) {
|
|
234
|
+
scrollToPage(page);
|
|
235
|
+
}
|
|
236
|
+
}, [page, pageDimensions, numPages, scrollToPage]);
|
|
237
|
+
const file = useMemo(() => {
|
|
238
|
+
return { url };
|
|
239
|
+
}, [url]);
|
|
240
|
+
const onPrevPage = useCallback2(() => {
|
|
241
|
+
if (pageIndex > 0) {
|
|
242
|
+
const newPageIndex = pageIndex - 1;
|
|
243
|
+
listRef.current?.scrollToRow({ index: newPageIndex, align: "start" });
|
|
244
|
+
setPageIndex(newPageIndex);
|
|
245
|
+
}
|
|
246
|
+
}, [pageIndex]);
|
|
247
|
+
const onNextPage = useCallback2(() => {
|
|
248
|
+
if (pageIndex < numPages - 1) {
|
|
249
|
+
const newPageIndex = pageIndex + 1;
|
|
250
|
+
listRef.current?.scrollToRow({ index: newPageIndex, align: "start" });
|
|
251
|
+
setPageIndex(newPageIndex);
|
|
252
|
+
}
|
|
253
|
+
}, [pageIndex, numPages]);
|
|
254
|
+
const onChangeZoom = useCallback2((value) => {
|
|
255
|
+
if (value === "automatic") {
|
|
256
|
+
setZoomConfig({ mode: "automatic", value: 1 });
|
|
257
|
+
} else {
|
|
258
|
+
setZoomConfig({ mode: "manual", value: Number.parseFloat(value) });
|
|
259
|
+
}
|
|
260
|
+
}, []);
|
|
261
|
+
const getItemSize = useCallback2(
|
|
262
|
+
(index) => {
|
|
263
|
+
if (pageDimensions[index]) {
|
|
264
|
+
return pageDimensions[index].height + PAGE_MARGIN;
|
|
265
|
+
}
|
|
266
|
+
return 0;
|
|
267
|
+
},
|
|
268
|
+
[pageDimensions]
|
|
269
|
+
);
|
|
270
|
+
const onRowsRendered = useCallback2(
|
|
271
|
+
(visibleRows) => {
|
|
272
|
+
const prev = scrollPageRef.current;
|
|
273
|
+
scrollPageRef.current = visibleRows.startIndex;
|
|
274
|
+
if (visibleRows.startIndex !== prev) {
|
|
275
|
+
setPageIndex(visibleRows.startIndex);
|
|
276
|
+
onPageChange?.(visibleRows.startIndex);
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
[onPageChange]
|
|
280
|
+
);
|
|
281
|
+
return /* @__PURE__ */ jsxs2("div", { className: "hj-viewer", ref: containerRef, children: [
|
|
282
|
+
file ? /* @__PURE__ */ jsx5(
|
|
283
|
+
Document,
|
|
284
|
+
{
|
|
285
|
+
file,
|
|
286
|
+
onLoadSuccess,
|
|
287
|
+
error: PDFErrorPage,
|
|
288
|
+
loading: PDFLoadingPage,
|
|
289
|
+
children: pageDimensions.length > 0 && pageDimensions.length === numPages && /* @__PURE__ */ jsx5(
|
|
290
|
+
List,
|
|
291
|
+
{
|
|
292
|
+
listRef,
|
|
293
|
+
rowCount: numPages,
|
|
294
|
+
rowHeight: getItemSize,
|
|
295
|
+
onRowsRendered,
|
|
296
|
+
rowProps: { scale: zoomConfig.value },
|
|
297
|
+
rowComponent: PDFPageRenderer
|
|
298
|
+
}
|
|
299
|
+
)
|
|
300
|
+
}
|
|
301
|
+
) : /* @__PURE__ */ jsx5(PDFLoadingPage, {}),
|
|
302
|
+
/* @__PURE__ */ jsx5(
|
|
303
|
+
PDFViewerControls,
|
|
304
|
+
{
|
|
305
|
+
pageIndex,
|
|
306
|
+
numPages,
|
|
307
|
+
onPrevPage,
|
|
308
|
+
onNextPage,
|
|
309
|
+
zoomConfig,
|
|
310
|
+
onChangeZoom
|
|
311
|
+
}
|
|
312
|
+
)
|
|
313
|
+
] });
|
|
314
|
+
}
|
|
315
|
+
export {
|
|
316
|
+
HyperJumpViewer
|
|
317
|
+
};
|
|
318
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/viewer.tsx","../src/controls.tsx","../src/constants.ts","../src/error-page.tsx","../src/loading-page.tsx","../src/renderer.tsx","../src/use-element-size.ts","../src/utils.ts"],"sourcesContent":["import type { PDFDocumentProxy } from \"pdfjs-dist\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { Document, pdfjs } from \"react-pdf\";\nimport \"react-pdf/dist/Page/AnnotationLayer.css\";\nimport \"react-pdf/dist/Page/TextLayer.css\";\nimport type { OnDocumentLoadSuccess } from \"react-pdf/dist/shared/types.js\";\nimport { List, type ListImperativeAPI } from \"react-window\";\nimport PDFViewerControls from \"./controls\";\nimport PDFErrorPage from \"./error-page\";\nimport PDFLoadingPage from \"./loading-page\";\nimport PDFPageRenderer from \"./renderer\";\nimport \"./styles.css\";\nimport type { ZoomConfig } from \"./types\";\nimport { useElementSize } from \"./use-element-size\";\nimport { getPageDimensions } from \"./utils\";\n\npdfjs.GlobalWorkerOptions.workerSrc = new URL(\n\t\"pdfjs-dist/build/pdf.worker.min.mjs\",\n\timport.meta.url,\n).toString();\n\nconst PAGE_MARGIN = 12;\n\nexport interface HyperJumpViewerProps {\n\t/** URL of the PDF file to display */\n\turl: string;\n\t/** Page number to jump to (0-indexed) */\n\tpage?: number;\n\t/** Called when the visible page changes (0-indexed) */\n\tonPageChange?: (page: number) => void;\n}\n\nexport function HyperJumpViewer(props: HyperJumpViewerProps) {\n\tconst { url, page, onPageChange } = props;\n\tconst [document, setDocument] = useState<PDFDocumentProxy>();\n\tconst [pageIndex, setPageIndex] = useState(0);\n\tconst [pageDimensions, setPageDimensions] = useState<\n\t\t{ width: number; height: number }[]\n\t>([]);\n\tconst [zoomConfig, setZoomConfig] = useState<ZoomConfig>({\n\t\tmode: \"automatic\",\n\t\tvalue: 1,\n\t});\n\tconst scrollPageRef = useRef(0);\n\n\tconst { ref: containerRef } = useElementSize();\n\tconst listRef = useRef<ListImperativeAPI>(null);\n\n\tconst numPages = useMemo(() => {\n\t\treturn document?.numPages || 0;\n\t}, [document]);\n\n\tuseEffect(() => {\n\t\tif (document) {\n\t\t\tgetPageDimensions(document, zoomConfig.value).then((value) => {\n\t\t\t\tsetPageDimensions(value);\n\t\t\t});\n\t\t}\n\t}, [document, zoomConfig]);\n\n\tconst scrollToPage = useCallback((index: number) => {\n\t\tlistRef.current?.scrollToRow({ index, align: \"start\" });\n\t\tsetPageIndex(index);\n\t}, []);\n\n\tconst onLoadSuccess: OnDocumentLoadSuccess = useCallback((response) => {\n\t\tsetDocument(response);\n\t}, []);\n\n\tuseEffect(() => {\n\t\tif (\n\t\t\tpage !== undefined &&\n\t\t\tpageDimensions.length === numPages &&\n\t\t\tnumPages > 0\n\t\t) {\n\t\t\tscrollToPage(page);\n\t\t}\n\t}, [page, pageDimensions, numPages, scrollToPage]);\n\n\tconst file = useMemo(() => {\n\t\treturn { url };\n\t}, [url]);\n\n\tconst onPrevPage = useCallback(() => {\n\t\tif (pageIndex > 0) {\n\t\t\tconst newPageIndex = pageIndex - 1;\n\t\t\tlistRef.current?.scrollToRow({ index: newPageIndex, align: \"start\" });\n\t\t\tsetPageIndex(newPageIndex);\n\t\t}\n\t}, [pageIndex]);\n\n\tconst onNextPage = useCallback(() => {\n\t\tif (pageIndex < numPages - 1) {\n\t\t\tconst newPageIndex = pageIndex + 1;\n\t\t\tlistRef.current?.scrollToRow({ index: newPageIndex, align: \"start\" });\n\t\t\tsetPageIndex(newPageIndex);\n\t\t}\n\t}, [pageIndex, numPages]);\n\n\tconst onChangeZoom = useCallback((value: string) => {\n\t\tif (value === \"automatic\") {\n\t\t\tsetZoomConfig({ mode: \"automatic\", value: 1 });\n\t\t} else {\n\t\t\tsetZoomConfig({ mode: \"manual\", value: Number.parseFloat(value) });\n\t\t}\n\t}, []);\n\n\tconst getItemSize = useCallback(\n\t\t(index: number) => {\n\t\t\tif (pageDimensions[index]) {\n\t\t\t\treturn pageDimensions[index].height + PAGE_MARGIN;\n\t\t\t}\n\t\t\treturn 0;\n\t\t},\n\t\t[pageDimensions],\n\t);\n\n\tconst onRowsRendered = useCallback(\n\t\t(visibleRows: { startIndex: number; stopIndex: number }) => {\n\t\t\tconst prev = scrollPageRef.current;\n\t\t\tscrollPageRef.current = visibleRows.startIndex;\n\t\t\tif (visibleRows.startIndex !== prev) {\n\t\t\t\tsetPageIndex(visibleRows.startIndex);\n\t\t\t\tonPageChange?.(visibleRows.startIndex);\n\t\t\t}\n\t\t},\n\t\t[onPageChange],\n\t);\n\n\treturn (\n\t\t<div className=\"hj-viewer\" ref={containerRef}>\n\t\t\t{file ? (\n\t\t\t\t<Document\n\t\t\t\t\tfile={file}\n\t\t\t\t\tonLoadSuccess={onLoadSuccess}\n\t\t\t\t\terror={PDFErrorPage}\n\t\t\t\t\tloading={PDFLoadingPage}\n\t\t\t\t>\n\t\t\t\t\t{pageDimensions.length > 0 && pageDimensions.length === numPages && (\n\t\t\t\t\t\t<List\n\t\t\t\t\t\t\tlistRef={listRef}\n\t\t\t\t\t\t\trowCount={numPages}\n\t\t\t\t\t\t\trowHeight={getItemSize}\n\t\t\t\t\t\t\tonRowsRendered={onRowsRendered}\n\t\t\t\t\t\t\trowProps={{ scale: zoomConfig.value }}\n\t\t\t\t\t\t\trowComponent={PDFPageRenderer}\n\t\t\t\t\t\t/>\n\t\t\t\t\t)}\n\t\t\t\t</Document>\n\t\t\t) : (\n\t\t\t\t<PDFLoadingPage />\n\t\t\t)}\n\t\t\t<PDFViewerControls\n\t\t\t\tpageIndex={pageIndex}\n\t\t\t\tnumPages={numPages}\n\t\t\t\tonPrevPage={onPrevPage}\n\t\t\t\tonNextPage={onNextPage}\n\t\t\t\tzoomConfig={zoomConfig}\n\t\t\t\tonChangeZoom={onChangeZoom}\n\t\t\t/>\n\t\t</div>\n\t);\n}\n","import type { ZoomConfig } from \"./types\";\n\nconst ZOOM_OPTIONS = [\n\t{ label: \"Auto Width\", value: \"automatic\" },\n\t{ label: \"50%\", value: \"0.5\" },\n\t{ label: \"75%\", value: \"0.75\" },\n\t{ label: \"100%\", value: \"1\" },\n\t{ label: \"125%\", value: \"1.25\" },\n\t{ label: \"150%\", value: \"1.5\" },\n\t{ label: \"200%\", value: \"2\" },\n\t{ label: \"300%\", value: \"3\" },\n\t{ label: \"400%\", value: \"4\" },\n];\n\ninterface IProps {\n\tonChangeZoom(value: string): void;\n\tpageIndex: number;\n\tnumPages: number;\n\tzoomConfig: ZoomConfig;\n\tonNextPage(): void;\n\tonPrevPage(): void;\n}\n\nfunction ChevronLeft() {\n\treturn (\n\t\t<svg\n\t\t\twidth=\"16\"\n\t\t\theight=\"16\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t>\n\t\t\t<title>Previous Page</title>\n\t\t\t<path d=\"M15 18l-6-6 6-6\" />\n\t\t</svg>\n\t);\n}\n\nfunction ChevronRight() {\n\treturn (\n\t\t<svg\n\t\t\twidth=\"16\"\n\t\t\theight=\"16\"\n\t\t\tviewBox=\"0 0 24 24\"\n\t\t\tfill=\"none\"\n\t\t\tstroke=\"currentColor\"\n\t\t\tstrokeWidth=\"2\"\n\t\t\tstrokeLinecap=\"round\"\n\t\t\tstrokeLinejoin=\"round\"\n\t\t>\n\t\t\t<title>Next Page</title>\n\t\t\t<path d=\"M9 18l6-6-6 6\" />\n\t\t</svg>\n\t);\n}\n\nexport default function PDFViewerControls(props: IProps) {\n\tconst {\n\t\tonChangeZoom,\n\t\tpageIndex,\n\t\tnumPages,\n\t\tzoomConfig,\n\t\tonNextPage,\n\t\tonPrevPage,\n\t} = props;\n\n\tconst zoomValue =\n\t\tzoomConfig.mode === \"automatic\" || zoomConfig.mode === \"page-width\"\n\t\t\t? zoomConfig.mode\n\t\t\t: zoomConfig.value.toString();\n\n\treturn (\n\t\t<div className=\"hj-controls\">\n\t\t\t<div className=\"hj-controls-bar\">\n\t\t\t\t<div className=\"hj-controls-group\">\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"hj-icon-btn\"\n\t\t\t\t\t\tonClick={onPrevPage}\n\t\t\t\t\t\tdisabled={pageIndex <= 0}\n\t\t\t\t\t\taria-label=\"Previous Page\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<ChevronLeft />\n\t\t\t\t\t</button>\n\t\t\t\t\t<span className=\"hj-page-indicator\">\n\t\t\t\t\t\t{pageIndex + 1} / {numPages}\n\t\t\t\t\t</span>\n\t\t\t\t\t<button\n\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\tclassName=\"hj-icon-btn\"\n\t\t\t\t\t\tonClick={onNextPage}\n\t\t\t\t\t\tdisabled={pageIndex >= numPages - 1}\n\t\t\t\t\t\taria-label=\"Next Page\"\n\t\t\t\t\t>\n\t\t\t\t\t\t<ChevronRight />\n\t\t\t\t\t</button>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"hj-divider\" />\n\t\t\t\t<select\n\t\t\t\t\tclassName=\"hj-select\"\n\t\t\t\t\tvalue={zoomValue}\n\t\t\t\t\tonChange={(e) => onChangeZoom(e.target.value)}\n\t\t\t\t\taria-label=\"Zoom Level\"\n\t\t\t\t>\n\t\t\t\t\t{ZOOM_OPTIONS.map((opt) => (\n\t\t\t\t\t\t<option key={opt.value} value={opt.value}>\n\t\t\t\t\t\t\t{opt.label}\n\t\t\t\t\t\t</option>\n\t\t\t\t\t))}\n\t\t\t\t</select>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n","export const PAGE_HEIGHT = 842;\nexport const PAGE_WIDTH = 595;\n","import { PAGE_HEIGHT, PAGE_WIDTH } from \"./constants\";\n\nexport default function PDFErrorPage() {\n\treturn (\n\t\t<div\n\t\t\tclassName=\"hj-error\"\n\t\t\tstyle={{ width: PAGE_WIDTH, height: PAGE_HEIGHT }}\n\t\t>\n\t\t\tError loading file\n\t\t</div>\n\t);\n}\n","import { PAGE_HEIGHT, PAGE_WIDTH } from \"./constants\";\n\nexport default function PDFLoadingPage() {\n\treturn (\n\t\t<div\n\t\t\tclassName=\"hj-loading\"\n\t\t\tstyle={{ width: PAGE_WIDTH, height: PAGE_HEIGHT }}\n\t\t>\n\t\t\t<div className=\"hj-spinner\" />\n\t\t</div>\n\t);\n}\n","import { Page } from \"react-pdf\";\nimport type { RowComponentProps } from \"react-window\";\nimport PDFLoadingPage from \"./loading-page\";\n\ninterface RowProps {\n\tscale: number;\n}\n\nexport default function PDFPageRenderer(props: RowComponentProps<RowProps>) {\n\tconst { index, style, scale } = props;\n\treturn (\n\t\t<div className=\"hj-page\" style={style}>\n\t\t\t<Page pageIndex={index} scale={scale} loading={PDFLoadingPage} />\n\t\t</div>\n\t);\n}\n","import { useCallback, useRef, useState } from \"react\";\n\nexport function useElementSize<T extends HTMLElement = HTMLDivElement>() {\n\tconst [size, setSize] = useState({ width: 0, height: 0 });\n\tconst observerRef = useRef<ResizeObserver | null>(null);\n\n\tconst ref = useCallback((node: T | null) => {\n\t\tif (observerRef.current) {\n\t\t\tobserverRef.current.disconnect();\n\t\t\tobserverRef.current = null;\n\t\t}\n\n\t\tif (node) {\n\t\t\tconst observer = new ResizeObserver(([entry]) => {\n\t\t\t\tconst { width, height } = entry.contentRect;\n\t\t\t\tsetSize((prev) => {\n\t\t\t\t\tif (prev.width === width && prev.height === height) return prev;\n\t\t\t\t\treturn { width, height };\n\t\t\t\t});\n\t\t\t});\n\t\t\tobserver.observe(node);\n\t\t\tobserverRef.current = observer;\n\t\t}\n\t}, []);\n\n\treturn { ref, width: size.width, height: size.height };\n}\n","import type { PDFDocumentProxy } from \"pdfjs-dist\";\nimport { PAGE_HEIGHT, PAGE_WIDTH } from \"./constants\";\n\nexport async function getPageDimensions(\n\tdocument: PDFDocumentProxy,\n\tscale: number,\n) {\n\tconst dims = [];\n\tfor (let i = 1; i <= document.numPages; i++) {\n\t\ttry {\n\t\t\tconst page = await document.getPage(i);\n\t\t\tconst viewport = page.getViewport({ scale });\n\t\t\tconst { height, width } = viewport;\n\t\t\tdims.push({ height, width });\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Failed to get page dimensions\", error);\n\t\t\tdims.push({\n\t\t\t\twidth: PAGE_WIDTH,\n\t\t\t\theight: PAGE_HEIGHT,\n\t\t\t});\n\t\t}\n\t}\n\treturn dims;\n}\n"],"mappings":"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","names":["useCallback","useRef","useState","jsx","jsx","jsx","jsx","jsxs","useState","useRef","useCallback"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hypercard-ai/hyper-jump",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Document viewer built for RAG",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "HyperCard AI",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/hypercard-ai/hyper-jump.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/hypercard-ai/hyper-jump",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/hypercard-ai/hyper-jump/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pdf",
|
|
17
|
+
"viewer",
|
|
18
|
+
"react",
|
|
19
|
+
"rag",
|
|
20
|
+
"virtualized",
|
|
21
|
+
"react-pdf"
|
|
22
|
+
],
|
|
23
|
+
"type": "module",
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./styles.css": "./dist/index.css"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE"
|
|
37
|
+
],
|
|
38
|
+
"sideEffects": false,
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"dev": "tsup --watch",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest",
|
|
47
|
+
"lint": "eslint src/",
|
|
48
|
+
"prepublishOnly": "npm run build",
|
|
49
|
+
"size": "size-limit",
|
|
50
|
+
"storybook": "storybook dev -p 6006",
|
|
51
|
+
"build-storybook": "storybook build",
|
|
52
|
+
"check": "npx @biomejs/biome check --write ./"
|
|
53
|
+
},
|
|
54
|
+
"peerDependencies": {
|
|
55
|
+
"react": ">=19.0.0",
|
|
56
|
+
"react-dom": ">=19.0.0"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@biomejs/biome": "2.3.15",
|
|
60
|
+
"@storybook/addon-docs": "^10.2.8",
|
|
61
|
+
"@storybook/react-vite": "^10.2.8",
|
|
62
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
63
|
+
"@testing-library/react": "^16.3.2",
|
|
64
|
+
"@types/react": "^19.2.14",
|
|
65
|
+
"@types/react-dom": "^19.2.3",
|
|
66
|
+
"jsdom": "^28.0.0",
|
|
67
|
+
"react": "^19.2.4",
|
|
68
|
+
"react-dom": "^19.2.4",
|
|
69
|
+
"storybook": "^10.2.8",
|
|
70
|
+
"tsup": "^8.5.1",
|
|
71
|
+
"typescript": "^5.9.3",
|
|
72
|
+
"vitest": "^4.0.18"
|
|
73
|
+
},
|
|
74
|
+
"dependencies": {
|
|
75
|
+
"react-pdf": "10.3.0",
|
|
76
|
+
"react-window": "^2.2.7"
|
|
77
|
+
}
|
|
78
|
+
}
|