npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@web-ppt/react

v0.5.0

Published

Thin React component and hook for the framework-agnostic Web-PPT viewer/editor.

Readme

@web-ppt/react

Thin React bindings for the framework-agnostic @web-ppt/editor. The component owns only a container and lifecycle; parsing, preview, editing, history, and saving remain in the shared editor session.

npm i @web-ppt/core@next @web-ppt/edit-core@next @web-ppt/viewer-core@next @web-ppt/editor@next @web-ppt/react@next react

For a new-deck picker, lazy-load @web-ppt/react/templates. It forwards the shared frozen catalog and createPptxFromTemplate() without adding recipes to the React component entry.

const templates = await import('@web-ppt/react/templates');
const bytes = templates.createPptxFromTemplate(templates.listBuiltinTemplates()[0].id);

Classic chart data uses the same opt-in seam without adding it to the component bundle:

const charts = await import('@web-ppt/react/chart');
const session = editor.current!.session!;
const chart = charts.listEditableCharts(session.editor.doc)[0];
const data = charts.queryChartData(session.editor.doc, chart.id);
charts.createChartDataEditor(session.editor).setSeriesName(chart.id, data.series[0].id, 'Forecast');
import { useRef, useState } from 'react';
import { WebPptEditor, type WebPptEditorHandle } from '@web-ppt/react';
import { createIndexedDbRecoveryStore } from '@web-ppt/editor';

const recoveryStore = createIndexedDbRecoveryStore({ namespace: 'react-deck' });

export function Deck({ file }: { file: File }) {
  const editor = useRef<WebPptEditorHandle>(null);
  const [mode, setMode] = useState<'view' | 'edit'>('edit');
  const [slideId, setSlideId] = useState<string>();

  return <>
    <button onClick={() => setMode(mode === 'edit' ? 'view' : 'edit')}>{mode}</button>
    <button onClick={() => editor.current?.undo()}>Undo</button>
    <button onClick={async () => download(await editor.current!.save())}>Save</button>
    <WebPptEditor
      ref={editor}
      source={file}
      mode={mode}
      slideId={slideId}
      zoom={1}
      openOptions={{ recovery: { store: recoveryStore } }}
      onRecovery={(candidate) => openRecoveryModal(candidate)}
      onViewChange={(state) => setSlideId(state.slideId ?? undefined)}
      onTouchNavigate={({ phase, viewport }) => applyCanvasViewport(phase, viewport)}
      onContextRequest={(request) => openContextMenu(request)}
      onError={console.error}
      style={{ width: '100%', height: 600 }}
    />
  </>;
}

function download(bytes: Uint8Array) {
  const url = URL.createObjectURL(new Blob([bytes], {
    type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
  }));
  const link = Object.assign(document.createElement('a'), { href: url, download: 'edited.pptx' });
  link.click();
  URL.revokeObjectURL(url);
}

useWebPptAdapter(binding) exposes the same controlled contract without the component. It returns { containerRef, adapter, snapshot }; snapshot carries status, progress, errors, the active session/view, mode, slide, and zoom. Changing source atomically opens the replacement and releases the old owned session. The same snapshot includes formatPainter; call adapter.startFormatPainter({ continuous: true }) or adapter.cancelFormatPainter() from a toolbar. React only observes the editor session controller and does not create another painter state machine. Page-transition controls follow the same seam: adapter.queryTransition(), adapter.setTransition(value), and adapter.previewTransition(value?) work in a product toolbar without React-owned playback state. Preview is available in view and edit modes; mutation remains edit-only. SlideTransitionInput and SlideTransitionState are re-exported for typed controls. Element-animation controls use the same adapter: queryAnimations(), setAnimations(steps | null), and previewAnimations(draft?). Targets are stable ElementIds; preview automatically runs every click group and does not mutate the model or history. EditAnimationStep and SlideAnimationState are re-exported. Distribution, alternative text, sections, and slide-size controls call the same adapter methods and need no React-owned document state. ElementAltTextState, SectionRecord, and SlideSizeState are re-exported. Theme toolbars use snapshot.session.editor.exec({ type: 'SetTheme', ... }); SetThemeCommand and ThemeState are re-exported so React does not own a second theme model. Layout and master designers use createDesignEditor(container, snapshot.session, { target }) from the opt-in @web-ppt/editor/design entry; DesignEditor, DesignTarget, LayoutCatalogItem, LayoutDesignState, MasterCatalogItem, and MasterDesignState are re-exported for typed React-owned UI controls. onTouchNavigate receives the framework-neutral pinch viewport and onContextRequest receives the 500ms long-press target. React adds no gesture state: the shared adapter applies zoom, while product layout owns outer scrolling and menu presentation. TouchNavigationChange and EditorContextRequest are re-exported. snapshot.textSearch follows the same rule: render its query, match count, current match, invalidation flag, and canReplace directly, then call adapter actions from the toolbar:

const { adapter, snapshot } = useWebPptAdapter(binding);
return <form onSubmit={(event) => { event.preventDefault(); adapter?.nextTextSearch(); }}>
  <input
    aria-label="Find"
    value={snapshot.textSearch.query}
    onChange={(event) => adapter?.setTextSearchQuery(event.target.value)}
    onFocus={() => adapter?.openTextSearch({ mode: 'find' })}
  />
  <span>{snapshot.textSearch.currentIndex + 1}/{snapshot.textSearch.matches.length}</span>
  <button type="button" disabled={!snapshot.textSearch.canReplace}
    onClick={() => adapter?.replaceCurrentText()}>Replace</button>
</form>;

Unmount releases listeners, focus, Blob URLs, views, and owned package bytes. The entry is SSR-safe, including React StrictMode's development setup/cleanup/setup cycle.

The optional selection pane reuses that adapter; it does not maintain another document or selection:

const { adapter, containerRef } = useWebPptAdapter(binding);
return <div className="deck">
  <div ref={containerRef} />
  <WebPptSelectionPane adapter={adapter} aria-label="Objects" />
</div>;

WebPptSelectionPane follows controlled slide and view/edit mode changes, exposes its DOM controller through a ref, and detaches without disposing an external session.

While a journal is awaiting a decision, snapshot.status is recovering and snapshot.recovery contains its lightweight metadata. onRecovery may return restore, discard, or cancel; source hashing, IndexedDB writes, compaction, replay, and cleanup remain in @web-ppt/editor rather than the React component.

To share one session between several React, Vue, Web Component, Svelte, or vanilla views, ownership is explicit:

<WebPptEditor session={session} sessionOwnership="external" mode="edit" />
<WebPptEditor session={session} sessionOwnership="external" mode="view" />

Those components destroy their views but never dispose the injected session; the caller must call session.dispose(). A source session is owned and disposed by its adapter. Never pass both.

| Vanilla @web-ppt/editor | React | |---|---| | createWebPptAdapter() | useWebPptAdapter() | | adapter.attach(element) | returned containerRef | | adapter.attachSelectionPane(element) | <WebPptSelectionPane adapter={adapter} /> | | adapter.setDocument(...) | source or external session prop | | adapter.setView(...) | controlled mode, slideId, zoom, snapping props | | adapter.subscribe(...) | returned snapshot | | adapter.save/undo/redo() | component ref or returned adapter | | adapter.startFormatPainter/cancelFormatPainter() | returned adapter + snapshot.formatPainter | | adapter.openTextSearch/.../replaceAllText() | returned adapter + snapshot.textSearch | | adapter.query/set/previewAnimations() | returned adapter; no React playback state | | adapter.dispose() | automatic on unmount |

Validated in Node SSR and Chromium with React 19.2.8; the optional peer range supports React 18.2–19. The framework-excluded ESM is 1.12KB gzip, ships declarations, and does not add React to the base packages.