Skip to content

Add Nutrient Web SDK demo: full host-app shell around the viewer - #193

Merged
MahmoudElsayad merged 10 commits into
masterfrom
mahmoud/demo-example
Aug 9, 2026
Merged

MahmoudElsayad merged 10 commits into
masterfrom
mahmoud/demo-example

Conversation

@MahmoudElsayad

@MahmoudElsayad MahmoudElsayad commented May 16, 2026 •

Copy link
Copy Markdown
Contributor

What's special about this demo

Most existing Web SDK examples in this repo show one feature: a single custom button, a single custom tooltip, a single replacement slot. They live inside the SDK's chrome.

This demo does the opposite — it hides the SDK's chrome and rebuilds a complete document workspace around the viewer, in the style of a host application like Dropbox's PDF UI. The viewer is just a content surface; the toolbar, file explorer, signing flow, form-creator panel, and drawing/text tools are all host-app code talking to the SDK through its instance API.

That's the angle this example covers that no other example in the repo does: it stress-tests the app/viewer boundary end-to-end and shows that customer-owned UI around the SDK is a viable path, not just per-component customization.

The six integration patterns it demonstrates

  1. External floating ink toolbar. Activates `NutrientViewer.InteractionMode.INK`, sets app-owned annotation presets through `instance.setAnnotationPresets` / `instance.setCurrentAnnotationPreset`, and drives undo/redo through `instance.history.undo()`. The host owns the toolbar; the SDK just renders the strokes.
  2. Hide and replace the main toolbar. `ui.tools.main` is set to a render-nothing slot and the demo's own React toolbar takes over page navigation, rotate/add/delete/move, zoom, search, and download — all driven through `instance.setViewState` and `instance.applyOperations`.
  3. App-owned signature capture modal. Four-tab dialog (Draw / Type / Upload / Saved) running on a custom canvas. Output goes through `instance.createAttachment(blob)` → `ImageAnnotation({ isSignature: true, imageAttachmentId, contentType: 'image/png' })` → `instance.create()`. `signatures.create` and `signatures.list` slots are bridged so the SDK's own dialog never opens.
  4. Drag-and-drop from an external panel into the viewer. Drop listeners are attached to `instance.contentDocument` (the SDK runs in an iframe), then drop coordinates are converted with `instance.transformContentClientToPageSpace(new NutrientViewer.Geometry.Point(...), pageIndex)` and linked widget + form-field objects are created.
  5. Custom Form Creator property editor. `ui.formCreator.propertyEditor` is replaced with a host-rendered panel that walks the form-field → annotation pair to rename and delete fields through `instance.update` / `instance.delete`.
  6. Custom annotation tooltip actions. `annotationTooltipCallback` injects Delete-field and Edit-field buttons on top of the SDK's built-in tooltip. The Edit button flips into Form Creator mode and a separate listener restores the previous interaction mode when the selection clears.

The API surfaces it exercises across the boundary

Slot replacement: `ui.tools.main`, `ui.tools.contextual`, `ui.signatures.create`, `ui.signatures.list`, `ui.formCreator.propertyEditor`, `annotationTooltipCallback`.

Instance methods: `setUI`, `setViewState`, `applyOperations`, `history.undo`/`redo`, `setAnnotationPresets`, `setCurrentAnnotationPreset`, `contentDocument`, `transformContentClientToPageSpace`, `createAttachment`, `create`, `update`, `delete`, `getSelectedAnnotations`, `setSelectedAnnotations`, `getFormFields`, `getAnnotations`, `exportPDF`, `InteractionMode.*`.

Annotation/form model: `Annotations.WidgetAnnotation`, `Annotations.ImageAnnotation` (with `isSignature: true`), `FormFields.TextFormField`, `FormFields.CheckBoxFormField`, `FormOption`, `Geometry.Rect`, `Geometry.Point`.

This breadth across one app is the point — it shows that all the pieces a customer needs to build a full host-app shell are present and that they compose.

Why this matters

This example was originally built as a validation example for the headless / API-first strategy: prove that the SDK is usable as an embeddable viewer surface inside a customer-owned UI, not only as a closed UI box that can be poked at the edges. As a side benefit, the demo surfaced an API ergonomics gap in slot callbacks (which then drove a follow-up SDK change) — exactly the kind of feedback this category of example is meant to produce.

What's rebuilt vs. consumed

  • Rebuilt from scratch: main toolbar, signature dialog, Form Creator property editor, floating ink/text toolbars, file explorer, signers panel.
  • Augmented: annotation tooltip (custom Delete/Edit items on top of the SDK's tooltip).
  • Consumed as-is: page rendering, thumbnails sidebar, search UI, rich-text editor, annotation/form-field data model, history stack, attachment storage.

Notes

  • The SDK is loaded from the CDN via a `<script>` tag (`pspdfkit-web@1.15.0`), so there's no `pspdfkit-lib` copy step and no `pspdfkit` npm dependency. `baseUrl` is omitted — the SDK auto-detects it from the script's origin.
  • The grouped UI slots used here (`tools.main`, `signatures.create`, `formCreator.propertyEditor`) require Web SDK 1.14+. Older CDN versions reject the config with `'tools' is not a valid UI element`.
  • A shared Baseline UI dark theme is passed straight into the SDK iframe via `NutrientViewer.load({ theme })`, so the demo shell and the viewer chrome render with one set of color tokens — no CSS overrides, no runtime sync.
  • `biome.json` is updated to opt the new example out of repo-wide formatting, matching the convention every other example under `web/` follows (`!web/viewer/multi-tab`, `!web/signing/signing-demo-complete`, etc.).

Test plan

  • `cd web/viewer/web-sdk-demo && npm install && npm run dev`
  • Open http://localhost:5173 — bundled `example.pdf` renders with the custom dark theme
  • Drag a PDF into the sidebar — uploads, switches active doc
  • Toolbar: page nav, rotate, add/delete/move page, zoom, download
  • Sign popover → Get signatures → drag a Signature pill onto the page → signing modal opens at the drop point
  • Drawing mode → floating ink toolbar appears; colour + width work; Undo + Delete drawings work
  • Text mode → floating text toolbar; colour, size, bold/italic, alignment apply to a selected text annotation
  • Edit field (annotation tooltip) → switches to Form Creator, custom property editor renders; renaming via the input updates both the form-field and the widget; clicking off restores the previous interaction mode
  • `npm run build` succeeds

A complete, standalone Vite + React + TypeScript app that showcases a fully
custom UI around the Web SDK: custom top toolbar, draggable ink and text
toolbars, side file explorer with drag-and-drop PDF uploads, a four-tab
signature dialog, drag-and-drop form field placement, and a custom Form
Creator property editor mounted into the SDK's slot.

The SDK is loaded from the CDN via a script tag (pspdfkit-web@1.15.0), so
no pspdfkit-lib copy step is required.

biome.json is updated to opt the new example out of repo-wide formatting,
matching the convention of every other example under web/.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MahmoudElsayad MahmoudElsayad self-assigned this May 16, 2026
@MahmoudElsayad MahmoudElsayad changed the title Add Nutrient Web SDK demo app (React + Vite) Add Nutrient Web SDK demo: full host-app shell around the viewer May 16, 2026
@MahmoudElsayad
MahmoudElsayad marked this pull request as ready for review June 2, 2026 01:14
@MahmoudElsayad
MahmoudElsayad requested review from a team as code owners June 2, 2026 01:14
@MahmoudElsayad
MahmoudElsayad requested a review from ritz078 June 2, 2026 01:14
@MahmoudElsayad

Copy link
Copy Markdown
Contributor Author

@sashamilenkovic @sc0 This is ready for you.

@ritz078 ritz078 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review pass — inline suggestions below, none blocking.

Comment thread web/viewer/web-sdk-demo/src/App.tsx
Comment thread web/viewer/web-sdk-demo/src/App.tsx Outdated
Comment thread web/viewer/web-sdk-demo/src/App.tsx
Comment thread web/viewer/web-sdk-demo/src/App.tsx Outdated
Comment thread web/viewer/web-sdk-demo/src/Toolbar.tsx Outdated
Comment thread web/viewer/web-sdk-demo/src/ink/InkToolbar.tsx Outdated
Comment thread web/viewer/web-sdk-demo/src/form-creator/index.ts Outdated
Comment thread web/viewer/web-sdk-demo/src/types/global.d.ts Outdated
Comment thread web/viewer/web-sdk-demo/tsconfig.app.json
Comment thread web/viewer/web-sdk-demo/src/text/TextToolbar.tsx Outdated
@MahmoudElsayad
MahmoudElsayad requested a review from ritz078 June 16, 2026 12:12

@ritz078 ritz078 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two items with no single line to anchor to:

  • web/viewer/README.md not updated. AGENTS.md (Runnable Local Examples) requires updating the nearest category README so a new example is discoverable; the catalog currently lists only multi-tab.
  • No CI builds or typechecks this example — typecheck.yml is scoped to playground/** and the demo sits in biome.json's ignore list, so tsc -b && vite build isn't exercised in CI. Matches repo norms (multi-tab is the same); just verify the build locally before merge.

Comment thread web/viewer/web-sdk-demo/src/NutrientViewer.tsx
Comment thread web/viewer/web-sdk-demo/src/lib/sdk.ts Outdated
Comment thread web/viewer/web-sdk-demo/src/lib/selection.ts Outdated
Comment thread web/viewer/web-sdk-demo/src/form-creator/index.ts
Comment thread web/viewer/web-sdk-demo/src/form-creator/index.ts Outdated
Comment thread web/viewer/web-sdk-demo/src/signing/storage.ts Outdated
Comment thread web/viewer/web-sdk-demo/src/signing/SigningModal.tsx
Comment thread web/viewer/web-sdk-demo/src/ink/InkToolbar.tsx
Comment thread web/viewer/web-sdk-demo/src/App.tsx
Comment thread document-authoring/ai-editing/src/components/document-authoring-ai-shell.tsx Outdated
@MahmoudElsayad
MahmoudElsayad merged commit bad6735 into master Aug 9, 2026
2 checks passed
@MahmoudElsayad
MahmoudElsayad deleted the mahmoud/demo-example branch August 9, 2026 14:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants