Metadata-Version: 2.4
Name: reflex-pdfslick
Version: 0.0.1
Summary: A native Reflex component wrapping PDFSlick (PDF.js + Zustand) to view and interact with PDF documents in pure Python.
Author-email: Ernesto Crespo <ecrespo@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/ecrespo/reflex-pdfslick
Project-URL: Repository, https://github.com/ecrespo/reflex-pdfslick
Project-URL: Issues, https://github.com/ecrespo/reflex-pdfslick/issues
Project-URL: Documentation, https://github.com/ecrespo/reflex-pdfslick#readme
Project-URL: Changelog, https://github.com/ecrespo/reflex-pdfslick/releases
Project-URL: PyPI, https://pypi.org/project/reflex-pdfslick/
Project-URL: PDFSlick, https://pdfslick.dev/
Keywords: reflex,reflex-custom-components,pdf,pdfslick,pdfjs,pdf-viewer,component,viewer,thumbnails
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Framework :: AsyncIO
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: reflex>=0.9.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# reflex-pdfslick

[![PyPI version](https://img.shields.io/pypi/v/reflex-pdfslick.svg)](https://pypi.org/project/reflex-pdfslick/)
[![Python versions](https://img.shields.io/pypi/pyversions/reflex-pdfslick.svg)](https://pypi.org/project/reflex-pdfslick/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/ecrespo/reflex-pdfslick/blob/main/LICENSE)
[![GitHub](https://img.shields.io/badge/GitHub-ecrespo%2Freflex--pdfslick-181717?logo=github)](https://github.com/ecrespo/reflex-pdfslick)

- **Source:** https://github.com/ecrespo/reflex-pdfslick
- **Issues:** https://github.com/ecrespo/reflex-pdfslick/issues
- **PDFSlick (upstream):** https://github.com/pdfslick/pdfslick — https://pdfslick.dev

A native [Reflex](https://reflex.dev/) component that wraps
[**PDFSlick**](https://pdfslick.dev/) so you can view and interact with PDF
documents in **pure Python** — no HTML, no `iframe`, no hand-written
JavaScript.

PDFSlick is built on top of Mozilla's [PDF.js](https://github.com/mozilla/pdf.js)
and uses [Zustand](https://github.com/pmndrs/zustand) to expose a reactive store
for the loaded document. `reflex-pdfslick` bridges that store and PDFSlick's
imperative API into Reflex's declarative, state-driven model: document state
(page number, page count, zoom, rotation, metadata, thumbnails) flows back to
Python as events, and Python triggers actions (go to page, zoom, rotate, print,
download) on the viewer.

> **Project status — `ALPHA` / implemented, pre-release.**
> The component runtime, imperative controls, and the full 7-example demo
> gallery are built and covered by tests; the demo compiles to a Reflex
> production build in CI. The remaining step is the PyPI/GitHub release. See
> [Roadmap](#roadmap) for details.

```python
import reflex as rx
from reflex_pdfslick import pdf_slick


class State(rx.State):
    page_number: int = 1
    num_pages: int = 0

    @rx.event
    def on_page_change(self, page: int):
        self.page_number = page

    @rx.event
    def on_document_load(self, num_pages: int):
        self.num_pages = num_pages


def index() -> rx.Component:
    return pdf_slick(
        url="/sample.pdf",
        scale_value="page-fit",
        single_page_viewer=True,
        on_page_change=State.on_page_change,
        on_document_load=State.on_document_load,
        height="700px",
    )


app = rx.App()
app.add_page(index)
```

## Why a native component instead of an `iframe`?

Embedding a PDF viewer through an `iframe` or a raw `<embed>` works, but it is
isolated from your app: you cannot react to page changes, drive the zoom from a
Python toolbar, or read the document's metadata into your state. Wrapping
PDFSlick as a first-class Reflex component means:

- **Reactive state** — `page_number`, `num_pages`, `scale`, `rotation`,
  `is_document_loaded` and document metadata are pushed into Reflex state via
  typed events.
- **Imperative control from Python** — go to page, zoom in/out, fit width/page,
  rotate, switch scroll/spread modes, print and download are exposed as
  component-level controls.
- **Reusable & versioned** — installable with `pip`, with the npm dependency
  (`@pdfslick/react`) pinned for reproducible builds.
- **SSR-safe** — PDF.js is browser-only, so the component is wrapped as a
  Reflex `NoSSRComponent` and only ever renders on the client.

## Installation

```shell
pip install reflex-pdfslick
# or, from source
git clone https://github.com/ecrespo/reflex-pdfslick.git
cd reflex-pdfslick
pip install -e .
```

Reflex installs the underlying npm packages automatically on first run. The
component pins `@pdfslick/react` and `pdfjs-dist` (which pull in
`@pdfslick/core` and `zustand`) and imports the required stylesheet
`@pdfslick/react/dist/pdf_viewer.css` for you.

## Custom-component packaging (build & publish)

This repository is a standard [Reflex custom
component](https://reflex.dev/docs/custom-components/overview/): the package
lives in `custom_components/reflex_pdfslick/` (shipping the `PdfSlickWrapper.tsx`
wrapper and a generated `pdfslick.pyi` stub), the demo app in `pdfslick_demo/`,
and `pyproject.toml` carries the `reflex-custom-components` keyword used by the
Reflex component gallery to discover it on PyPI.

```shell
# Build the wheel + sdist into dist/ (also regenerates the .pyi type stubs).
# PYTHONPATH=. lets the stub generator import custom_components/.
PYTHONPATH=. uv run reflex component build

# Inspect, then publish to PyPI (bump the version in pyproject.toml first):
uv run twine upload dist/*        # or: uv publish

# Share on the Reflex community gallery:
uv run reflex component share
```

The built wheel includes the React wrapper (`reflex_pdfslick/PdfSlickWrapper.tsx`)
and the type stub, so `rx.asset(...)` resolves the wrapper when the package is
installed from PyPI — not only in editable mode.

## Planned API (summary)

The full contract lives in [specs/api/component-api.md](specs/api/component-api.md).

| Prop | Type | Description |
|---|---|---|
| `url` | `str` | Path/URL of the PDF to load. |
| `scale_value` | `str` | `"auto"`, `"page-fit"`, `"page-width"`, `"page-actual"` or a numeric string. |
| `single_page_viewer` | `bool` | Render a single page at a time. |
| `thumbnail_width` | `int` | Width (px) of generated thumbnails. |
| `remove_page_borders` | `bool` | Hide page borders. |

| Event | Payload | Fired when |
|---|---|---|
| `on_document_load` | `int` (num pages) | The document finishes loading. |
| `on_page_change` | `int` (page) | The current page changes. |
| `on_scale_change` | `float` (scale) | The zoom level changes. |
| `on_error` | `dict` | Loading/parsing fails. |

Imperative controls (next/previous page, zoom, rotate, print, download) are
driven from Python state — see the API spec for the dispatch mechanism.

## Examples (demo gallery)

The `pdfslick_demo/` Reflex app mirrors PDFSlick's own example set. Each example
is documented in the [PRD](specs/prd/pdfslick-component.md) and
[task breakdown](specs/tasks/task-breakdown.md):

1. **Simple Viewer** — basic viewer with prev/next navigation and zoom.
2. **Full Viewer App** — toolbar, zoom selector, rotation, scroll/spread modes,
   document info, search and thumbnails sidebar.
3. **Thumbnails Layout** — a grid of page thumbnails as the primary surface.
4. **Horizontal Thumbnails** — a horizontal thumbnail strip.
5. **Multiple Documents** — several independent viewers on one page.
6. **Load from ArrayBuffer** — open a PDF from in-memory bytes.
7. **Error Handling** — graceful handling of load/parse errors.

Run the demo:

```shell
cd pdfslick_demo
pip install -r requirements.txt
reflex run
```

## Architecture (at a glance)

```
Python (Reflex app)
  pdf_slick(url=..., scale_value=..., on_page_change=...)
        │  compiles to
        ▼
React / Next.js (generated by Reflex)
  <PdfSlickWrapper>  ← local .tsx, NoSSR, calls usePDFSlick(url, options)
        │  renders <PDFSlickViewer/> + (optional) thumbnails
        │  subscribes to usePDFSlickStore selectors → emits Reflex events
        ▼
@pdfslick/react → @pdfslick/core → PDF.js (pdfjs-dist) + Zustand store
```

Because PDFSlick's public surface is the **hook** `usePDFSlick(...)` (not a
drop-in component) and its thumbnails component uses a render-prop child, the
wrapper is a small local `.tsx` component that calls the hook internally and
exposes a clean prop/event API to Python. Full rationale and trade-offs are in
[specs/technical/architecture.md](specs/technical/architecture.md).

## Spec-Driven Design documentation

This project is documented before it is built. The `specs/` directory is the
source of truth:

| Document | Purpose |
|---|---|
| [`specs/prd/pdfslick-component.md`](specs/prd/pdfslick-component.md) | Product requirements, goals, scope, examples. |
| [`specs/technical/architecture.md`](specs/technical/architecture.md) | Technical design, SSR strategy, wrapper pattern. |
| [`specs/api/component-api.md`](specs/api/component-api.md) | Public Python API: props, events, controls. |
| [`specs/data-model/store-schema.md`](specs/data-model/store-schema.md) | PDFSlick store schema and Python ↔ JS mapping. |
| [`specs/plans/implementation-plan.md`](specs/plans/implementation-plan.md) | Phases, milestones, sequencing. |
| [`specs/tasks/task-breakdown.md`](specs/tasks/task-breakdown.md) | Granular, trackable task list per phase. |
| [`specs/research/pdfslick-and-reflex-research.md`](specs/research/pdfslick-and-reflex-research.md) | Primary-source research notes that ground the design. |

## Roadmap

- [x] **Phase 0 — Foundation & SDD**: repository, packaging, SDD artifacts,
      README, demo skeleton.
- [x] **Phase 1 — Core wrapper**: `PdfSlickWrapper.tsx`, `pdf_slick` component,
      props, CSS import, NoSSR.
- [x] **Phase 2 — Events & state bridge**: page/scale/load/error/metadata events.
- [x] **Phase 3 — Imperative controls**: navigation, zoom, rotation, modes,
      print, download (declarative `command` prop + `commands` helpers).
- [x] **Phase 4 — Thumbnails & multi-document**: vertical/horizontal thumbnails,
      independent multi-instance viewers.
- [x] **Phase 5 — Demo gallery** (all 7 examples) **& docs polish**.
- [x] **Phase 6 — Tests & CI**: 35 unit tests, GitHub Actions (pytest 3.10–3.13,
      TSX transpile, demo build smoke). Demo compiles to a Reflex production build.
- [ ] **Release** — PyPI publish + tagged GitHub release *(awaiting maintainer
      credentials)*.

## Credits

- [PDFSlick](https://pdfslick.dev/) by
  [Vancho Stojkov](https://github.com/van100j) — the library being wrapped (MIT).
- [PDF.js](https://github.com/mozilla/pdf.js) by Mozilla.
- [Reflex](https://reflex.dev/) — the Python web framework.

This wrapper is an independent project and is not affiliated with or endorsed by
the PDFSlick authors.

## License

[MIT](LICENSE) © Ernesto Crespo
