@barmark/render 0.0.0 → 0.2.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 ADDED
@@ -0,0 +1,104 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@barmark/render` are recorded here. The format
4
+ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
5
+ package uses [semantic versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.0] - 2026-11-01
10
+
11
+ Easier to read, and the bar numbers easier to scan. Every size below was
12
+ chosen on barmark.dev. The chart looks different, so check pages that
13
+ style it. Requires `barmark` 0.2.0 or later.
14
+
15
+ ### Changed
16
+
17
+ - **Chords are 16px by default** (`--barmark-size` was `1.25rem`, now `1rem`),
18
+ and no label is under 12px: the meta line, headings, bar numbers, lyrics,
19
+ repeat counts, triplet marks and diagnostics are all larger in proportion.
20
+ - **Bar numbers sit flush left** in a fixed gutter, on their row's chord
21
+ baseline. Section headings, lyrics, note lines and mid-chart changes
22
+ start at the first bar line, so a `3/4` never sits among the numbers.
23
+ - **Chords start at the left** of their share of the bar instead of centred,
24
+ and no bar is narrower than a bar holding D7sus4.
25
+ - **A change written mid-chart names itself** and marks where it takes
26
+ effect: "→ Time 3/4 · Tempo ♩ = 120", with a rule, not a lone "3/4".
27
+ - **`###` note lines are italic** at weight 400 (Edwin has no bold, so 600 was
28
+ faked), and lyrics and note lines use Georgia, which has a true italic.
29
+ - **A single ringed note** has room inside its ring and sits on the chord
30
+ baseline (it was 1px low).
31
+
32
+ ### Added
33
+
34
+ - **A `text` property on `<barmark-chart>`** for live editing: set it and the
35
+ chart redraws, batched with attribute changes. While it is a string it
36
+ wins over `src` (no fetch) and the script child; `null` returns to them.
37
+ Text set before the element is defined is kept.
38
+ - **`--barmark-text-font` and `--barmark-label-font`**: the fonts for lyrics
39
+ and note lines, and for the meta line, mid-chart changes, headings and
40
+ diagnostics.
41
+ - **`--barmark-directive` and `--barmark-note`**: the colours of mid-chart
42
+ changes and of note lines.
43
+
44
+ ## [0.1.0] - 2026-10-31
45
+
46
+ The first release: barmark charts as an evenly spaced bar grid, as HTML
47
+ that needs no script or as the `<barmark-chart>` web component. Requires
48
+ `barmark` 0.2.0 or later.
49
+
50
+ ### Added
51
+
52
+ - **`layoutChart(text)`**: the chart as data to draw. Bar lines become rows
53
+ with their play-order bar numbers (numbering stops after an open repeat),
54
+ `%` becomes the bar it copies, and text lines, directives and lines with
55
+ errors keep their place.
56
+ - **Replays are drawn in full** with their own bar numbers. A replay's own
57
+ text lines (verse 2's words) replace the written section's in order;
58
+ `->` marks the bar the parser cut. Optional rehearsal letters (A, B, …,
59
+ AA) mark each section occurrence in play order.
60
+ - **`renderChart(text, options)`** returns the chart as a `<barmark-chart>`
61
+ element holding a declarative shadow root, and `renderChartUnwrapped`
62
+ the bare markup (`bm-` classes) for pages that bring the stylesheet.
63
+ Options: `theme`, `barNumbers`, `rehearsalLetters`, `diagnostics`. All
64
+ chart text is escaped.
65
+ - **Chords in canonical form,** with ♯ and ♭ glyphs and the extension as a
66
+ superscript; single notes ringed; note sets in braces with the root
67
+ underlined; groups under a tuplet count; fermatas, one-bar meters, N.C.,
68
+ silence and `?` each drawn distinctly.
69
+ - **Spoken names:** every bar reads to a screen reader as music ("bar 9: A
70
+ seven sus four, hold"), with sections labelled by their headings and
71
+ repeats spoken ("play 2 times").
72
+ - **Styles, light and dark.** The chart follows the system's colour scheme
73
+ (`theme: "auto"`), or is forced `light` or `dark`; printing is always
74
+ light, and Windows High Contrast keeps bar lines and marks visible. Both
75
+ palettes meet WCAG AA contrast. Everything is sized in em from
76
+ `--barmark-size`, and a section too wide for its container wraps its
77
+ rows rather than scrolling sideways. Pages restyle the chart through
78
+ `--barmark-font`, `--barmark-size`, `--barmark-ink`, `--barmark-paper`,
79
+ `--barmark-muted`, `--barmark-rule` and `--barmark-accent`, or
80
+ `::part()`.
81
+ - **Edwin ships with the package,** unmodified, with its OFL licence
82
+ (`@barmark/render/fonts/Edwin-Roman.otf`). Pass `fontUrl` to emit its
83
+ `@font-face` rule before the chart, or call `fontFaceCss(url)` once per
84
+ page. The family is registered as "barmark Edwin"; without it the chart
85
+ falls back to Georgia or another serif.
86
+ - **`<barmark-chart>`** (`import "@barmark/render/element"`): renders a
87
+ chart from its `src` URL or a child `<script type="text/barmark">`, and
88
+ again when either changes. Attributes (and matching properties):
89
+ `theme`, `bar-numbers="off"`, `rehearsal-letters`. It registers the font
90
+ itself, reuses a server-rendered shadow root (which still follows
91
+ `theme` and `bar-numbers`), fetches a `src` once, renders once per batch
92
+ of changes, and shows a failed load as an error inside the chart.
93
+
94
+ ### Technical
95
+
96
+ - **Browser tests** (Playwright, Chromium) in CI: the chart looks pixel
97
+ for pixel the same on a page with hostile CSS, themes follow the system
98
+ and the page's custom properties, long rows wrap on a phone and at 400%
99
+ zoom without sideways scrolling, the element renders and re-renders, and
100
+ a screen reader hears "bar 9: D". Run them with `bun run test:browser`.
101
+
102
+ [Unreleased]: https://github.com/kevinpeckham/barmark/compare/render-v0.2.0...HEAD
103
+ [0.2.0]: https://github.com/kevinpeckham/barmark/compare/render-v0.1.0...render-v0.2.0
104
+ [0.1.0]: https://github.com/kevinpeckham/barmark/releases/tag/render-v0.1.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kevin Peckham
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 +1,158 @@
1
- First release coming soon.
1
+ # @barmark/render
2
+
3
+ Renders [barmark](https://github.com/kevinpeckham/barmark) chord charts as
4
+ a clean, evenly spaced bar grid that looks the same on any page, follows
5
+ light and dark mode, scales with the reader's text size, and reads to a
6
+ screen reader as music.
7
+
8
+ ```
9
+ npm install @barmark/render
10
+ ```
11
+
12
+ ## As HTML (no browser JavaScript needed)
13
+
14
+ ```js
15
+ import { renderChart } from "@barmark/render";
16
+
17
+ const html = renderChart(chartText, { fontUrl: "/fonts/Edwin-Roman.otf" });
18
+ ```
19
+
20
+ `renderChart` returns a `<barmark-chart>` element holding a declarative
21
+ shadow root: put it in any page, server-rendered or static, and it shows
22
+ the chart with no script. The shadow root keeps the page's CSS out, so a
23
+ blog's resets and fonts cannot rearrange the bars. Browsers attach a
24
+ declarative shadow root only while parsing HTML: to insert the markup from
25
+ script, use `setHTMLUnsafe` (or the element below), not `innerHTML`.
26
+
27
+ `renderChartUnwrapped(text, options)` returns the same markup without the
28
+ shadow root (classes prefixed `bm-`), for pages that include `stylesheet`
29
+ themselves.
30
+
31
+ ## As a web component
32
+
33
+ ```html
34
+ <script type="module">import "@barmark/render/element";</script>
35
+
36
+ <barmark-chart src="/charts/peaceful-dreams.chart.md"></barmark-chart>
37
+
38
+ <barmark-chart theme="dark" rehearsal-letters>
39
+ <script type="text/barmark">
40
+ ## Verse
41
+ | D | A | Bm | G |
42
+ </script>
43
+ </barmark-chart>
44
+ ```
45
+
46
+ The element renders from its `text` property, its `src` URL, or a child
47
+ `<script type="text/barmark">` (indentation is removed), and renders again
48
+ whenever one changes; it fetches a `src` once, and renders once however
49
+ many properties and attributes change together. A chart rendered on the
50
+ server by `renderChart` keeps its music until it has a source of its own,
51
+ and still follows `theme` and `bar-numbers`.
52
+
53
+ For a live editor, set `text`:
54
+
55
+ ```js
56
+ const chart = document.querySelector("barmark-chart");
57
+ editor.addEventListener("input", () => { chart.text = editor.value; });
58
+ ```
59
+
60
+ While `text` is a string it wins over `src` and the script child, and no
61
+ fetch happens; set it to `null` to go back to them. Any string is safe:
62
+ the chart escapes it, and problems show as diagnostics, never errors.
63
+ There is no `text` attribute: chart text belongs in the property or the
64
+ script child.
65
+
66
+ The element registers the font itself, from the `fonts/` folder beside its
67
+ own module. If your bundler moves that file, register it yourself before
68
+ the element loads, with `<style data-barmark-font>` holding
69
+ `fontFaceCss(yourUrl)`; the element then leaves it alone.
70
+
71
+ ## Options and attributes
72
+
73
+ | `renderChart` option | Element attribute (property) | Default | |
74
+ |---|---|---|---|
75
+ | `theme` | `theme` (`theme`) | `"auto"` | `"auto"` follows the system; `"light"` or `"dark"` force one |
76
+ | `barNumbers` | `bar-numbers="off"` (`barNumbers`) | `true` | bar numbers at the start of each row |
77
+ | `rehearsalLetters` | `rehearsal-letters` (`rehearsalLetters`) | `false` | boxed A, B, C… on each section as played |
78
+ | `diagnostics` | | `false` | list the chart's errors and warnings below it |
79
+ | `fontUrl` | | none | emit Edwin's `@font-face` rule before the chart |
80
+ | (the `text` argument) | (`text`) | `null` | the chart to draw; wins over `src` and the script child |
81
+
82
+ ## Styling on purpose
83
+
84
+ Set these custom properties on the element or anything above it:
85
+
86
+ | Property | What it sets |
87
+ |---|---|
88
+ | `--barmark-font` | the chords' typeface |
89
+ | `--barmark-text-font` | lyrics and `###` note lines (default Georgia) |
90
+ | `--barmark-label-font` | the meta line, mid-chart changes, headings and diagnostics (default the system sans) |
91
+ | `--barmark-size` | the chord size everything scales from (default `1rem`) |
92
+ | `--barmark-ink` | chords and text |
93
+ | `--barmark-paper` | the background |
94
+ | `--barmark-muted` | holds, bar numbers, braces, meta lines |
95
+ | `--barmark-rule` | bar lines |
96
+ | `--barmark-accent` | errors |
97
+ | `--barmark-directive` | mid-chart changes (default the muted colour) |
98
+ | `--barmark-note` | `###` note lines (default the ink colour) |
99
+
100
+ ```css
101
+ barmark-chart {
102
+ --barmark-size: 0.9rem;
103
+ --barmark-paper: transparent;
104
+ --barmark-text-font: "Noto Serif", Georgia, serif;
105
+ }
106
+ barmark-chart::part(heading) {
107
+ color: teal;
108
+ }
109
+ ```
110
+
111
+ `::part()` names: `title`, `meta`, `heading`, `bar`, `chord`, `text`,
112
+ `bar-number`, `rehearsal`. These, the custom properties and the
113
+ attributes are the public surface: changing them is a breaking change.
114
+
115
+ ## Light, dark and print
116
+
117
+ Both palettes meet WCAG AA contrast (4.5:1 for text). `theme="auto"`
118
+ follows `prefers-color-scheme`; a page with its own dark-mode switch sets
119
+ `theme` or the colour properties. Printing always uses the light palette,
120
+ and Windows High Contrast keeps bar lines and marks visible.
121
+
122
+ ## Accessibility
123
+
124
+ - Everything is sized in em from `--barmark-size`, so text size and zoom
125
+ scale the chart; a section too wide for its container wraps its rows
126
+ instead of scrolling sideways.
127
+ - Every bar has a spoken name built from the parsed chart: "bar 9: A seven
128
+ sus four, hold", "triplet: D, E, F sharp minor", "note E", "C over E".
129
+ Sections are labelled by their headings, and repeats are spoken ("play
130
+ 2 times").
131
+
132
+ ## The font
133
+
134
+ Edwin, MuseScore's text font for music, ships unmodified in
135
+ `@barmark/render/fonts/Edwin-Roman.otf` with its SIL Open Font License
136
+ (`fonts/OFL.txt`). It is registered under the family name "barmark
137
+ Edwin". Serve the file and pass its URL as `fontUrl`, or call
138
+ `fontFaceCss(url)` once per page; without it the chart uses Georgia or
139
+ another serif.
140
+
141
+ ## What it draws
142
+
143
+ Bar numbers sit in a gutter at the left edge; section headings, lyrics,
144
+ note lines and mid-chart changes start at the first bar line. Chords start
145
+ at the left of their share of the bar. A change written mid-chart names
146
+ itself: `time: 3/4, tempo: 120` draws as "→ Time 3/4 · Tempo ♩ = 120".
147
+ At the default size chords are 16px and no label is under 12px.
148
+
149
+ Chords appear in their canonical form whatever the author typed (`Cø`
150
+ shows as Cm⁷(♭5)). `%` is drawn as the bar it copies; a replayed section
151
+ is drawn in full with its own bar numbers, and its own lyrics replace the
152
+ written section's. A line with an error shows as written, underlined,
153
+ with its message. `layoutChart(text)` returns all of this as data, if
154
+ you want to draw it yourself.
155
+
156
+ ## License
157
+
158
+ MIT. Edwin is © MuseScore Limited, under the SIL Open Font License 1.1.
@@ -0,0 +1,18 @@
1
+ /** Chords and notes as display pieces: ♯ and ♭ glyphs, the suffix split for superscript. */
2
+ import type { ChordParts } from "barmark";
3
+ export type ChordPieces = {
4
+ root: string;
5
+ /** m, dim, aug or 5: set on the baseline. */
6
+ quality: string;
7
+ /** Extension, sus and tones: set as a superscript. */
8
+ superscript: string;
9
+ bass: string | null;
10
+ };
11
+ /** `F#` → `F♯`, `b9` → `♭9`, `Bbb` → `B𝄫`. */
12
+ export declare function glyphs(text: string): string;
13
+ export declare function typesetChord(parts: ChordParts): ChordPieces;
14
+ /** A note set's label (`E1`, `F#`) as name and octave. */
15
+ export declare function splitPitch(label: string): {
16
+ name: string;
17
+ octave: string | null;
18
+ };
package/dist/chord.js ADDED
@@ -0,0 +1,24 @@
1
+ const ACCIDENTALS = [
2
+ [/##/g, "𝄪"],
3
+ [/bb(?=\d|$|\))/g, "𝄫"],
4
+ [/#/g, "♯"],
5
+ [/(?<=[A-G]|^|\(|,)b/g, "♭"],
6
+ ];
7
+ /** `F#` → `F♯`, `b9` → `♭9`, `Bbb` → `B𝄫`. */
8
+ export function glyphs(text) {
9
+ return ACCIDENTALS.reduce((t, [re, g]) => t.replace(re, g), text);
10
+ }
11
+ export function typesetChord(parts) {
12
+ const tones = parts.tones.length ? `(${parts.tones.join(",")})` : "";
13
+ return {
14
+ root: glyphs(parts.root),
15
+ quality: parts.quality,
16
+ superscript: glyphs(`${parts.extension ?? ""}${parts.sus ?? ""}${tones}`),
17
+ bass: parts.bass ? glyphs(parts.bass) : null,
18
+ };
19
+ }
20
+ /** A note set's label (`E1`, `F#`) as name and octave. */
21
+ export function splitPitch(label) {
22
+ const m = /^([A-G](?:##|bb|#|b)?)(-?\d+)?$/.exec(label);
23
+ return { name: glyphs(m?.[1] ?? label), octave: m?.[2] ?? null };
24
+ }
@@ -0,0 +1,16 @@
1
+ import { type RenderOptions } from "./html.js";
2
+ type Attributes = {
3
+ getAttribute(name: string): string | null;
4
+ hasAttribute(name: string): boolean;
5
+ };
6
+ export declare function optionsFromAttributes(el: Attributes): RenderOptions;
7
+ export type ChartSource = "text" | "src" | "script" | "none";
8
+ /** What a chart draws: its `text` while that is a string, else `src`, else its script child. */
9
+ export declare function chartSource(from: {
10
+ text: string | null;
11
+ src: string;
12
+ script: boolean;
13
+ }): ChartSource;
14
+ /** Chart text from a script element: blank edges and common indentation removed. */
15
+ export declare function inlineChartText(text: string): string;
16
+ export {};
@@ -0,0 +1,162 @@
1
+ /**
2
+ * `<barmark-chart>`: renders itself from its `text` property, a `src` URL
3
+ * or a child `<script type="text/barmark">`, and again whenever one
4
+ * changes. A shadow root rendered on the server is reused. Importing this
5
+ * module registers the element (once) and the font (once per document).
6
+ */
7
+ import { FONT_FAMILY, fontFaceCss } from "./font.js";
8
+ import { loadErrorContent, shadowContent, THEMES } from "./html.js";
9
+ export function optionsFromAttributes(el) {
10
+ const theme = el.getAttribute("theme");
11
+ return {
12
+ theme: THEMES.find((t) => t === theme) ?? "auto",
13
+ barNumbers: el.getAttribute("bar-numbers") !== "off",
14
+ rehearsalLetters: el.hasAttribute("rehearsal-letters"),
15
+ };
16
+ }
17
+ /** What a chart draws: its `text` while that is a string, else `src`, else its script child. */
18
+ export function chartSource(from) {
19
+ if (from.text !== null)
20
+ return "text";
21
+ if (from.src)
22
+ return "src";
23
+ return from.script ? "script" : "none";
24
+ }
25
+ /** Chart text from a script element: blank edges and common indentation removed. */
26
+ export function inlineChartText(text) {
27
+ const lines = text.replace(/^\s*\n|\n\s*$/g, "").split("\n");
28
+ const indents = lines.filter((l) => l.trim()).map((l) => /^[ \t]*/.exec(l)?.[0].length ?? 0);
29
+ const cut = Math.min(...indents, Number.POSITIVE_INFINITY);
30
+ return lines.map((l) => l.slice(Number.isFinite(cut) ? cut : 0).trimEnd()).join("\n");
31
+ }
32
+ function registerFont(doc) {
33
+ if (doc.querySelector("style[data-barmark-font]"))
34
+ return;
35
+ const style = doc.createElement("style");
36
+ style.dataset.barmarkFont = FONT_FAMILY;
37
+ style.textContent = fontFaceCss(new URL("../fonts/Edwin-Roman.otf", import.meta.url).href);
38
+ doc.head.append(style);
39
+ }
40
+ async function fetchText(src) {
41
+ const response = await fetch(src);
42
+ if (!response.ok)
43
+ throw new Error(`${response.status} ${response.statusText}`.trim());
44
+ return response.text();
45
+ }
46
+ function defineElement() {
47
+ class BarmarkChart extends HTMLElement {
48
+ static observedAttributes = ["src", "theme", "bar-numbers", "rehearsal-letters"];
49
+ #observer = new MutationObserver(() => this.#schedule());
50
+ #renders = 0;
51
+ #scheduled = false;
52
+ /** The text fetched for `src`, kept so a change of options does not fetch again. */
53
+ #fetched = null;
54
+ #text = null;
55
+ connectedCallback() {
56
+ // Text set before the element was defined landed on the instance: route it through the setter.
57
+ if (Object.hasOwn(this, "text")) {
58
+ const text = this.text ?? null;
59
+ delete this.text;
60
+ this.text = text;
61
+ }
62
+ registerFont(this.ownerDocument);
63
+ this.#observer.observe(this, { childList: true, subtree: true, characterData: true });
64
+ this.#schedule();
65
+ }
66
+ disconnectedCallback() {
67
+ this.#observer.disconnect();
68
+ }
69
+ attributeChangedCallback() {
70
+ if (this.isConnected)
71
+ this.#schedule();
72
+ }
73
+ /** Chart text to draw; while it is a string it wins over `src` and a script child. */
74
+ get text() {
75
+ return this.#text;
76
+ }
77
+ set text(v) {
78
+ this.#text = v === null || v === undefined ? null : String(v);
79
+ if (this.isConnected)
80
+ this.#schedule();
81
+ }
82
+ get src() {
83
+ return this.getAttribute("src") ?? "";
84
+ }
85
+ set src(v) {
86
+ this.setAttribute("src", v);
87
+ }
88
+ get theme() {
89
+ return optionsFromAttributes(this).theme ?? "auto";
90
+ }
91
+ set theme(v) {
92
+ this.setAttribute("theme", v);
93
+ }
94
+ get barNumbers() {
95
+ return optionsFromAttributes(this).barNumbers ?? true;
96
+ }
97
+ set barNumbers(v) {
98
+ if (v)
99
+ this.removeAttribute("bar-numbers");
100
+ else
101
+ this.setAttribute("bar-numbers", "off");
102
+ }
103
+ get rehearsalLetters() {
104
+ return this.hasAttribute("rehearsal-letters");
105
+ }
106
+ set rehearsalLetters(v) {
107
+ this.toggleAttribute("rehearsal-letters", v);
108
+ }
109
+ /** Every change in one task (an upgrade sets each attribute in turn) renders once. */
110
+ #schedule() {
111
+ if (this.#scheduled)
112
+ return;
113
+ this.#scheduled = true;
114
+ queueMicrotask(() => {
115
+ this.#scheduled = false;
116
+ void this.#render();
117
+ });
118
+ }
119
+ async #render() {
120
+ const script = this.querySelector('script[type="text/barmark"]');
121
+ const options = optionsFromAttributes(this);
122
+ const source = chartSource({ text: this.#text, src: this.src, script: script !== null });
123
+ // With no source of its own, a server-rendered chart keeps its music and takes the options it can.
124
+ if (source === "none")
125
+ return this.#restyle(options);
126
+ const render = ++this.#renders;
127
+ const content = await this.#content(source, script, options);
128
+ if (render !== this.#renders)
129
+ return;
130
+ (this.shadowRoot ?? this.attachShadow({ mode: "open" })).innerHTML = content;
131
+ }
132
+ async #content(source, script, options) {
133
+ if (source === "text")
134
+ return shadowContent(this.#text ?? "", options);
135
+ if (source !== "src")
136
+ return shadowContent(inlineChartText(script?.textContent ?? ""), options);
137
+ try {
138
+ return shadowContent(await this.#fetch(this.src), options);
139
+ }
140
+ catch (e) {
141
+ this.#fetched = null;
142
+ return loadErrorContent(`Could not load ${this.src}: ${e.message}`, options);
143
+ }
144
+ }
145
+ #fetch(src) {
146
+ if (this.#fetched?.src !== src)
147
+ this.#fetched = { src, text: fetchText(src) };
148
+ return this.#fetched.text;
149
+ }
150
+ #restyle(options) {
151
+ const chart = this.shadowRoot?.querySelector(".bm-chart");
152
+ chart?.setAttribute("data-theme", options.theme ?? "auto");
153
+ if (options.barNumbers === false)
154
+ chart?.setAttribute("data-bar-numbers", "off");
155
+ else
156
+ chart?.removeAttribute("data-bar-numbers");
157
+ }
158
+ }
159
+ customElements.define("barmark-chart", BarmarkChart);
160
+ }
161
+ if (typeof customElements !== "undefined" && !customElements.get("barmark-chart"))
162
+ defineElement();
@@ -0,0 +1,2 @@
1
+ /** Text from the chart, safe in element content and in quoted attributes. */
2
+ export declare const escapeHtml: (text: string) => string;
package/dist/escape.js ADDED
@@ -0,0 +1,9 @@
1
+ const ENTITIES = {
2
+ "&": "&amp;",
3
+ "<": "&lt;",
4
+ ">": "&gt;",
5
+ '"': "&quot;",
6
+ "'": "&#39;",
7
+ };
8
+ /** Text from the chart, safe in element content and in quoted attributes. */
9
+ export const escapeHtml = (text) => text.replace(/[&<>"']/g, (c) => ENTITIES[c] ?? c);
package/dist/font.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Edwin is registered on the host page, not in the shadow root (some
3
+ * browsers ignore @font-face there), under its own family name so it can
4
+ * never collide with a page's fonts. The file ships unmodified (OFL).
5
+ */
6
+ export declare const FONT_FAMILY = "barmark Edwin";
7
+ /** One @font-face rule for the font at `url`; the URL cannot close the string or the <style>. */
8
+ export declare function fontFaceCss(url: string): string;
package/dist/font.js ADDED
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Edwin is registered on the host page, not in the shadow root (some
3
+ * browsers ignore @font-face there), under its own family name so it can
4
+ * never collide with a page's fonts. The file ships unmodified (OFL).
5
+ */
6
+ export const FONT_FAMILY = "barmark Edwin";
7
+ /** One @font-face rule for the font at `url`; the URL cannot close the string or the <style>. */
8
+ export function fontFaceCss(url) {
9
+ const safe = url.replace(/["\\<>\n\r]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
10
+ return `@font-face{font-family:"${FONT_FAMILY}";src:url("${safe}") format("opentype");font-display:swap}`;
11
+ }
package/dist/html.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export type RenderOptions = {
2
+ theme?: "auto" | "light" | "dark";
3
+ barNumbers?: boolean;
4
+ rehearsalLetters?: boolean;
5
+ /** List the chart's errors and warnings below it. */
6
+ diagnostics?: boolean;
7
+ /** Where the page serves Edwin-Roman.otf: emits its @font-face rule before the chart. */
8
+ fontUrl?: string;
9
+ };
10
+ export declare function renderChart(text: string, options?: RenderOptions): string;
11
+ export declare const THEMES: readonly ["auto", "light", "dark"];
12
+ /** What goes in the shadow root: the styles and the chart. */
13
+ export declare const shadowContent: (text: string, options?: RenderOptions) => string;
14
+ /** A chart that could not be loaded: the message, shown where the chart would be. */
15
+ export declare const loadErrorContent: (message: string, options?: RenderOptions) => string;
16
+ export declare function renderChartUnwrapped(text: string, options?: RenderOptions): string;