enarratio-emdash 0.0.0-stage → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,69 @@
1
+ # Changelog
2
+
3
+ All notable changes to enarratio-emdash are recorded here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project uses
5
+ [Semantic Versioning](https://semver.org/). Before 1.0.0, a minor version may change the options or
6
+ the saved block's fields; every such change is listed here.
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.4.1] - 2026-11-03
11
+
12
+ ### Changed
13
+
14
+ - Draws with enarratio-astro 0.4.1 (Enarratio 0.7.2: timeline text never renders under 12 px).
15
+ - Requires Node.js 22.22 or later (was 22.16), the floor Astro 7.4 sets.
16
+
17
+ ## [0.4.0] - 2026-11-01
18
+
19
+ ### Changed
20
+
21
+ - Built on enarratio-astro 0.4.0 (Enarratio 0.7.0). No block field changes.
22
+
23
+ ### Release
24
+
25
+ - Staging works: the release job's setup-node moves from v4 to v7 (see enarratio-astro 0.4.0).
26
+
27
+ - Each release's `npm pack` tarball gets a GitHub artifact attestation beside npm's provenance:
28
+ `gh attestation verify enarratio-emdash-<version>.tgz --repo DrDustinEdwards/astro` checks a download.
29
+
30
+ ## [0.3.0] - 2026-10-31
31
+
32
+ ### Changed
33
+
34
+ - Built on enarratio-astro 0.3.0 (Enarratio 0.5.0). No block field changes.
35
+ - First release on npm since 0.1.0: 0.2.0 and 0.2.1 never reached npm, because trusted publishing was not yet
36
+ set up.
37
+ - The Data field's placeholder example and the README's theme example use neutral names.
38
+
39
+ ## [0.2.1] - 2026-10-31
40
+
41
+ ### Changed
42
+
43
+ - Built on enarratio-astro 0.2.1 (Enarratio 0.4.1).
44
+
45
+ ## [0.2.0] - 2026-10-31
46
+
47
+ ### Changed
48
+
49
+ - Draws with enarratio-astro 0.2.0 (Enarratio 0.4.0): a head with the title and the finding, a
50
+ summary under the chart, a CSV download, and wide charts that scroll sideways on a phone. The
51
+ caption field is now labeled for method or caveats; the source has its own field.
52
+
53
+ ### Added
54
+
55
+ - Fields `subtitle`, `notes`, `source`, `sourceHref`, `byline`, `xUnit` and `yUnit`. Blocks saved by
56
+ 0.1.0 draw as before.
57
+
58
+ ## [0.1.0] - 2026-10-24
59
+
60
+ ### Added
61
+
62
+ - The Chart block for EmDash's Portable Text editor: bar, line, stacked area, scatter and heatmap
63
+ charts drawn on the server by enarratio-astro 0.1.0 (Enarratio 0.3.1) from pasted CSV or
64
+ spreadsheet data, with a required description, a data table, and light and dark themes.
65
+ - Optional interactivity per chart, from Enarratio's enhancement script, added only to pages with
66
+ an interactive chart.
67
+ - `enarratioPlugin({ styles })`: `"theme"` (default), `"base"` for sites whose tokens define the
68
+ colors, or `"none"`.
69
+ - A note in place of a chart that cannot be drawn, saying why.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dustin Edwards
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,122 @@
1
- # Temporary Holding Version
1
+ # enarratio-emdash
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Accessible charts for [EmDash](https://github.com/emdash-cms/emdash) sites, drawn on the server by
4
+ [Enarratio](https://github.com/DrDustinEdwards/enarratio).**
5
+
6
+ The plugin adds a **Chart** block to EmDash's Portable Text editor. An editor types `/chart`, pastes
7
+ data from a spreadsheet (or types CSV), writes one sentence saying what the chart shows, and names
8
+ the columns to plot. The published page gets the chart as HTML and SVG: complete before any script
9
+ runs, readable by screen readers and search engines, with a data table, and in light and dark.
10
+
11
+ It draws bar (stacked, side by side, vertical or horizontal; counting rows when no value column is
12
+ given), line, stacked area, scatter (with an optional regression line and 95% band) and heatmap
13
+ charts. A chart can be made interactive: hover and focus details, keyboard navigation, and
14
+ click-to-filter, from Enarratio's small enhancement script, which is added only to pages that have
15
+ an interactive chart.
16
+
17
+ Supports EmDash 1.0 or later and Astro 6 or later. Status: 0.x, so options may change before 1.0;
18
+ every change is in the [changelog](https://github.com/DrDustinEdwards/astro/blob/main/packages/enarratio-emdash/CHANGELOG.md).
19
+
20
+ ## Install
21
+
22
+ ```sh
23
+ npm install enarratio-emdash
24
+ ```
25
+
26
+ Register it in `astro.config.mjs`, then build and deploy the site:
27
+
28
+ ```js
29
+ import emdash from "emdash/astro";
30
+ import { enarratioPlugin } from "enarratio-emdash";
31
+
32
+ export default defineConfig({
33
+ integrations: [
34
+ emdash({
35
+ // ...your database and storage
36
+ plugins: [enarratioPlugin()],
37
+ }),
38
+ ],
39
+ });
40
+ ```
41
+
42
+ Nothing else is needed: EmDash adds the plugin's renderer to its `<PortableText>` component.
43
+
44
+ ## Why a native plugin
45
+
46
+ EmDash runs registry plugins in a sandbox, and sandboxed plugins cannot render on public pages. A
47
+ block that draws on the page needs an Astro component loaded when the site builds, which only a
48
+ native plugin (an npm package listed in `astro.config.mjs`) can provide. Native plugins run with the
49
+ site's authority. This one declares no capabilities, makes no network requests and stores nothing:
50
+ it only reads the block an editor saved and draws it.
51
+
52
+ ## The Chart block
53
+
54
+ | Field | Used by | Notes |
55
+ |---|---|---|
56
+ | Chart | all | Bar, Line, Area (stacked), Scatter or Heatmap. |
57
+ | Title, subtitle | all | Shown above the chart. Optional. |
58
+ | Description | all | **Required.** What the chart shows and finds, in a sentence. It names the chart for screen readers. |
59
+ | Data | all | CSV, semicolon-separated, or pasted from a spreadsheet (tab-separated). The first row names the columns. Numbers such as `1,234` (quoted in CSV) are read as numbers; an empty cell is a gap. Up to 5000 rows and 50 columns. |
60
+ | Column along the x axis | all | Column names match without regard to case. Line and area charts read ISO dates (`2026-03-01`) as a time axis. |
61
+ | Column of values (y) | all | Bar charts count rows when it is blank. For a heatmap, the row column. |
62
+ | Series column | bar, line, area, scatter | Splits rows into series, told apart by color and by legend, labels or symbols. |
63
+ | Heatmap: column of cell values | heatmap | |
64
+ | Axis labels and units | all | Optional. A unit shows after its axis label. |
65
+ | Caption, notes | all | Optional. The caption is for method or caveats; notes are for definitions. |
66
+ | Source, source link, byline | all | Optional. The source line, linked when a link is given, sits beside a CSV download of the data. |
67
+ | Bar: several series, direction | bar | Stacked or side by side; vertical or horizontal. |
68
+ | y scale | line, scatter | Linear or logarithmic. |
69
+ | Regression | scatter | Least-squares line with its 95% confidence band. |
70
+ | Data table | all | Behind a "Data table" toggle (default) or hidden for screen readers only. Every chart has one. |
71
+ | Interactive | all | Adds Enarratio's enhancement script to the page. Off by default, so pages stay script-free. |
72
+
73
+ A block that cannot be drawn (a column that is not in the data, text in a number column, a missing
74
+ description) is replaced on the page by a short note saying why, and the reason is logged, so one
75
+ mistake never breaks the page. Text in every field is escaped.
76
+
77
+ ## Styles
78
+
79
+ The plugin writes the chart CSS once per page that has a chart. `enarratioPlugin({ styles })` picks
80
+ what it writes:
81
+
82
+ - `"theme"` (default): Enarratio's default theme, in light and dark. Charts follow the page's
83
+ `color-scheme`, so a site with `<meta name="color-scheme" content="light dark">` gets dark charts
84
+ in dark mode.
85
+ - `"base"`: Enarratio's layout rules without colors or fonts, for a site whose own design tokens
86
+ define the `--enarratio-*` properties. [Capsomer](https://github.com/DrDustinEdwards/capsomer)'s
87
+ tokens do.
88
+ - `"none"`: nothing; the site includes Enarratio's stylesheet itself.
89
+
90
+ To use your own Enarratio theme, or a theme that switches with an attribute instead of
91
+ `color-scheme`, wrap the renderer and pass it to `<PortableText>`, which takes precedence over the
92
+ plugin's:
93
+
94
+ ```astro
95
+ ---
96
+ // src/components/SiteChart.astro
97
+ import { Chart } from "enarratio-emdash/astro";
98
+ import { siteTheme } from "../site-theme";
99
+ ---
100
+ <Chart node={Astro.props.node} theme={siteTheme} stylesheetOptions={{ colorScheme: { dark: '[data-theme="dark"]' } }} />
101
+ ```
102
+
103
+ ```astro
104
+ <PortableText value={post.data.content} components={{ type: { enarratioChart: SiteChart } }} />
105
+ ```
106
+
107
+ Enarratio's [README](https://github.com/DrDustinEdwards/enarratio#themes) covers themes and the
108
+ custom properties.
109
+
110
+ ## How it draws
111
+
112
+ The block is rendered by [enarratio-astro](https://github.com/DrDustinEdwards/astro/tree/main/packages/enarratio-astro#readme)'s `Chart` component, so a chart
113
+ saved in EmDash looks the same as one in an Astro page or a Markdown file. Bar, line and area
114
+ charts are drawn by `enarratio/lite`, which builds the SVG as a string with no DOM, the cheapest
115
+ render on Cloudflare Workers. Scatter plots and heatmaps load `enarratio/plot` (Observable Plot and
116
+ linkedom) only on a page that has one.
117
+
118
+ Development, the demo site and releases: the [repository README](https://github.com/DrDustinEdwards/astro#readme).
119
+
120
+ ## License
121
+
122
+ [MIT](LICENSE). Copyright (c) 2026 Dustin Edwards.
@@ -0,0 +1,38 @@
1
+ import type { PortableTextBlockConfig } from "emdash";
2
+ /** The Portable Text `_type` of a chart block, and the key of its renderer. */
3
+ export declare const BLOCK_TYPE = "enarratioChart";
4
+ /** A saved Chart block: the editor form's values, every one optional until the chart is drawn. */
5
+ export interface ChartNode {
6
+ readonly _type: string;
7
+ readonly _key?: string;
8
+ readonly chart?: string;
9
+ readonly title?: string;
10
+ readonly subtitle?: string;
11
+ readonly alt?: string;
12
+ readonly caption?: string;
13
+ readonly notes?: string;
14
+ readonly source?: string;
15
+ readonly sourceHref?: string;
16
+ readonly byline?: string;
17
+ readonly data?: string;
18
+ readonly x?: string;
19
+ readonly y?: string;
20
+ readonly series?: string;
21
+ readonly value?: string;
22
+ readonly xLabel?: string;
23
+ readonly yLabel?: string;
24
+ readonly xUnit?: string;
25
+ readonly yUnit?: string;
26
+ readonly layout?: string;
27
+ readonly orientation?: string;
28
+ readonly scale?: string;
29
+ readonly regression?: boolean;
30
+ readonly dataTable?: string;
31
+ readonly interactive?: boolean;
32
+ }
33
+ /**
34
+ * The chart block as the editor shows it: one form, whose `action_id`s become the saved block's
35
+ * properties. Every field but the chart kind, description, data and x column is optional, and
36
+ * the labels say which chart kinds read it.
37
+ */
38
+ export declare const chartBlock: PortableTextBlockConfig;
package/dist/block.js ADDED
@@ -0,0 +1,120 @@
1
+ /** The Portable Text `_type` of a chart block, and the key of its renderer. */
2
+ export const BLOCK_TYPE = "enarratioChart";
3
+ /**
4
+ * The chart block as the editor shows it: one form, whose `action_id`s become the saved block's
5
+ * properties. Every field but the chart kind, description, data and x column is optional, and
6
+ * the labels say which chart kinds read it.
7
+ */
8
+ export const chartBlock = {
9
+ type: BLOCK_TYPE,
10
+ label: "Chart",
11
+ description: "An accessible chart drawn from data you paste",
12
+ category: "Media",
13
+ fields: [
14
+ {
15
+ type: "select",
16
+ action_id: "chart",
17
+ label: "Chart",
18
+ options: [
19
+ { label: "Bar", value: "bar" },
20
+ { label: "Line", value: "line" },
21
+ { label: "Area (stacked)", value: "area" },
22
+ { label: "Scatter", value: "scatter" },
23
+ { label: "Heatmap", value: "heatmap" },
24
+ ],
25
+ initial_value: "bar",
26
+ },
27
+ { type: "text_input", action_id: "title", label: "Title" },
28
+ {
29
+ type: "text_input",
30
+ action_id: "subtitle",
31
+ label: "Subtitle: how to read the chart (optional)",
32
+ },
33
+ {
34
+ type: "text_input",
35
+ action_id: "alt",
36
+ label: "Description (required)",
37
+ placeholder: "What the chart shows and what it finds, in a sentence. Screen readers read it.",
38
+ multiline: true,
39
+ },
40
+ {
41
+ type: "text_input",
42
+ action_id: "data",
43
+ label: "Data",
44
+ placeholder: "Paste from a spreadsheet or type CSV. The first row names the columns.\nyear,type,count\n2024,Guides,3",
45
+ multiline: true,
46
+ },
47
+ { type: "text_input", action_id: "x", label: "Column along the x axis" },
48
+ {
49
+ type: "text_input",
50
+ action_id: "y",
51
+ label: "Column of values (y)",
52
+ placeholder: "Bar charts count rows when this is blank. Heatmaps: the row column.",
53
+ },
54
+ {
55
+ type: "text_input",
56
+ action_id: "series",
57
+ label: "Column that splits rows into series (optional)",
58
+ },
59
+ { type: "text_input", action_id: "value", label: "Heatmap: column of cell values" },
60
+ { type: "text_input", action_id: "xLabel", label: "x axis label (optional)" },
61
+ { type: "text_input", action_id: "yLabel", label: "y axis label (optional)" },
62
+ { type: "text_input", action_id: "xUnit", label: "x axis unit, such as kg (optional)" },
63
+ { type: "text_input", action_id: "yUnit", label: "y axis unit (optional)" },
64
+ {
65
+ type: "text_input",
66
+ action_id: "caption",
67
+ label: "Caption: method or caveats (optional)",
68
+ },
69
+ { type: "text_input", action_id: "notes", label: "Notes: what a measure means (optional)" },
70
+ { type: "text_input", action_id: "source", label: "Source of the data (optional)" },
71
+ { type: "text_input", action_id: "sourceHref", label: "Link to the source, https (optional)" },
72
+ { type: "text_input", action_id: "byline", label: "Made by (optional)" },
73
+ {
74
+ type: "select",
75
+ action_id: "layout",
76
+ label: "Bar: several series",
77
+ options: [
78
+ { label: "Stacked", value: "stacked" },
79
+ { label: "Side by side", value: "grouped" },
80
+ ],
81
+ initial_value: "stacked",
82
+ },
83
+ {
84
+ type: "select",
85
+ action_id: "orientation",
86
+ label: "Bar: direction",
87
+ options: [
88
+ { label: "Vertical", value: "vertical" },
89
+ { label: "Horizontal", value: "horizontal" },
90
+ ],
91
+ initial_value: "vertical",
92
+ },
93
+ {
94
+ type: "select",
95
+ action_id: "scale",
96
+ label: "Line and scatter: y scale",
97
+ options: [
98
+ { label: "Linear", value: "linear" },
99
+ { label: "Logarithmic", value: "log" },
100
+ ],
101
+ initial_value: "linear",
102
+ },
103
+ { type: "toggle", action_id: "regression", label: "Scatter: regression line with 95% band" },
104
+ {
105
+ type: "select",
106
+ action_id: "dataTable",
107
+ label: "Data table",
108
+ options: [
109
+ { label: "Behind a Data table toggle", value: "details" },
110
+ { label: "Hidden, for screen readers only", value: "visually-hidden" },
111
+ ],
112
+ initial_value: "details",
113
+ },
114
+ {
115
+ type: "toggle",
116
+ action_id: "interactive",
117
+ label: "Interactive: hover details, keyboard navigation and filtering (adds a small script)",
118
+ },
119
+ ],
120
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Enarratio charts for EmDash: a Chart block for the Portable Text editor, drawn on the server
3
+ * by Enarratio as accessible HTML and SVG.
4
+ *
5
+ * @example
6
+ * // astro.config.mjs
7
+ * import { enarratioPlugin } from "enarratio-emdash";
8
+ * emdash({ plugins: [enarratioPlugin()] });
9
+ */
10
+ import type { PluginDescriptor, ResolvedPlugin } from "emdash";
11
+ export { BLOCK_TYPE, type ChartNode, chartBlock } from "./block.js";
12
+ /** Kept equal to package.json's version, which a test checks. */
13
+ export declare const VERSION = "0.4.1";
14
+ /** Options for {@link enarratioPlugin}. */
15
+ export interface EnarratioPluginOptions {
16
+ /**
17
+ * The CSS the plugin writes, once per page with a chart:
18
+ * - `"theme"` (default): Enarratio's default theme, colors and rules, light and dark.
19
+ * - `"base"`: the rules without colors or fonts, for a site whose own tokens define the
20
+ * `--enarratio-*` properties (Capsomer's tokens do).
21
+ * - `"none"`: nothing; the site includes Enarratio's stylesheet itself.
22
+ */
23
+ readonly styles?: "theme" | "base" | "none";
24
+ }
25
+ /** The descriptor a site lists in `astro.config.mjs`. */
26
+ export declare function enarratioPlugin(options?: EnarratioPluginOptions): PluginDescriptor;
27
+ /** The plugin EmDash loads at run time: the Chart block, and no capabilities, since it only draws. */
28
+ export declare function createPlugin(): ResolvedPlugin;
29
+ export default createPlugin;
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ import { definePlugin } from "emdash";
2
+ import { chartBlock } from "./block.js";
3
+ export { BLOCK_TYPE, chartBlock } from "./block.js";
4
+ /** Kept equal to package.json's version, which a test checks. */
5
+ export const VERSION = "0.4.1";
6
+ const ID = "enarratio";
7
+ const COMPONENTS = {
8
+ theme: "enarratio-emdash/astro",
9
+ base: "enarratio-emdash/astro/base",
10
+ none: "enarratio-emdash/astro/none",
11
+ };
12
+ /** The descriptor a site lists in `astro.config.mjs`. */
13
+ export function enarratioPlugin(options = {}) {
14
+ return {
15
+ id: ID,
16
+ version: VERSION,
17
+ format: "native",
18
+ entrypoint: "enarratio-emdash",
19
+ // Astro imports the renderer while it builds the site, before any plugin option reaches it,
20
+ // so each styles option is its own entry.
21
+ componentsEntry: COMPONENTS[options.styles ?? "theme"],
22
+ };
23
+ }
24
+ /** The plugin EmDash loads at run time: the Chart block, and no capabilities, since it only draws. */
25
+ export function createPlugin() {
26
+ return definePlugin({
27
+ id: ID,
28
+ version: VERSION,
29
+ capabilities: [],
30
+ admin: { portableTextBlocks: [chartBlock] },
31
+ });
32
+ }
33
+ export default createPlugin;
package/package.json CHANGED
@@ -1,6 +1,62 @@
1
1
  {
2
2
  "name": "enarratio-emdash",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.4.1",
4
+ "description": "EmDash plugin: a Chart block for the Portable Text editor, drawn on the server by Enarratio as accessible HTML and SVG",
5
+ "keywords": [
6
+ "emdash",
7
+ "emdash-plugin",
8
+ "astro",
9
+ "charts",
10
+ "accessibility",
11
+ "enarratio"
12
+ ],
13
+ "license": "MIT",
14
+ "author": "Dustin Edwards (https://dustinedwards.info)",
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "main": "./dist/index.js",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./astro": "./src/astro/index.ts",
24
+ "./astro/base": "./src/astro/base.ts",
25
+ "./astro/none": "./src/astro/none.ts",
26
+ "./package.json": "./package.json"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "src",
31
+ "CHANGELOG.md",
32
+ "README.md"
33
+ ],
34
+ "engines": {
35
+ "node": ">=22.22"
36
+ },
37
+ "peerDependencies": {
38
+ "astro": "7.4.4",
39
+ "emdash": "1.4.0"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public",
43
+ "tag": "latest"
44
+ },
45
+ "homepage": "https://github.com/DrDustinEdwards/astro/tree/main/packages/enarratio-emdash#readme",
46
+ "bugs": {
47
+ "url": "https://github.com/DrDustinEdwards/astro/issues"
48
+ },
49
+ "repository": {
50
+ "type": "git",
51
+ "url": "git+https://github.com/DrDustinEdwards/astro.git",
52
+ "directory": "packages/enarratio-emdash"
53
+ },
54
+ "scripts": {
55
+ "build": "tsc -p tsconfig.build.json",
56
+ "test": "vitest run",
57
+ "prepare": "npm run build"
58
+ },
59
+ "dependencies": {
60
+ "enarratio-astro": "0.4.1"
61
+ }
62
+ }
@@ -0,0 +1,46 @@
1
+ ---
2
+ /**
3
+ * Renders a saved Chart block with enarratio-astro's Chart. EmDash passes the block as `node`;
4
+ * the plugin's `styles` option picks the entry (astro, astro/base, astro/none) and so the `styles`
5
+ * prop. A site can also wrap this component to pass its own Enarratio theme, and register the
6
+ * wrapper through `<PortableText components={{ type: { enarratioChart: MyChart } }}>`.
7
+ */
8
+ import type { StyleOptions } from "enarratio-astro";
9
+ import Chart from "enarratio-astro/Chart.astro";
10
+ import type { ChartNode } from "../block.ts";
11
+
12
+ interface Props extends StyleOptions {
13
+ node: ChartNode;
14
+ }
15
+
16
+ const { node, ...style } = Astro.props;
17
+ ---
18
+
19
+ <Chart
20
+ type={node.chart}
21
+ data={node.data ?? ""}
22
+ alt={node.alt}
23
+ title={node.title}
24
+ subtitle={node.subtitle}
25
+ caption={node.caption}
26
+ notes={node.notes}
27
+ source={node.source}
28
+ sourceHref={node.sourceHref}
29
+ byline={node.byline}
30
+ x={node.x}
31
+ y={node.y}
32
+ series={node.series}
33
+ value={node.value}
34
+ xLabel={node.xLabel}
35
+ yLabel={node.yLabel}
36
+ xUnit={node.xUnit}
37
+ yUnit={node.yUnit}
38
+ layout={node.layout}
39
+ orientation={node.orientation}
40
+ scale={node.scale}
41
+ regression={node.regression}
42
+ dataTable={node.dataTable}
43
+ id={node._key ? `chart-${node._key}` : undefined}
44
+ interactive={node.interactive === true}
45
+ {...style}
46
+ />
@@ -0,0 +1,6 @@
1
+ ---
2
+ /** The Chart block with Enarratio's rules but no colors: the site's tokens define them. */
3
+ import Chart from "./Chart.astro";
4
+ ---
5
+
6
+ <Chart node={Astro.props.node} styles="base" />
@@ -0,0 +1,6 @@
1
+ ---
2
+ /** The Chart block with no CSS of its own: the site includes Enarratio's stylesheet itself. */
3
+ import Chart from "./Chart.astro";
4
+ ---
5
+
6
+ <Chart node={Astro.props.node} styles="none" />
@@ -0,0 +1,4 @@
1
+ /** The renderer for `enarratioPlugin({ styles: "base" })`: Enarratio's rules, the site's colors. */
2
+ import ChartBase from "./ChartBase.astro";
3
+
4
+ export const blockComponents = { enarratioChart: ChartBase };
@@ -0,0 +1,7 @@
1
+ // The type checker does not read .astro files; Astro's own compiler checks them when a site builds.
2
+ declare module "*.astro" {
3
+ import type { AstroComponentFactory } from "astro/runtime/server/index.js";
4
+
5
+ const component: AstroComponentFactory;
6
+ export default component;
7
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The site-side renderer EmDash merges into `<PortableText>`, writing the default theme's CSS.
3
+ * The key must equal the block's `_type` (BLOCK_TYPE). `./base` and `./none` are the same
4
+ * renderer for the plugin's other `styles` options.
5
+ */
6
+ import Chart from "./Chart.astro";
7
+
8
+ export const blockComponents = { enarratioChart: Chart };
9
+ export { Chart };
@@ -0,0 +1,4 @@
1
+ /** The renderer for `enarratioPlugin({ styles: "none" })`: no CSS from the plugin. */
2
+ import ChartUnstyled from "./ChartUnstyled.astro";
3
+
4
+ export const blockComponents = { enarratioChart: ChartUnstyled };
package/src/block.ts ADDED
@@ -0,0 +1,154 @@
1
+ import type { PortableTextBlockConfig } from "emdash";
2
+
3
+ /** The Portable Text `_type` of a chart block, and the key of its renderer. */
4
+ export const BLOCK_TYPE = "enarratioChart";
5
+
6
+ /** A saved Chart block: the editor form's values, every one optional until the chart is drawn. */
7
+ export interface ChartNode {
8
+ readonly _type: string;
9
+ readonly _key?: string;
10
+ readonly chart?: string;
11
+ readonly title?: string;
12
+ readonly subtitle?: string;
13
+ readonly alt?: string;
14
+ readonly caption?: string;
15
+ readonly notes?: string;
16
+ readonly source?: string;
17
+ readonly sourceHref?: string;
18
+ readonly byline?: string;
19
+ readonly data?: string;
20
+ readonly x?: string;
21
+ readonly y?: string;
22
+ readonly series?: string;
23
+ readonly value?: string;
24
+ readonly xLabel?: string;
25
+ readonly yLabel?: string;
26
+ readonly xUnit?: string;
27
+ readonly yUnit?: string;
28
+ readonly layout?: string;
29
+ readonly orientation?: string;
30
+ readonly scale?: string;
31
+ readonly regression?: boolean;
32
+ readonly dataTable?: string;
33
+ readonly interactive?: boolean;
34
+ }
35
+
36
+ /**
37
+ * The chart block as the editor shows it: one form, whose `action_id`s become the saved block's
38
+ * properties. Every field but the chart kind, description, data and x column is optional, and
39
+ * the labels say which chart kinds read it.
40
+ */
41
+ export const chartBlock: PortableTextBlockConfig = {
42
+ type: BLOCK_TYPE,
43
+ label: "Chart",
44
+ description: "An accessible chart drawn from data you paste",
45
+ category: "Media",
46
+ fields: [
47
+ {
48
+ type: "select",
49
+ action_id: "chart",
50
+ label: "Chart",
51
+ options: [
52
+ { label: "Bar", value: "bar" },
53
+ { label: "Line", value: "line" },
54
+ { label: "Area (stacked)", value: "area" },
55
+ { label: "Scatter", value: "scatter" },
56
+ { label: "Heatmap", value: "heatmap" },
57
+ ],
58
+ initial_value: "bar",
59
+ },
60
+ { type: "text_input", action_id: "title", label: "Title" },
61
+ {
62
+ type: "text_input",
63
+ action_id: "subtitle",
64
+ label: "Subtitle: how to read the chart (optional)",
65
+ },
66
+ {
67
+ type: "text_input",
68
+ action_id: "alt",
69
+ label: "Description (required)",
70
+ placeholder: "What the chart shows and what it finds, in a sentence. Screen readers read it.",
71
+ multiline: true,
72
+ },
73
+ {
74
+ type: "text_input",
75
+ action_id: "data",
76
+ label: "Data",
77
+ placeholder:
78
+ "Paste from a spreadsheet or type CSV. The first row names the columns.\nyear,type,count\n2024,Guides,3",
79
+ multiline: true,
80
+ },
81
+ { type: "text_input", action_id: "x", label: "Column along the x axis" },
82
+ {
83
+ type: "text_input",
84
+ action_id: "y",
85
+ label: "Column of values (y)",
86
+ placeholder: "Bar charts count rows when this is blank. Heatmaps: the row column.",
87
+ },
88
+ {
89
+ type: "text_input",
90
+ action_id: "series",
91
+ label: "Column that splits rows into series (optional)",
92
+ },
93
+ { type: "text_input", action_id: "value", label: "Heatmap: column of cell values" },
94
+ { type: "text_input", action_id: "xLabel", label: "x axis label (optional)" },
95
+ { type: "text_input", action_id: "yLabel", label: "y axis label (optional)" },
96
+ { type: "text_input", action_id: "xUnit", label: "x axis unit, such as kg (optional)" },
97
+ { type: "text_input", action_id: "yUnit", label: "y axis unit (optional)" },
98
+ {
99
+ type: "text_input",
100
+ action_id: "caption",
101
+ label: "Caption: method or caveats (optional)",
102
+ },
103
+ { type: "text_input", action_id: "notes", label: "Notes: what a measure means (optional)" },
104
+ { type: "text_input", action_id: "source", label: "Source of the data (optional)" },
105
+ { type: "text_input", action_id: "sourceHref", label: "Link to the source, https (optional)" },
106
+ { type: "text_input", action_id: "byline", label: "Made by (optional)" },
107
+ {
108
+ type: "select",
109
+ action_id: "layout",
110
+ label: "Bar: several series",
111
+ options: [
112
+ { label: "Stacked", value: "stacked" },
113
+ { label: "Side by side", value: "grouped" },
114
+ ],
115
+ initial_value: "stacked",
116
+ },
117
+ {
118
+ type: "select",
119
+ action_id: "orientation",
120
+ label: "Bar: direction",
121
+ options: [
122
+ { label: "Vertical", value: "vertical" },
123
+ { label: "Horizontal", value: "horizontal" },
124
+ ],
125
+ initial_value: "vertical",
126
+ },
127
+ {
128
+ type: "select",
129
+ action_id: "scale",
130
+ label: "Line and scatter: y scale",
131
+ options: [
132
+ { label: "Linear", value: "linear" },
133
+ { label: "Logarithmic", value: "log" },
134
+ ],
135
+ initial_value: "linear",
136
+ },
137
+ { type: "toggle", action_id: "regression", label: "Scatter: regression line with 95% band" },
138
+ {
139
+ type: "select",
140
+ action_id: "dataTable",
141
+ label: "Data table",
142
+ options: [
143
+ { label: "Behind a Data table toggle", value: "details" },
144
+ { label: "Hidden, for screen readers only", value: "visually-hidden" },
145
+ ],
146
+ initial_value: "details",
147
+ },
148
+ {
149
+ type: "toggle",
150
+ action_id: "interactive",
151
+ label: "Interactive: hover details, keyboard navigation and filtering (adds a small script)",
152
+ },
153
+ ],
154
+ };
package/src/index.ts ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Enarratio charts for EmDash: a Chart block for the Portable Text editor, drawn on the server
3
+ * by Enarratio as accessible HTML and SVG.
4
+ *
5
+ * @example
6
+ * // astro.config.mjs
7
+ * import { enarratioPlugin } from "enarratio-emdash";
8
+ * emdash({ plugins: [enarratioPlugin()] });
9
+ */
10
+ import type { PluginDescriptor, ResolvedPlugin } from "emdash";
11
+ import { definePlugin } from "emdash";
12
+ import { chartBlock } from "./block.js";
13
+
14
+ export { BLOCK_TYPE, type ChartNode, chartBlock } from "./block.js";
15
+
16
+ /** Kept equal to package.json's version, which a test checks. */
17
+ export const VERSION = "0.4.1";
18
+ const ID = "enarratio";
19
+
20
+ /** Options for {@link enarratioPlugin}. */
21
+ export interface EnarratioPluginOptions {
22
+ /**
23
+ * The CSS the plugin writes, once per page with a chart:
24
+ * - `"theme"` (default): Enarratio's default theme, colors and rules, light and dark.
25
+ * - `"base"`: the rules without colors or fonts, for a site whose own tokens define the
26
+ * `--enarratio-*` properties (Capsomer's tokens do).
27
+ * - `"none"`: nothing; the site includes Enarratio's stylesheet itself.
28
+ */
29
+ readonly styles?: "theme" | "base" | "none";
30
+ }
31
+
32
+ const COMPONENTS = {
33
+ theme: "enarratio-emdash/astro",
34
+ base: "enarratio-emdash/astro/base",
35
+ none: "enarratio-emdash/astro/none",
36
+ } as const;
37
+
38
+ /** The descriptor a site lists in `astro.config.mjs`. */
39
+ export function enarratioPlugin(options: EnarratioPluginOptions = {}): PluginDescriptor {
40
+ return {
41
+ id: ID,
42
+ version: VERSION,
43
+ format: "native",
44
+ entrypoint: "enarratio-emdash",
45
+ // Astro imports the renderer while it builds the site, before any plugin option reaches it,
46
+ // so each styles option is its own entry.
47
+ componentsEntry: COMPONENTS[options.styles ?? "theme"],
48
+ };
49
+ }
50
+
51
+ /** The plugin EmDash loads at run time: the Chart block, and no capabilities, since it only draws. */
52
+ export function createPlugin(): ResolvedPlugin {
53
+ return definePlugin({
54
+ id: ID,
55
+ version: VERSION,
56
+ capabilities: [],
57
+ admin: { portableTextBlocks: [chartBlock] },
58
+ });
59
+ }
60
+
61
+ export default createPlugin;