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.
Files changed (24) hide show
  1. reflex_pdfslick-0.0.1/LICENSE +21 -0
  2. reflex_pdfslick-0.0.1/PKG-INFO +262 -0
  3. reflex_pdfslick-0.0.1/README.md +232 -0
  4. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/PdfSlickWrapper.tsx +268 -0
  5. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/__init__.py +31 -0
  6. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/commands.py +98 -0
  7. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/models.py +47 -0
  8. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/options.py +41 -0
  9. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/pdfslick.py +80 -0
  10. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick/pdfslick.pyi +113 -0
  11. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick.egg-info/PKG-INFO +262 -0
  12. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick.egg-info/SOURCES.txt +22 -0
  13. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick.egg-info/dependency_links.txt +1 -0
  14. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick.egg-info/requires.txt +6 -0
  15. reflex_pdfslick-0.0.1/custom_components/reflex_pdfslick.egg-info/top_level.txt +1 -0
  16. reflex_pdfslick-0.0.1/pyproject.toml +63 -0
  17. reflex_pdfslick-0.0.1/setup.cfg +4 -0
  18. reflex_pdfslick-0.0.1/tests/test_commands.py +63 -0
  19. reflex_pdfslick-0.0.1/tests/test_component.py +63 -0
  20. reflex_pdfslick-0.0.1/tests/test_demo.py +45 -0
  21. reflex_pdfslick-0.0.1/tests/test_events.py +34 -0
  22. reflex_pdfslick-0.0.1/tests/test_imports.py +31 -0
  23. reflex_pdfslick-0.0.1/tests/test_models.py +39 -0
  24. 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
+ [![PyPI version](https://img.shields.io/pypi/v/reflex-pdfslick.svg)](https://pypi.org/project/reflex-pdfslick/)
34
+ [![Python versions](https://img.shields.io/pypi/pyversions/reflex-pdfslick.svg)](https://pypi.org/project/reflex-pdfslick/)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/ecrespo/reflex-pdfslick/blob/main/LICENSE)
36
+ [![GitHub](https://img.shields.io/badge/GitHub-ecrespo%2Freflex--pdfslick-181717?logo=github)](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
+ [![PyPI version](https://img.shields.io/pypi/v/reflex-pdfslick.svg)](https://pypi.org/project/reflex-pdfslick/)
4
+ [![Python versions](https://img.shields.io/pypi/pyversions/reflex-pdfslick.svg)](https://pypi.org/project/reflex-pdfslick/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/ecrespo/reflex-pdfslick/blob/main/LICENSE)
6
+ [![GitHub](https://img.shields.io/badge/GitHub-ecrespo%2Freflex--pdfslick-181717?logo=github)](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