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

@qkix/chartkit-editor

v0.3.0

Published

The Chartkit chart editor for the Strapi admin - a data grid, CSV paste and live preview, shared by the standalone custom field and the Better Blocks block

Readme

@qkix/chartkit-editor

The chart editor for the Strapi admin: a preview, a data grid, and paste from a spreadsheet.

import { ChartEditor } from '@qkix/chartkit-editor';

<ChartEditor spec={spec} onChange={setSpec} locale="en-US" />;

Why it is its own package

Chartkit appears in two places - as a standalone Strapi custom field, and as a block inside Better Blocks. Both need the same editing surface. If it lived in either plugin, the other would have to depend on a whole editor plugin to reuse it, which is precisely the coupling the block registration API exists to avoid.

The preview is the real renderer

It calls renderChart from @qkix/chartkit-core - the same function the React, Astro and Vue renderers call. The editor and the published page draw from one code path, so a chart cannot look right in one and wrong in the other.

A spec that will not render shows its validation issues instead of an empty frame. The author is the only person who can fix it, and this is the one moment they are looking at it.

Pasting from a spreadsheet

The path most charts will actually be created by: the numbers already exist in Sheets or Excel, and retyping them is the worst part of every charting tool.

Deliberately generous about the input, because none of this is the author's mistake:

| Input | Read as | | --------------------------------- | ----------------------------------------------------------------------------- | | tab, comma or semicolon separated | detected per paste; tab first, since that is what a spreadsheet copy produces | | 1.234,5 and 1,234.5 | 1234.5 either way | | €1 234,50 | 1234.5 - currency, spaces and thousands marks stripped | | "North, inland" | one cell; a quoted delimiter survives | | n/a, -, empty | a gap, never a zero |

Semicolons are preferred over commas because a semicolon-separated file usually comes from a locale that also writes decimals with a comma - splitting on those commas would shred every number.

It shows what it parsed before replacing anything. The parser has to guess whether the first row is a header, and a wrong guess that silently overwrites an author's data is far worse than one they can see and cancel.

Edits are pure functions

Every grid operation - setCell, addRow, removeSeries, setType - takes a spec and returns a new one, and they are exported. A host that wants its own controls can drive a spec with these rather than reimplementing array surgery that has to preserve nulls.

They are pure for two reasons. Strapi decides a document is dirty by comparing references, so an in-place edit would show changes the save button does not know about. And editing a chart is mostly array surgery - keep every series the same length as the labels, never lose a null that means "no reading" - which is miserable to test through a rendered table and trivial to test directly.

normalizeShape runs on every change, so a spec can never leave the editor ragged: a series shorter than its labels makes every later edit ambiguous.

Two behaviours worth knowing

Cells commit on blur, not per keystroke. Parsing every keystroke means 1. collapses to 1 and the cursor jumps behind the dot the moment anyone types 1.5, and a cleared cell flickers through 0.

Switching to pie or donut asks first. Those types show one series as shares of a whole and the core refuses more, so the switch discards data. Doing it silently would be a deletion disguised as a conversion.

From the Media Library

The third way in: pick a CSV, TSV or text file already uploaded to Strapi.

It goes through exactly the same panel as a paste - same parser, same preview, same header switch, same confirm before anything is replaced. A file is no more trustworthy than typed text: the header guess and the number formats are the same problem either way.

The values are written into the spec, alongside a note of which file they came from:

data: {
  source: 'media',
  fileId: 42,
  url: '/uploads/quarterly.csv',
  name: 'quarterly.csv',
  importedAt: '2026-08-16T13:51:00.000Z',
  labels: [...],
  series: [...],
}

So nothing is fetched when the chart renders - no resolver, no request at page load, and no permission model to get wrong. The reference is kept so the editor can say where the numbers came from and offer to read the file again, which is an explicit action rather than something that happens behind a reader's back.

A file whose extension is not .csv, .tsv, .txt or .tab is refused before it is even fetched. Handing a PDF to a CSV parser produces a confident table of nonsense, which is worse than a refusal.

readAssetText imports nothing from Strapi, so it is testable on its own; only useMediaLibraryDialog touches the admin's component registry, and it returns undefined - disabling the button - on a build that has no Media Library.

The Better Blocks block, ready made

@qkix/chartkit-editor/block exports the whole registration, so putting charts in a rich-text document is one line rather than a file:

import { registerBlock } from '@qkix/strapi-plugin-better-blocks/strapi-admin';
import { chartBlockDefinition } from '@qkix/chartkit-editor/block';

registerBlock(chartBlockDefinition({ locale: 'en-US' }));

| Option | Default | | --------- | ------------------- | | locale | the browser's | | label | 'Chart' | | icon | a small bar chart | | starter | four empty quarters |

It used to take about eighty lines in every app that wanted it: find the node's path, write the edited spec back with Transforms.setNodes, remember contentEditable={false}, remember the empty text child Slate demands of a void. Identical every time, and four chances to get a detail wrong.

Why a separate entry point. This is the only module here that imports Slate, and the standalone custom field uses this package without it. Exporting it from the package root would make every consumer install an editor framework to get a data grid, so slate and slate-react are optional peers and you only need them if you import /block.

The definition is structurally an EditorBlockDefinition without importing Better Blocks - a chart editor has no business depending on a rich-text plugin. The two shapes meet at your registerBlock call, which is where TypeScript checks them against each other.

License

MIT