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

@kodezen/editor

v1.3.4

Published

Embeddable drag-and-drop block email editor. Framework-agnostic, zero server/CMS dependency. Drop-in replacement for a Quill/TinyMCE editor: produces email HTML client-side.

Downloads

476

Readme

@kodezen/editor

Embeddable drag-and-drop block editor. Framework-agnostic, zero server/CMS dependency. A drop-in replacement for a Quill/TinyMCE editor: it produces HTML client-side (no DB table, backend endpoint, or any other package required beyond React) and ships zero global CSS (every pane is inline-styled).

One component (EmailEditor) covers four modes — email builder, plain rich-text, form builder, and certificate/print layout — plus two convenience wrappers (RichTextEditor, FormEditor) for call sites that only ever want one mode.

Install

npm install @kodezen/editor

The package is pre-built (transpiled ESM) so it works regardless of the consumer's bundler/transform setup.

Peer dependencies (host-supplied)

react, react-dom. These are left external so the consumer's single React instance is reused. @dnd-kit/*, react-icons, and just-debounce-it are bundled in.

Media picker

The default mediaUpload handler opens a native <input type="file"> picker and resolves the chosen file as a data: URL — it works in any browser with no configuration, but it's a testing baseline, not a production upload pipeline (data: URLs bloat stored HTML and are unreliable in several email clients). A host with real storage (S3, its own media library, etc.) should pass its own mediaUpload prop instead — see below.

Usage

import { EmailEditor, renderContent, renderDocument, BLANK_TREE } from '@kodezen/editor';

const ref = useRef();

<EmailEditor
  ref={ref}
  initialTree={savedTree}                 // { version, root } — read once at mount; remount via React `key`
  mergeTags={[{ tag: '{{first_name}}', label: 'First name' }]}
  mediaUpload={async ({ allowedTypes }) => ({ url, id, alt })}   // optional; defaults to a native file picker
  onChange={({ tree, html }) => persist(tree, html)}            // debounced
  renderMode="document"                   // 'document' (full email) | 'fragment' (host wraps in its own shell)
  header                                  // show a slim Preview bar
/>

// On submit, pull the latest value past the debounce:
const html = ref.current.getHtml();
const tree = ref.current.getTree();

Modes

EmailEditor takes a mode prop; RichTextEditor and FormEditor are thin wrappers that hard-code it (<EmailEditor mode="richtext" /> / <EmailEditor mode="form" /> respectively):

| mode | What | | --- | --- | | builder (default) | Full drag-and-drop block email builder. | | richtext | A simple Quill-like rich-text field (persistent toolbar, single editable surface). Takes value (HTML string) instead of initialTree; onChange still fires { tree, html } (tree is a single Custom HTML block wrapping the field's HTML). | | form | Drag-and-drop form builder (semantic, responsive <form> output). onChange fires { tree, html, schema }. | | certificate | The block builder laid out on a fixed landscape print page with a background, for certificates/awards. |

Props

| Prop | Modes | What | | --- | --- | --- | | mode | all | 'builder' (default) | 'richtext' | 'form' | 'certificate'. | | initialTree | builder, form, certificate | { version, root } — saved block tree, or omit for a blank starter. Read once at mount (like defaultValue); remount via a React key to load a different record. | | value | richtext | HTML string — the saved content. Re-applied if it changes while the field is unfocused (async form loads). | | mergeTags | all | [{ tag: '{{first_name}}', label: 'First name' }, ...] — host personalization tags, inserted verbatim and resolved at the host's send time. | | mediaUpload | all | ({ allowedTypes }) => Promise<{ url, id, alt } | null>. Optional; defaults to defaultMediaUpload (native file picker, data: URL). | | onChange | all | Debounced. { tree, html } (form mode also includes schema). | | header | builder, richtext, form | false (default) | true — shows a slim Preview bar. Always on for certificate. | | height | all | Editor height in px. Omit to use the stylesheet defaults (640 builder/form/certificate, 320 richtext), which hosts can override by class. | | renderMode | builder | 'document' (full <!doctype html>… email) | 'fragment' (content only — host wraps its own <html>/<body> shell). | | blocks | builder, form, certificate | Optional manifest override, replacing the mode's default block set entirely. | | blockPlugins | builder, form | [{ type, spec, render, controls, schema }, ...] — teach the editor host-specific blocks without forking the core. render(node, ctx, helpers) returns an HTML string; helpers exposes the built-in renderers and style utilities so a plugin can delegate to them. See dev_emb/library/blockPlugins.js. | | hideBlocks | builder, form, certificate | string[] of core block types to drop from the palette/quick-add. Blocks already present in a saved tree still render. | | templates | builder, certificate | Array of starter templates shown in the template picker. | | onTemplatesClick | builder, certificate | Callback when the host's own "browse templates" affordance is used. | | suffix | all | Theming hook, e.g. suffix="zaplane". Every shell chrome piece gets a stable --{suffix} modifier class, and hosts can override the --emb-* custom properties on .emb-embed-root--{suffix} to theme the chrome (e.g. dark mode) without !important. | | config | all | { 'English string': 'Host-translated string', ... } — plain lookup object. The package does no translation logic of its own; the host resolves everything and hands over final strings. Anything missing falls back to the English text as-is. Omit for plain English. See i18n. |

Ref handle (all modes): { getTree, getHtml, getSchema, setTree, setHtml }. Not every method is meaningful in every mode (e.g. setHtml is for richtext) — calling one that doesn't apply is a no-op. Read getHtml()/getTree() on the host's submit to capture the latest value even if the debounce hasn't fired.

Exports

| Export | What | | --- | --- | | EmailEditor | The controlled editor component (forwardRef, all modes — see above). | | RichTextEditor | Convenience wrapper = <EmailEditor mode="richtext" />. | | FormEditor | Convenience wrapper = <EmailEditor mode="form" />. | | renderDocument(tree) | Tree → full email HTML document (doctype + MSO shell). | | renderContent(tree) | Tree → email content fragment (no doctype/body) for hosts that wrap in their own email shell. | | renderFormDocument(tree) / renderFormContent(tree) | Same, for a form-mode tree. | | renderCertificateDocument(tree) / renderCertificateContent(tree) | Same, for a certificate-mode tree. | | extractSchema(tree) | Form tree → field schema (also emitted as schema in form-mode onChange). | | pageDims | Fixed page dimensions used by the certificate layout. | | treeFromHtml(html) / htmlToBlocks(html) | Best-effort HTML → block tree, for importing existing content. | | BLANK_TREE | Starter tree used when initialTree is absent (builder/richtext). | | BLANK_CERT_TREE | Starter tree for certificate mode. | | defaultMediaUpload | The native file-picker default media handler (see Media picker above). |

i18n

Every UI string is untranslated (plain English) by default — there's no bundled translation catalog and no runtime translate hook. To localize, pass a plain lookup object as the config prop:

<EmailEditor config={{ 'Add block': 'Ajouter un bloc' }} />

The package does no lookup/interpolation logic beyond config[text] ?? text — the host is responsible for resolving strings up front.

Storage contract

The host owns persistence. onChange gives { tree, html } (plus schema in form mode):

  • html → the field your send pipeline already uses.
  • tree (JSON) → a sibling field, so the editor round-trips on reopen.

Building from source

npm install
npm run build      # → dist/index.js