enarratio-emdash 0.0.0-stage → 0.1.0
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 +21 -0
- package/LICENSE +21 -0
- package/README.md +120 -3
- package/dist/block.d.ts +31 -0
- package/dist/block.js +109 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +33 -0
- package/package.json +62 -6
- package/src/astro/Chart.astro +39 -0
- package/src/astro/ChartBase.astro +6 -0
- package/src/astro/ChartUnstyled.astro +6 -0
- package/src/astro/base.ts +4 -0
- package/src/astro/env.d.ts +7 -0
- package/src/astro/index.ts +9 -0
- package/src/astro/none.ts +4 -0
- package/src/block.ts +136 -0
- package/src/index.ts +61 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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.1.0] - 2026-10-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- The Chart block for EmDash's Portable Text editor: bar, line, stacked area, scatter and heatmap
|
|
15
|
+
charts drawn on the server by enarratio-astro 0.1.0 (Enarratio 0.3.1) from pasted CSV or
|
|
16
|
+
spreadsheet data, with a required description, a data table, and light and dark themes.
|
|
17
|
+
- Optional interactivity per chart, from Enarratio's enhancement script, added only to pages with
|
|
18
|
+
an interactive chart.
|
|
19
|
+
- `enarratioPlugin({ styles })`: `"theme"` (default), `"base"` for sites whose tokens define the
|
|
20
|
+
colors, or `"none"`.
|
|
21
|
+
- 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,120 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# enarratio-emdash
|
|
2
|
+
|
|
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 | 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, caption | all | Optional. The caption is for source, method or caveats. |
|
|
65
|
+
| Bar: several series, direction | bar | Stacked or side by side; vertical or horizontal. |
|
|
66
|
+
| y scale | line, scatter | Linear or logarithmic. |
|
|
67
|
+
| Regression | scatter | Least-squares line with its 95% confidence band. |
|
|
68
|
+
| Data table | all | Behind a "Data table" toggle (default) or hidden for screen readers only. Every chart has one. |
|
|
69
|
+
| Interactive | all | Adds Enarratio's enhancement script to the page. Off by default, so pages stay script-free. |
|
|
70
|
+
|
|
71
|
+
A block that cannot be drawn (a column that is not in the data, text in a number column, a missing
|
|
72
|
+
description) is replaced on the page by a short note saying why, and the reason is logged, so one
|
|
73
|
+
mistake never breaks the page. Text in every field is escaped.
|
|
74
|
+
|
|
75
|
+
## Styles
|
|
76
|
+
|
|
77
|
+
The plugin writes the chart CSS once per page that has a chart. `enarratioPlugin({ styles })` picks
|
|
78
|
+
what it writes:
|
|
79
|
+
|
|
80
|
+
- `"theme"` (default): Enarratio's default theme, in light and dark. Charts follow the page's
|
|
81
|
+
`color-scheme`, so a site with `<meta name="color-scheme" content="light dark">` gets dark charts
|
|
82
|
+
in dark mode.
|
|
83
|
+
- `"base"`: Enarratio's layout rules without colors or fonts, for a site whose own design tokens
|
|
84
|
+
define the `--enarratio-*` properties. [Capsomer](https://github.com/DrDustinEdwards/capsomer)'s
|
|
85
|
+
tokens do.
|
|
86
|
+
- `"none"`: nothing; the site includes Enarratio's stylesheet itself.
|
|
87
|
+
|
|
88
|
+
To use your own Enarratio theme, or a theme that switches with an attribute instead of
|
|
89
|
+
`color-scheme`, wrap the renderer and pass it to `<PortableText>`, which takes precedence over the
|
|
90
|
+
plugin's:
|
|
91
|
+
|
|
92
|
+
```astro
|
|
93
|
+
---
|
|
94
|
+
// src/components/SiteChart.astro
|
|
95
|
+
import { Chart } from "enarratio-emdash/astro";
|
|
96
|
+
import { labTheme } from "../lab-theme";
|
|
97
|
+
---
|
|
98
|
+
<Chart node={Astro.props.node} theme={labTheme} stylesheetOptions={{ colorScheme: { dark: '[data-theme="dark"]' } }} />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
```astro
|
|
102
|
+
<PortableText value={post.data.content} components={{ type: { enarratioChart: SiteChart } }} />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Enarratio's [README](https://github.com/DrDustinEdwards/enarratio#themes) covers themes and the
|
|
106
|
+
custom properties.
|
|
107
|
+
|
|
108
|
+
## How it draws
|
|
109
|
+
|
|
110
|
+
The block is rendered by [enarratio-astro](https://github.com/DrDustinEdwards/astro/tree/main/packages/enarratio-astro#readme)'s `Chart` component, so a chart
|
|
111
|
+
saved in EmDash looks the same as one in an Astro page or a Markdown file. Bar, line and area
|
|
112
|
+
charts are drawn by `enarratio/lite`, which builds the SVG as a string with no DOM, the cheapest
|
|
113
|
+
render on Cloudflare Workers. Scatter plots and heatmaps load `enarratio/plot` (Observable Plot and
|
|
114
|
+
linkedom) only on a page that has one.
|
|
115
|
+
|
|
116
|
+
Development, the demo site and releases: the [repository README](https://github.com/DrDustinEdwards/astro#readme).
|
|
117
|
+
|
|
118
|
+
## License
|
|
119
|
+
|
|
120
|
+
[MIT](LICENSE). Copyright (c) 2026 Dustin Edwards.
|
package/dist/block.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
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 alt?: string;
|
|
11
|
+
readonly caption?: string;
|
|
12
|
+
readonly data?: string;
|
|
13
|
+
readonly x?: string;
|
|
14
|
+
readonly y?: string;
|
|
15
|
+
readonly series?: string;
|
|
16
|
+
readonly value?: string;
|
|
17
|
+
readonly xLabel?: string;
|
|
18
|
+
readonly yLabel?: string;
|
|
19
|
+
readonly layout?: string;
|
|
20
|
+
readonly orientation?: string;
|
|
21
|
+
readonly scale?: string;
|
|
22
|
+
readonly regression?: boolean;
|
|
23
|
+
readonly dataTable?: string;
|
|
24
|
+
readonly interactive?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The chart block as the editor shows it: one form, whose `action_id`s become the saved block's
|
|
28
|
+
* properties. Every field but the chart kind, description, data and x column is optional, and
|
|
29
|
+
* the labels say which chart kinds read it.
|
|
30
|
+
*/
|
|
31
|
+
export declare const chartBlock: PortableTextBlockConfig;
|
package/dist/block.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
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: "alt",
|
|
31
|
+
label: "Description (required)",
|
|
32
|
+
placeholder: "What the chart shows and what it finds, in a sentence. Screen readers read it.",
|
|
33
|
+
multiline: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: "text_input",
|
|
37
|
+
action_id: "data",
|
|
38
|
+
label: "Data",
|
|
39
|
+
placeholder: "Paste from a spreadsheet or type CSV. The first row names the columns.\nyear,type,count\n2024,Talks,3",
|
|
40
|
+
multiline: true,
|
|
41
|
+
},
|
|
42
|
+
{ type: "text_input", action_id: "x", label: "Column along the x axis" },
|
|
43
|
+
{
|
|
44
|
+
type: "text_input",
|
|
45
|
+
action_id: "y",
|
|
46
|
+
label: "Column of values (y)",
|
|
47
|
+
placeholder: "Bar charts count rows when this is blank. Heatmaps: the row column.",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: "text_input",
|
|
51
|
+
action_id: "series",
|
|
52
|
+
label: "Column that splits rows into series (optional)",
|
|
53
|
+
},
|
|
54
|
+
{ type: "text_input", action_id: "value", label: "Heatmap: column of cell values" },
|
|
55
|
+
{ type: "text_input", action_id: "xLabel", label: "x axis label (optional)" },
|
|
56
|
+
{ type: "text_input", action_id: "yLabel", label: "y axis label (optional)" },
|
|
57
|
+
{
|
|
58
|
+
type: "text_input",
|
|
59
|
+
action_id: "caption",
|
|
60
|
+
label: "Caption: source, method or caveats (optional)",
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
type: "select",
|
|
64
|
+
action_id: "layout",
|
|
65
|
+
label: "Bar: several series",
|
|
66
|
+
options: [
|
|
67
|
+
{ label: "Stacked", value: "stacked" },
|
|
68
|
+
{ label: "Side by side", value: "grouped" },
|
|
69
|
+
],
|
|
70
|
+
initial_value: "stacked",
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: "select",
|
|
74
|
+
action_id: "orientation",
|
|
75
|
+
label: "Bar: direction",
|
|
76
|
+
options: [
|
|
77
|
+
{ label: "Vertical", value: "vertical" },
|
|
78
|
+
{ label: "Horizontal", value: "horizontal" },
|
|
79
|
+
],
|
|
80
|
+
initial_value: "vertical",
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
type: "select",
|
|
84
|
+
action_id: "scale",
|
|
85
|
+
label: "Line and scatter: y scale",
|
|
86
|
+
options: [
|
|
87
|
+
{ label: "Linear", value: "linear" },
|
|
88
|
+
{ label: "Logarithmic", value: "log" },
|
|
89
|
+
],
|
|
90
|
+
initial_value: "linear",
|
|
91
|
+
},
|
|
92
|
+
{ type: "toggle", action_id: "regression", label: "Scatter: regression line with 95% band" },
|
|
93
|
+
{
|
|
94
|
+
type: "select",
|
|
95
|
+
action_id: "dataTable",
|
|
96
|
+
label: "Data table",
|
|
97
|
+
options: [
|
|
98
|
+
{ label: "Behind a Data table toggle", value: "details" },
|
|
99
|
+
{ label: "Hidden, for screen readers only", value: "visually-hidden" },
|
|
100
|
+
],
|
|
101
|
+
initial_value: "details",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
type: "toggle",
|
|
105
|
+
action_id: "interactive",
|
|
106
|
+
label: "Interactive: hover details, keyboard navigation and filtering (adds a small script)",
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -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.1.0";
|
|
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.1.0";
|
|
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
|
-
{
|
|
2
|
-
"name": "enarratio-emdash",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "enarratio-emdash",
|
|
3
|
+
"version": "0.1.0",
|
|
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.16"
|
|
36
|
+
},
|
|
37
|
+
"peerDependencies": {
|
|
38
|
+
"astro": ">=6.0.0",
|
|
39
|
+
"emdash": ">=1.0.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.1.0"
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
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
|
+
caption={node.caption}
|
|
25
|
+
x={node.x}
|
|
26
|
+
y={node.y}
|
|
27
|
+
series={node.series}
|
|
28
|
+
value={node.value}
|
|
29
|
+
xLabel={node.xLabel}
|
|
30
|
+
yLabel={node.yLabel}
|
|
31
|
+
layout={node.layout}
|
|
32
|
+
orientation={node.orientation}
|
|
33
|
+
scale={node.scale}
|
|
34
|
+
regression={node.regression}
|
|
35
|
+
dataTable={node.dataTable}
|
|
36
|
+
id={node._key ? `chart-${node._key}` : undefined}
|
|
37
|
+
interactive={node.interactive === true}
|
|
38
|
+
{...style}
|
|
39
|
+
/>
|
|
@@ -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 };
|
package/src/block.ts
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
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 alt?: string;
|
|
13
|
+
readonly caption?: string;
|
|
14
|
+
readonly data?: string;
|
|
15
|
+
readonly x?: string;
|
|
16
|
+
readonly y?: string;
|
|
17
|
+
readonly series?: string;
|
|
18
|
+
readonly value?: string;
|
|
19
|
+
readonly xLabel?: string;
|
|
20
|
+
readonly yLabel?: string;
|
|
21
|
+
readonly layout?: string;
|
|
22
|
+
readonly orientation?: string;
|
|
23
|
+
readonly scale?: string;
|
|
24
|
+
readonly regression?: boolean;
|
|
25
|
+
readonly dataTable?: string;
|
|
26
|
+
readonly interactive?: boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The chart block as the editor shows it: one form, whose `action_id`s become the saved block's
|
|
31
|
+
* properties. Every field but the chart kind, description, data and x column is optional, and
|
|
32
|
+
* the labels say which chart kinds read it.
|
|
33
|
+
*/
|
|
34
|
+
export const chartBlock: PortableTextBlockConfig = {
|
|
35
|
+
type: BLOCK_TYPE,
|
|
36
|
+
label: "Chart",
|
|
37
|
+
description: "An accessible chart drawn from data you paste",
|
|
38
|
+
category: "Media",
|
|
39
|
+
fields: [
|
|
40
|
+
{
|
|
41
|
+
type: "select",
|
|
42
|
+
action_id: "chart",
|
|
43
|
+
label: "Chart",
|
|
44
|
+
options: [
|
|
45
|
+
{ label: "Bar", value: "bar" },
|
|
46
|
+
{ label: "Line", value: "line" },
|
|
47
|
+
{ label: "Area (stacked)", value: "area" },
|
|
48
|
+
{ label: "Scatter", value: "scatter" },
|
|
49
|
+
{ label: "Heatmap", value: "heatmap" },
|
|
50
|
+
],
|
|
51
|
+
initial_value: "bar",
|
|
52
|
+
},
|
|
53
|
+
{ type: "text_input", action_id: "title", label: "Title" },
|
|
54
|
+
{
|
|
55
|
+
type: "text_input",
|
|
56
|
+
action_id: "alt",
|
|
57
|
+
label: "Description (required)",
|
|
58
|
+
placeholder: "What the chart shows and what it finds, in a sentence. Screen readers read it.",
|
|
59
|
+
multiline: true,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
type: "text_input",
|
|
63
|
+
action_id: "data",
|
|
64
|
+
label: "Data",
|
|
65
|
+
placeholder:
|
|
66
|
+
"Paste from a spreadsheet or type CSV. The first row names the columns.\nyear,type,count\n2024,Talks,3",
|
|
67
|
+
multiline: true,
|
|
68
|
+
},
|
|
69
|
+
{ type: "text_input", action_id: "x", label: "Column along the x axis" },
|
|
70
|
+
{
|
|
71
|
+
type: "text_input",
|
|
72
|
+
action_id: "y",
|
|
73
|
+
label: "Column of values (y)",
|
|
74
|
+
placeholder: "Bar charts count rows when this is blank. Heatmaps: the row column.",
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
type: "text_input",
|
|
78
|
+
action_id: "series",
|
|
79
|
+
label: "Column that splits rows into series (optional)",
|
|
80
|
+
},
|
|
81
|
+
{ type: "text_input", action_id: "value", label: "Heatmap: column of cell values" },
|
|
82
|
+
{ type: "text_input", action_id: "xLabel", label: "x axis label (optional)" },
|
|
83
|
+
{ type: "text_input", action_id: "yLabel", label: "y axis label (optional)" },
|
|
84
|
+
{
|
|
85
|
+
type: "text_input",
|
|
86
|
+
action_id: "caption",
|
|
87
|
+
label: "Caption: source, method or caveats (optional)",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
type: "select",
|
|
91
|
+
action_id: "layout",
|
|
92
|
+
label: "Bar: several series",
|
|
93
|
+
options: [
|
|
94
|
+
{ label: "Stacked", value: "stacked" },
|
|
95
|
+
{ label: "Side by side", value: "grouped" },
|
|
96
|
+
],
|
|
97
|
+
initial_value: "stacked",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
type: "select",
|
|
101
|
+
action_id: "orientation",
|
|
102
|
+
label: "Bar: direction",
|
|
103
|
+
options: [
|
|
104
|
+
{ label: "Vertical", value: "vertical" },
|
|
105
|
+
{ label: "Horizontal", value: "horizontal" },
|
|
106
|
+
],
|
|
107
|
+
initial_value: "vertical",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
type: "select",
|
|
111
|
+
action_id: "scale",
|
|
112
|
+
label: "Line and scatter: y scale",
|
|
113
|
+
options: [
|
|
114
|
+
{ label: "Linear", value: "linear" },
|
|
115
|
+
{ label: "Logarithmic", value: "log" },
|
|
116
|
+
],
|
|
117
|
+
initial_value: "linear",
|
|
118
|
+
},
|
|
119
|
+
{ type: "toggle", action_id: "regression", label: "Scatter: regression line with 95% band" },
|
|
120
|
+
{
|
|
121
|
+
type: "select",
|
|
122
|
+
action_id: "dataTable",
|
|
123
|
+
label: "Data table",
|
|
124
|
+
options: [
|
|
125
|
+
{ label: "Behind a Data table toggle", value: "details" },
|
|
126
|
+
{ label: "Hidden, for screen readers only", value: "visually-hidden" },
|
|
127
|
+
],
|
|
128
|
+
initial_value: "details",
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
type: "toggle",
|
|
132
|
+
action_id: "interactive",
|
|
133
|
+
label: "Interactive: hover details, keyboard navigation and filtering (adds a small script)",
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
};
|
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.1.0";
|
|
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;
|