reflex-pdfslick 0.0.1__tar.gz
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.
- reflex_pdfslick-0.0.1/LICENSE +21 -0
- reflex_pdfslick-0.0.1/PKG-INFO +262 -0
- reflex_pdfslick-0.0.1/README.md +232 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/PdfSlickWrapper.tsx +268 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/__init__.py +31 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/commands.py +98 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/models.py +47 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/options.py +41 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/pdfslick.py +80 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/pdfslick.pyi +113 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick.egg-info/PKG-INFO +262 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick.egg-info/SOURCES.txt +22 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick.egg-info/dependency_links.txt +1 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick.egg-info/requires.txt +6 -0
- reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick.egg-info/top_level.txt +1 -0
- reflex_pdfslick-0.0.1/pyproject.toml +63 -0
- reflex_pdfslick-0.0.1/setup.cfg +4 -0
- reflex_pdfslick-0.0.1/tests/test_commands.py +63 -0
- reflex_pdfslick-0.0.1/tests/test_component.py +63 -0
- reflex_pdfslick-0.0.1/tests/test_demo.py +45 -0
- reflex_pdfslick-0.0.1/tests/test_events.py +34 -0
- reflex_pdfslick-0.0.1/tests/test_imports.py +31 -0
- reflex_pdfslick-0.0.1/tests/test_models.py +39 -0
- reflex_pdfslick-0.0.1/tests/test_thumbnails.py +31 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ernesto Crespo
|
|
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.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: reflex-pdfslick
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A native Reflex component wrapping PDFSlick (PDF.js + Zustand) to view and interact with PDF documents in pure Python.
|
|
5
|
+
Author-email: Ernesto Crespo <ecrespo@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/ecrespo/reflex-pdfslick
|
|
8
|
+
Project-URL: Repository, https://github.com/ecrespo/reflex-pdfslick
|
|
9
|
+
Project-URL: Issues, https://github.com/ecrespo/reflex-pdfslick/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/ecrespo/reflex-pdfslick#readme
|
|
11
|
+
Project-URL: Changelog, https://github.com/ecrespo/reflex-pdfslick/releases
|
|
12
|
+
Project-URL: PyPI, https://pypi.org/project/reflex-pdfslick/
|
|
13
|
+
Project-URL: PDFSlick, https://pdfslick.dev/
|
|
14
|
+
Keywords: reflex,reflex-custom-components,pdf,pdfslick,pdfjs,pdf-viewer,component,viewer,thumbnails
|
|
15
|
+
Classifier: Development Status :: 3 - Alpha
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Framework :: AsyncIO
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: reflex>=0.9.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: build; extra == "dev"
|
|
28
|
+
Requires-Dist: twine; extra == "dev"
|
|
29
|
+
Dynamic: license-file
|
|
30
|
+
|
|
31
|
+
# reflex-pdfslick
|
|
32
|
+
|
|
33
|
+
[](https://pypi.org/project/reflex-pdfslick/)
|
|
34
|
+
[](https://pypi.org/project/reflex-pdfslick/)
|
|
35
|
+
[](https://github.com/ecrespo/reflex-pdfslick/blob/main/LICENSE)
|
|
36
|
+
[](https://github.com/ecrespo/reflex-pdfslick)
|
|
37
|
+
|
|
38
|
+
- **Source:** https://github.com/ecrespo/reflex-pdfslick
|
|
39
|
+
- **Issues:** https://github.com/ecrespo/reflex-pdfslick/issues
|
|
40
|
+
- **PDFSlick (upstream):** https://github.com/pdfslick/pdfslick — https://pdfslick.dev
|
|
41
|
+
|
|
42
|
+
A native [Reflex](https://reflex.dev/) component that wraps
|
|
43
|
+
[**PDFSlick**](https://pdfslick.dev/) so you can view and interact with PDF
|
|
44
|
+
documents in **pure Python** — no HTML, no `iframe`, no hand-written
|
|
45
|
+
JavaScript.
|
|
46
|
+
|
|
47
|
+
PDFSlick is built on top of Mozilla's [PDF.js](https://github.com/mozilla/pdf.js)
|
|
48
|
+
and uses [Zustand](https://github.com/pmndrs/zustand) to expose a reactive store
|
|
49
|
+
for the loaded document. `reflex-pdfslick` bridges that store and PDFSlick's
|
|
50
|
+
imperative API into Reflex's declarative, state-driven model: document state
|
|
51
|
+
(page number, page count, zoom, rotation, metadata, thumbnails) flows back to
|
|
52
|
+
Python as events, and Python triggers actions (go to page, zoom, rotate, print,
|
|
53
|
+
download) on the viewer.
|
|
54
|
+
|
|
55
|
+
> **Project status — `ALPHA` / implemented, pre-release.**
|
|
56
|
+
> The component runtime, imperative controls, and the full 7-example demo
|
|
57
|
+
> gallery are built and covered by tests; the demo compiles to a Reflex
|
|
58
|
+
> production build in CI. The remaining step is the PyPI/GitHub release. See
|
|
59
|
+
> [Roadmap](#roadmap) for details.
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
import reflex as rx
|
|
63
|
+
from reflex_pdfslick import pdf_slick
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class State(rx.State):
|
|
67
|
+
page_number: int = 1
|
|
68
|
+
num_pages: int = 0
|
|
69
|
+
|
|
70
|
+
@rx.event
|
|
71
|
+
def on_page_change(self, page: int):
|
|
72
|
+
self.page_number = page
|
|
73
|
+
|
|
74
|
+
@rx.event
|
|
75
|
+
def on_document_load(self, num_pages: int):
|
|
76
|
+
self.num_pages = num_pages
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def index() -> rx.Component:
|
|
80
|
+
return pdf_slick(
|
|
81
|
+
url="/sample.pdf",
|
|
82
|
+
scale_value="page-fit",
|
|
83
|
+
single_page_viewer=True,
|
|
84
|
+
on_page_change=State.on_page_change,
|
|
85
|
+
on_document_load=State.on_document_load,
|
|
86
|
+
height="700px",
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
app = rx.App()
|
|
91
|
+
app.add_page(index)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Why a native component instead of an `iframe`?
|
|
95
|
+
|
|
96
|
+
Embedding a PDF viewer through an `iframe` or a raw `<embed>` works, but it is
|
|
97
|
+
isolated from your app: you cannot react to page changes, drive the zoom from a
|
|
98
|
+
Python toolbar, or read the document's metadata into your state. Wrapping
|
|
99
|
+
PDFSlick as a first-class Reflex component means:
|
|
100
|
+
|
|
101
|
+
- **Reactive state** — `page_number`, `num_pages`, `scale`, `rotation`,
|
|
102
|
+
`is_document_loaded` and document metadata are pushed into Reflex state via
|
|
103
|
+
typed events.
|
|
104
|
+
- **Imperative control from Python** — go to page, zoom in/out, fit width/page,
|
|
105
|
+
rotate, switch scroll/spread modes, print and download are exposed as
|
|
106
|
+
component-level controls.
|
|
107
|
+
- **Reusable & versioned** — installable with `pip`, with the npm dependency
|
|
108
|
+
(`@pdfslick/react`) pinned for reproducible builds.
|
|
109
|
+
- **SSR-safe** — PDF.js is browser-only, so the component is wrapped as a
|
|
110
|
+
Reflex `NoSSRComponent` and only ever renders on the client.
|
|
111
|
+
|
|
112
|
+
## Installation
|
|
113
|
+
|
|
114
|
+
```shell
|
|
115
|
+
pip install reflex-pdfslick
|
|
116
|
+
# or, from source
|
|
117
|
+
git clone https://github.com/ecrespo/reflex-pdfslick.git
|
|
118
|
+
cd reflex-pdfslick
|
|
119
|
+
pip install -e .
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Reflex installs the underlying npm packages automatically on first run. The
|
|
123
|
+
component pins `@pdfslick/react` and `pdfjs-dist` (which pull in
|
|
124
|
+
`@pdfslick/core` and `zustand`) and imports the required stylesheet
|
|
125
|
+
`@pdfslick/react/dist/pdf_viewer.css` for you.
|
|
126
|
+
|
|
127
|
+
## Custom-component packaging (build & publish)
|
|
128
|
+
|
|
129
|
+
This repository is a standard [Reflex custom
|
|
130
|
+
component](https://reflex.dev/docs/custom-components/overview/): the package
|
|
131
|
+
lives in `custom_components/reflex_pdfslick/` (shipping the `PdfSlickWrapper.tsx`
|
|
132
|
+
wrapper and a generated `pdfslick.pyi` stub), the demo app in `pdfslick_demo/`,
|
|
133
|
+
and `pyproject.toml` carries the `reflex-custom-components` keyword used by the
|
|
134
|
+
Reflex component gallery to discover it on PyPI.
|
|
135
|
+
|
|
136
|
+
```shell
|
|
137
|
+
# Build the wheel + sdist into dist/ (also regenerates the .pyi type stubs).
|
|
138
|
+
# PYTHONPATH=. lets the stub generator import custom_components/.
|
|
139
|
+
PYTHONPATH=. uv run reflex component build
|
|
140
|
+
|
|
141
|
+
# Inspect, then publish to PyPI (bump the version in pyproject.toml first):
|
|
142
|
+
uv run twine upload dist/* # or: uv publish
|
|
143
|
+
|
|
144
|
+
# Share on the Reflex community gallery:
|
|
145
|
+
uv run reflex component share
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The built wheel includes the React wrapper (`reflex_pdfslick/PdfSlickWrapper.tsx`)
|
|
149
|
+
and the type stub, so `rx.asset(...)` resolves the wrapper when the package is
|
|
150
|
+
installed from PyPI — not only in editable mode.
|
|
151
|
+
|
|
152
|
+
## Planned API (summary)
|
|
153
|
+
|
|
154
|
+
The full contract lives in [specs/api/component-api.md](specs/api/component-api.md).
|
|
155
|
+
|
|
156
|
+
| Prop | Type | Description |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `url` | `str` | Path/URL of the PDF to load. |
|
|
159
|
+
| `scale_value` | `str` | `"auto"`, `"page-fit"`, `"page-width"`, `"page-actual"` or a numeric string. |
|
|
160
|
+
| `single_page_viewer` | `bool` | Render a single page at a time. |
|
|
161
|
+
| `thumbnail_width` | `int` | Width (px) of generated thumbnails. |
|
|
162
|
+
| `remove_page_borders` | `bool` | Hide page borders. |
|
|
163
|
+
|
|
164
|
+
| Event | Payload | Fired when |
|
|
165
|
+
|---|---|---|
|
|
166
|
+
| `on_document_load` | `int` (num pages) | The document finishes loading. |
|
|
167
|
+
| `on_page_change` | `int` (page) | The current page changes. |
|
|
168
|
+
| `on_scale_change` | `float` (scale) | The zoom level changes. |
|
|
169
|
+
| `on_error` | `dict` | Loading/parsing fails. |
|
|
170
|
+
|
|
171
|
+
Imperative controls (next/previous page, zoom, rotate, print, download) are
|
|
172
|
+
driven from Python state — see the API spec for the dispatch mechanism.
|
|
173
|
+
|
|
174
|
+
## Examples (demo gallery)
|
|
175
|
+
|
|
176
|
+
The `pdfslick_demo/` Reflex app mirrors PDFSlick's own example set. Each example
|
|
177
|
+
is documented in the [PRD](specs/prd/pdfslick-component.md) and
|
|
178
|
+
[task breakdown](specs/tasks/task-breakdown.md):
|
|
179
|
+
|
|
180
|
+
1. **Simple Viewer** — basic viewer with prev/next navigation and zoom.
|
|
181
|
+
2. **Full Viewer App** — toolbar, zoom selector, rotation, scroll/spread modes,
|
|
182
|
+
document info, search and thumbnails sidebar.
|
|
183
|
+
3. **Thumbnails Layout** — a grid of page thumbnails as the primary surface.
|
|
184
|
+
4. **Horizontal Thumbnails** — a horizontal thumbnail strip.
|
|
185
|
+
5. **Multiple Documents** — several independent viewers on one page.
|
|
186
|
+
6. **Load from ArrayBuffer** — open a PDF from in-memory bytes.
|
|
187
|
+
7. **Error Handling** — graceful handling of load/parse errors.
|
|
188
|
+
|
|
189
|
+
Run the demo:
|
|
190
|
+
|
|
191
|
+
```shell
|
|
192
|
+
cd pdfslick_demo
|
|
193
|
+
pip install -r requirements.txt
|
|
194
|
+
reflex run
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Architecture (at a glance)
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
Python (Reflex app)
|
|
201
|
+
pdf_slick(url=..., scale_value=..., on_page_change=...)
|
|
202
|
+
│ compiles to
|
|
203
|
+
▼
|
|
204
|
+
React / Next.js (generated by Reflex)
|
|
205
|
+
<PdfSlickWrapper> ← local .tsx, NoSSR, calls usePDFSlick(url, options)
|
|
206
|
+
│ renders <PDFSlickViewer/> + (optional) thumbnails
|
|
207
|
+
│ subscribes to usePDFSlickStore selectors → emits Reflex events
|
|
208
|
+
▼
|
|
209
|
+
@pdfslick/react → @pdfslick/core → PDF.js (pdfjs-dist) + Zustand store
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Because PDFSlick's public surface is the **hook** `usePDFSlick(...)` (not a
|
|
213
|
+
drop-in component) and its thumbnails component uses a render-prop child, the
|
|
214
|
+
wrapper is a small local `.tsx` component that calls the hook internally and
|
|
215
|
+
exposes a clean prop/event API to Python. Full rationale and trade-offs are in
|
|
216
|
+
[specs/technical/architecture.md](specs/technical/architecture.md).
|
|
217
|
+
|
|
218
|
+
## Spec-Driven Design documentation
|
|
219
|
+
|
|
220
|
+
This project is documented before it is built. The `specs/` directory is the
|
|
221
|
+
source of truth:
|
|
222
|
+
|
|
223
|
+
| Document | Purpose |
|
|
224
|
+
|---|---|
|
|
225
|
+
| [`specs/prd/pdfslick-component.md`](specs/prd/pdfslick-component.md) | Product requirements, goals, scope, examples. |
|
|
226
|
+
| [`specs/technical/architecture.md`](specs/technical/architecture.md) | Technical design, SSR strategy, wrapper pattern. |
|
|
227
|
+
| [`specs/api/component-api.md`](specs/api/component-api.md) | Public Python API: props, events, controls. |
|
|
228
|
+
| [`specs/data-model/store-schema.md`](specs/data-model/store-schema.md) | PDFSlick store schema and Python ↔ JS mapping. |
|
|
229
|
+
| [`specs/plans/implementation-plan.md`](specs/plans/implementation-plan.md) | Phases, milestones, sequencing. |
|
|
230
|
+
| [`specs/tasks/task-breakdown.md`](specs/tasks/task-breakdown.md) | Granular, trackable task list per phase. |
|
|
231
|
+
| [`specs/research/pdfslick-and-reflex-research.md`](specs/research/pdfslick-and-reflex-research.md) | Primary-source research notes that ground the design. |
|
|
232
|
+
|
|
233
|
+
## Roadmap
|
|
234
|
+
|
|
235
|
+
- [x] **Phase 0 — Foundation & SDD**: repository, packaging, SDD artifacts,
|
|
236
|
+
README, demo skeleton.
|
|
237
|
+
- [x] **Phase 1 — Core wrapper**: `PdfSlickWrapper.tsx`, `pdf_slick` component,
|
|
238
|
+
props, CSS import, NoSSR.
|
|
239
|
+
- [x] **Phase 2 — Events & state bridge**: page/scale/load/error/metadata events.
|
|
240
|
+
- [x] **Phase 3 — Imperative controls**: navigation, zoom, rotation, modes,
|
|
241
|
+
print, download (declarative `command` prop + `commands` helpers).
|
|
242
|
+
- [x] **Phase 4 — Thumbnails & multi-document**: vertical/horizontal thumbnails,
|
|
243
|
+
independent multi-instance viewers.
|
|
244
|
+
- [x] **Phase 5 — Demo gallery** (all 7 examples) **& docs polish**.
|
|
245
|
+
- [x] **Phase 6 — Tests & CI**: 35 unit tests, GitHub Actions (pytest 3.10–3.13,
|
|
246
|
+
TSX transpile, demo build smoke). Demo compiles to a Reflex production build.
|
|
247
|
+
- [ ] **Release** — PyPI publish + tagged GitHub release *(awaiting maintainer
|
|
248
|
+
credentials)*.
|
|
249
|
+
|
|
250
|
+
## Credits
|
|
251
|
+
|
|
252
|
+
- [PDFSlick](https://pdfslick.dev/) by
|
|
253
|
+
[Vancho Stojkov](https://github.com/van100j) — the library being wrapped (MIT).
|
|
254
|
+
- [PDF.js](https://github.com/mozilla/pdf.js) by Mozilla.
|
|
255
|
+
- [Reflex](https://reflex.dev/) — the Python web framework.
|
|
256
|
+
|
|
257
|
+
This wrapper is an independent project and is not affiliated with or endorsed by
|
|
258
|
+
the PDFSlick authors.
|
|
259
|
+
|
|
260
|
+
## License
|
|
261
|
+
|
|
262
|
+
[MIT](LICENSE) © Ernesto Crespo
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# reflex-pdfslick
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/reflex-pdfslick/)
|
|
4
|
+
[](https://pypi.org/project/reflex-pdfslick/)
|
|
5
|
+
[](https://github.com/ecrespo/reflex-pdfslick/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/ecrespo/reflex-pdfslick)
|
|
7
|
+
|
|
8
|
+
- **Source:** https://github.com/ecrespo/reflex-pdfslick
|
|
9
|
+
- **Issues:** https://github.com/ecrespo/reflex-pdfslick/issues
|
|
10
|
+
- **PDFSlick (upstream):** https://github.com/pdfslick/pdfslick — https://pdfslick.dev
|
|
11
|
+
|
|
12
|
+
A native [Reflex](https://reflex.dev/) component that wraps
|
|
13
|
+
[**PDFSlick**](https://pdfslick.dev/) so you can view and interact with PDF
|
|
14
|
+
documents in **pure Python** — no HTML, no `iframe`, no hand-written
|
|
15
|
+
JavaScript.
|
|
16
|
+
|
|
17
|
+
PDFSlick is built on top of Mozilla's [PDF.js](https://github.com/mozilla/pdf.js)
|
|
18
|
+
and uses [Zustand](https://github.com/pmndrs/zustand) to expose a reactive store
|
|
19
|
+
for the loaded document. `reflex-pdfslick` bridges that store and PDFSlick's
|
|
20
|
+
imperative API into Reflex's declarative, state-driven model: document state
|
|
21
|
+
(page number, page count, zoom, rotation, metadata, thumbnails) flows back to
|
|
22
|
+
Python as events, and Python triggers actions (go to page, zoom, rotate, print,
|
|
23
|
+
download) on the viewer.
|
|
24
|
+
|
|
25
|
+
> **Project status — `ALPHA` / implemented, pre-release.**
|
|
26
|
+
> The component runtime, imperative controls, and the full 7-example demo
|
|
27
|
+
> gallery are built and covered by tests; the demo compiles to a Reflex
|
|
28
|
+
> production build in CI. The remaining step is the PyPI/GitHub release. See
|
|
29
|
+
> [Roadmap](#roadmap) for details.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import reflex as rx
|
|
33
|
+
from reflex_pdfslick import pdf_slick
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class State(rx.State):
|
|
37
|
+
page_number: int = 1
|
|
38
|
+
num_pages: int = 0
|
|
39
|
+
|
|
40
|
+
@rx.event
|
|
41
|
+
def on_page_change(self, page: int):
|
|
42
|
+
self.page_number = page
|
|
43
|
+
|
|
44
|
+
@rx.event
|
|
45
|
+
def on_document_load(self, num_pages: int):
|
|
46
|
+
self.num_pages = num_pages
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def index() -> rx.Component:
|
|
50
|
+
return pdf_slick(
|
|
51
|
+
url="/sample.pdf",
|
|
52
|
+
scale_value="page-fit",
|
|
53
|
+
single_page_viewer=True,
|
|
54
|
+
on_page_change=State.on_page_change,
|
|
55
|
+
on_document_load=State.on_document_load,
|
|
56
|
+
height="700px",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
app = rx.App()
|
|
61
|
+
app.add_page(index)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Why a native component instead of an `iframe`?
|
|
65
|
+
|
|
66
|
+
Embedding a PDF viewer through an `iframe` or a raw `<embed>` works, but it is
|
|
67
|
+
isolated from your app: you cannot react to page changes, drive the zoom from a
|
|
68
|
+
Python toolbar, or read the document's metadata into your state. Wrapping
|
|
69
|
+
PDFSlick as a first-class Reflex component means:
|
|
70
|
+
|
|
71
|
+
- **Reactive state** — `page_number`, `num_pages`, `scale`, `rotation`,
|
|
72
|
+
`is_document_loaded` and document metadata are pushed into Reflex state via
|
|
73
|
+
typed events.
|
|
74
|
+
- **Imperative control from Python** — go to page, zoom in/out, fit width/page,
|
|
75
|
+
rotate, switch scroll/spread modes, print and download are exposed as
|
|
76
|
+
component-level controls.
|
|
77
|
+
- **Reusable & versioned** — installable with `pip`, with the npm dependency
|
|
78
|
+
(`@pdfslick/react`) pinned for reproducible builds.
|
|
79
|
+
- **SSR-safe** — PDF.js is browser-only, so the component is wrapped as a
|
|
80
|
+
Reflex `NoSSRComponent` and only ever renders on the client.
|
|
81
|
+
|
|
82
|
+
## Installation
|
|
83
|
+
|
|
84
|
+
```shell
|
|
85
|
+
pip install reflex-pdfslick
|
|
86
|
+
# or, from source
|
|
87
|
+
git clone https://github.com/ecrespo/reflex-pdfslick.git
|
|
88
|
+
cd reflex-pdfslick
|
|
89
|
+
pip install -e .
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Reflex installs the underlying npm packages automatically on first run. The
|
|
93
|
+
component pins `@pdfslick/react` and `pdfjs-dist` (which pull in
|
|
94
|
+
`@pdfslick/core` and `zustand`) and imports the required stylesheet
|
|
95
|
+
`@pdfslick/react/dist/pdf_viewer.css` for you.
|
|
96
|
+
|
|
97
|
+
## Custom-component packaging (build & publish)
|
|
98
|
+
|
|
99
|
+
This repository is a standard [Reflex custom
|
|
100
|
+
component](https://reflex.dev/docs/custom-components/overview/): the package
|
|
101
|
+
lives in `custom_components/reflex_pdfslick/` (shipping the `PdfSlickWrapper.tsx`
|
|
102
|
+
wrapper and a generated `pdfslick.pyi` stub), the demo app in `pdfslick_demo/`,
|
|
103
|
+
and `pyproject.toml` carries the `reflex-custom-components` keyword used by the
|
|
104
|
+
Reflex component gallery to discover it on PyPI.
|
|
105
|
+
|
|
106
|
+
```shell
|
|
107
|
+
# Build the wheel + sdist into dist/ (also regenerates the .pyi type stubs).
|
|
108
|
+
# PYTHONPATH=. lets the stub generator import custom_components/.
|
|
109
|
+
PYTHONPATH=. uv run reflex component build
|
|
110
|
+
|
|
111
|
+
# Inspect, then publish to PyPI (bump the version in pyproject.toml first):
|
|
112
|
+
uv run twine upload dist/* # or: uv publish
|
|
113
|
+
|
|
114
|
+
# Share on the Reflex community gallery:
|
|
115
|
+
uv run reflex component share
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
The built wheel includes the React wrapper (`reflex_pdfslick/PdfSlickWrapper.tsx`)
|
|
119
|
+
and the type stub, so `rx.asset(...)` resolves the wrapper when the package is
|
|
120
|
+
installed from PyPI — not only in editable mode.
|
|
121
|
+
|
|
122
|
+
## Planned API (summary)
|
|
123
|
+
|
|
124
|
+
The full contract lives in [specs/api/component-api.md](specs/api/component-api.md).
|
|
125
|
+
|
|
126
|
+
| Prop | Type | Description |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `url` | `str` | Path/URL of the PDF to load. |
|
|
129
|
+
| `scale_value` | `str` | `"auto"`, `"page-fit"`, `"page-width"`, `"page-actual"` or a numeric string. |
|
|
130
|
+
| `single_page_viewer` | `bool` | Render a single page at a time. |
|
|
131
|
+
| `thumbnail_width` | `int` | Width (px) of generated thumbnails. |
|
|
132
|
+
| `remove_page_borders` | `bool` | Hide page borders. |
|
|
133
|
+
|
|
134
|
+
| Event | Payload | Fired when |
|
|
135
|
+
|---|---|---|
|
|
136
|
+
| `on_document_load` | `int` (num pages) | The document finishes loading. |
|
|
137
|
+
| `on_page_change` | `int` (page) | The current page changes. |
|
|
138
|
+
| `on_scale_change` | `float` (scale) | The zoom level changes. |
|
|
139
|
+
| `on_error` | `dict` | Loading/parsing fails. |
|
|
140
|
+
|
|
141
|
+
Imperative controls (next/previous page, zoom, rotate, print, download) are
|
|
142
|
+
driven from Python state — see the API spec for the dispatch mechanism.
|
|
143
|
+
|
|
144
|
+
## Examples (demo gallery)
|
|
145
|
+
|
|
146
|
+
The `pdfslick_demo/` Reflex app mirrors PDFSlick's own example set. Each example
|
|
147
|
+
is documented in the [PRD](specs/prd/pdfslick-component.md) and
|
|
148
|
+
[task breakdown](specs/tasks/task-breakdown.md):
|
|
149
|
+
|
|
150
|
+
1. **Simple Viewer** — basic viewer with prev/next navigation and zoom.
|
|
151
|
+
2. **Full Viewer App** — toolbar, zoom selector, rotation, scroll/spread modes,
|
|
152
|
+
document info, search and thumbnails sidebar.
|
|
153
|
+
3. **Thumbnails Layout** — a grid of page thumbnails as the primary surface.
|
|
154
|
+
4. **Horizontal Thumbnails** — a horizontal thumbnail strip.
|
|
155
|
+
5. **Multiple Documents** — several independent viewers on one page.
|
|
156
|
+
6. **Load from ArrayBuffer** — open a PDF from in-memory bytes.
|
|
157
|
+
7. **Error Handling** — graceful handling of load/parse errors.
|
|
158
|
+
|
|
159
|
+
Run the demo:
|
|
160
|
+
|
|
161
|
+
```shell
|
|
162
|
+
cd pdfslick_demo
|
|
163
|
+
pip install -r requirements.txt
|
|
164
|
+
reflex run
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Architecture (at a glance)
|
|
168
|
+
|
|
169
|
+
```
|
|
170
|
+
Python (Reflex app)
|
|
171
|
+
pdf_slick(url=..., scale_value=..., on_page_change=...)
|
|
172
|
+
│ compiles to
|
|
173
|
+
▼
|
|
174
|
+
React / Next.js (generated by Reflex)
|
|
175
|
+
<PdfSlickWrapper> ← local .tsx, NoSSR, calls usePDFSlick(url, options)
|
|
176
|
+
│ renders <PDFSlickViewer/> + (optional) thumbnails
|
|
177
|
+
│ subscribes to usePDFSlickStore selectors → emits Reflex events
|
|
178
|
+
▼
|
|
179
|
+
@pdfslick/react → @pdfslick/core → PDF.js (pdfjs-dist) + Zustand store
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Because PDFSlick's public surface is the **hook** `usePDFSlick(...)` (not a
|
|
183
|
+
drop-in component) and its thumbnails component uses a render-prop child, the
|
|
184
|
+
wrapper is a small local `.tsx` component that calls the hook internally and
|
|
185
|
+
exposes a clean prop/event API to Python. Full rationale and trade-offs are in
|
|
186
|
+
[specs/technical/architecture.md](specs/technical/architecture.md).
|
|
187
|
+
|
|
188
|
+
## Spec-Driven Design documentation
|
|
189
|
+
|
|
190
|
+
This project is documented before it is built. The `specs/` directory is the
|
|
191
|
+
source of truth:
|
|
192
|
+
|
|
193
|
+
| Document | Purpose |
|
|
194
|
+
|---|---|
|
|
195
|
+
| [`specs/prd/pdfslick-component.md`](specs/prd/pdfslick-component.md) | Product requirements, goals, scope, examples. |
|
|
196
|
+
| [`specs/technical/architecture.md`](specs/technical/architecture.md) | Technical design, SSR strategy, wrapper pattern. |
|
|
197
|
+
| [`specs/api/component-api.md`](specs/api/component-api.md) | Public Python API: props, events, controls. |
|
|
198
|
+
| [`specs/data-model/store-schema.md`](specs/data-model/store-schema.md) | PDFSlick store schema and Python ↔ JS mapping. |
|
|
199
|
+
| [`specs/plans/implementation-plan.md`](specs/plans/implementation-plan.md) | Phases, milestones, sequencing. |
|
|
200
|
+
| [`specs/tasks/task-breakdown.md`](specs/tasks/task-breakdown.md) | Granular, trackable task list per phase. |
|
|
201
|
+
| [`specs/research/pdfslick-and-reflex-research.md`](specs/research/pdfslick-and-reflex-research.md) | Primary-source research notes that ground the design. |
|
|
202
|
+
|
|
203
|
+
## Roadmap
|
|
204
|
+
|
|
205
|
+
- [x] **Phase 0 — Foundation & SDD**: repository, packaging, SDD artifacts,
|
|
206
|
+
README, demo skeleton.
|
|
207
|
+
- [x] **Phase 1 — Core wrapper**: `PdfSlickWrapper.tsx`, `pdf_slick` component,
|
|
208
|
+
props, CSS import, NoSSR.
|
|
209
|
+
- [x] **Phase 2 — Events & state bridge**: page/scale/load/error/metadata events.
|
|
210
|
+
- [x] **Phase 3 — Imperative controls**: navigation, zoom, rotation, modes,
|
|
211
|
+
print, download (declarative `command` prop + `commands` helpers).
|
|
212
|
+
- [x] **Phase 4 — Thumbnails & multi-document**: vertical/horizontal thumbnails,
|
|
213
|
+
independent multi-instance viewers.
|
|
214
|
+
- [x] **Phase 5 — Demo gallery** (all 7 examples) **& docs polish**.
|
|
215
|
+
- [x] **Phase 6 — Tests & CI**: 35 unit tests, GitHub Actions (pytest 3.10–3.13,
|
|
216
|
+
TSX transpile, demo build smoke). Demo compiles to a Reflex production build.
|
|
217
|
+
- [ ] **Release** — PyPI publish + tagged GitHub release *(awaiting maintainer
|
|
218
|
+
credentials)*.
|
|
219
|
+
|
|
220
|
+
## Credits
|
|
221
|
+
|
|
222
|
+
- [PDFSlick](https://pdfslick.dev/) by
|
|
223
|
+
[Vancho Stojkov](https://github.com/van100j) — the library being wrapped (MIT).
|
|
224
|
+
- [PDF.js](https://github.com/mozilla/pdf.js) by Mozilla.
|
|
225
|
+
- [Reflex](https://reflex.dev/) — the Python web framework.
|
|
226
|
+
|
|
227
|
+
This wrapper is an independent project and is not affiliated with or endorsed by
|
|
228
|
+
the PDFSlick authors.
|
|
229
|
+
|
|
230
|
+
## License
|
|
231
|
+
|
|
232
|
+
[MIT](LICENSE) © Ernesto Crespo
|