@barmark/render 0.1.0 → 0.2.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 +56 -1
- package/README.md +33 -8
- package/dist/element.d.ts +7 -0
- package/dist/element.js +37 -10
- package/dist/html.js +23 -13
- package/dist/styles.d.ts +1 -5
- package/dist/styles.js +79 -24
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,59 @@ package uses [semantic versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.1] - 2026-11-01
|
|
10
|
+
|
|
11
|
+
Two fixes found while moving barmark.dev to the element. Depends on
|
|
12
|
+
`barmark` ^0.3.0, which warns when a chart has no bar lines.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Triplet brackets work on any paper.** The bracket is drawn either
|
|
17
|
+
side of its number, leaving a gap, instead of under it with the number
|
|
18
|
+
painted over in the paper colour. A see-through `--barmark-paper` used
|
|
19
|
+
to show the number in a box.
|
|
20
|
+
- **A chart from `renderChart` keeps its options when the element
|
|
21
|
+
loads.** The `<barmark-chart>` it wraps the chart in now carries them as
|
|
22
|
+
attributes (`theme`, `bar-numbers="off"`, `rehearsal-letters`). Before,
|
|
23
|
+
a page that rendered a dark chart on the server and imported
|
|
24
|
+
`@barmark/render/element` saw it switch to the system theme, and lose
|
|
25
|
+
its rehearsal letters once given new `text`.
|
|
26
|
+
|
|
27
|
+
## [0.2.0] - 2026-11-01
|
|
28
|
+
|
|
29
|
+
Easier to read, and the bar numbers easier to scan. Every size below was
|
|
30
|
+
chosen on barmark.dev. The chart looks different, so check pages that
|
|
31
|
+
style it. Requires `barmark` 0.2.0 or later.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- **Chords are 16px by default** (`--barmark-size` was `1.25rem`, now `1rem`),
|
|
36
|
+
and no label is under 12px: the meta line, headings, bar numbers, lyrics,
|
|
37
|
+
repeat counts, triplet marks and diagnostics are all larger in proportion.
|
|
38
|
+
- **Bar numbers sit flush left** in a fixed gutter, on their row's chord
|
|
39
|
+
baseline. Section headings, lyrics, note lines and mid-chart changes
|
|
40
|
+
start at the first bar line, so a `3/4` never sits among the numbers.
|
|
41
|
+
- **Chords start at the left** of their share of the bar instead of centred,
|
|
42
|
+
and no bar is narrower than a bar holding D7sus4.
|
|
43
|
+
- **A change written mid-chart names itself** and marks where it takes
|
|
44
|
+
effect: "→ Time 3/4 · Tempo ♩ = 120", with a rule, not a lone "3/4".
|
|
45
|
+
- **`###` note lines are italic** at weight 400 (Edwin has no bold, so 600 was
|
|
46
|
+
faked), and lyrics and note lines use Georgia, which has a true italic.
|
|
47
|
+
- **A single ringed note** has room inside its ring and sits on the chord
|
|
48
|
+
baseline (it was 1px low).
|
|
49
|
+
|
|
50
|
+
### Added
|
|
51
|
+
|
|
52
|
+
- **A `text` property on `<barmark-chart>`** for live editing: set it and the
|
|
53
|
+
chart redraws, batched with attribute changes. While it is a string it
|
|
54
|
+
wins over `src` (no fetch) and the script child; `null` returns to them.
|
|
55
|
+
Text set before the element is defined is kept.
|
|
56
|
+
- **`--barmark-text-font` and `--barmark-label-font`**: the fonts for lyrics
|
|
57
|
+
and note lines, and for the meta line, mid-chart changes, headings and
|
|
58
|
+
diagnostics.
|
|
59
|
+
- **`--barmark-directive` and `--barmark-note`**: the colours of mid-chart
|
|
60
|
+
changes and of note lines.
|
|
61
|
+
|
|
9
62
|
## [0.1.0] - 2026-10-31
|
|
10
63
|
|
|
11
64
|
The first release: barmark charts as an evenly spaced bar grid, as HTML
|
|
@@ -64,5 +117,7 @@ that needs no script or as the `<barmark-chart>` web component. Requires
|
|
|
64
117
|
zoom without sideways scrolling, the element renders and re-renders, and
|
|
65
118
|
a screen reader hears "bar 9: D". Run them with `bun run test:browser`.
|
|
66
119
|
|
|
67
|
-
[Unreleased]: https://github.com/kevinpeckham/barmark/compare/render-v0.1
|
|
120
|
+
[Unreleased]: https://github.com/kevinpeckham/barmark/compare/render-v0.2.1...HEAD
|
|
121
|
+
[0.2.1]: https://github.com/kevinpeckham/barmark/compare/render-v0.2.0...render-v0.2.1
|
|
122
|
+
[0.2.0]: https://github.com/kevinpeckham/barmark/compare/render-v0.1.0...render-v0.2.0
|
|
68
123
|
[0.1.0]: https://github.com/kevinpeckham/barmark/releases/tag/render-v0.1.0
|
package/README.md
CHANGED
|
@@ -43,12 +43,25 @@ themselves.
|
|
|
43
43
|
</barmark-chart>
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
The element renders from its `src` URL, or
|
|
46
|
+
The element renders from its `text` property, its `src` URL, or a child
|
|
47
47
|
`<script type="text/barmark">` (indentation is removed), and renders again
|
|
48
|
-
whenever
|
|
49
|
-
|
|
50
|
-
by `renderChart` keeps its music until it has a source of its own,
|
|
51
|
-
still follows `theme` and `bar-numbers`.
|
|
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.
|
|
52
65
|
|
|
53
66
|
The element registers the font itself, from the `fonts/` folder beside its
|
|
54
67
|
own module. If your bundler moves that file, register it yourself before
|
|
@@ -64,6 +77,7 @@ the element loads, with `<style data-barmark-font>` holding
|
|
|
64
77
|
| `rehearsalLetters` | `rehearsal-letters` (`rehearsalLetters`) | `false` | boxed A, B, C… on each section as played |
|
|
65
78
|
| `diagnostics` | | `false` | list the chart's errors and warnings below it |
|
|
66
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 |
|
|
67
81
|
|
|
68
82
|
## Styling on purpose
|
|
69
83
|
|
|
@@ -71,18 +85,23 @@ Set these custom properties on the element or anything above it:
|
|
|
71
85
|
|
|
72
86
|
| Property | What it sets |
|
|
73
87
|
|---|---|
|
|
74
|
-
| `--barmark-font` | the
|
|
75
|
-
| `--barmark-
|
|
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`) |
|
|
76
92
|
| `--barmark-ink` | chords and text |
|
|
77
93
|
| `--barmark-paper` | the background |
|
|
78
94
|
| `--barmark-muted` | holds, bar numbers, braces, meta lines |
|
|
79
95
|
| `--barmark-rule` | bar lines |
|
|
80
96
|
| `--barmark-accent` | errors |
|
|
97
|
+
| `--barmark-directive` | mid-chart changes (default the muted colour) |
|
|
98
|
+
| `--barmark-note` | `###` note lines (default the ink colour) |
|
|
81
99
|
|
|
82
100
|
```css
|
|
83
101
|
barmark-chart {
|
|
84
|
-
--barmark-size:
|
|
102
|
+
--barmark-size: 0.9rem;
|
|
85
103
|
--barmark-paper: transparent;
|
|
104
|
+
--barmark-text-font: "Noto Serif", Georgia, serif;
|
|
86
105
|
}
|
|
87
106
|
barmark-chart::part(heading) {
|
|
88
107
|
color: teal;
|
|
@@ -121,6 +140,12 @@ another serif.
|
|
|
121
140
|
|
|
122
141
|
## What it draws
|
|
123
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
|
+
|
|
124
149
|
Chords appear in their canonical form whatever the author typed (`Cø`
|
|
125
150
|
shows as Cm⁷(♭5)). `%` is drawn as the bar it copies; a replayed section
|
|
126
151
|
is drawn in full with its own bar numbers, and its own lyrics replace the
|
package/dist/element.d.ts
CHANGED
|
@@ -4,6 +4,13 @@ type Attributes = {
|
|
|
4
4
|
hasAttribute(name: string): boolean;
|
|
5
5
|
};
|
|
6
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;
|
|
7
14
|
/** Chart text from a script element: blank edges and common indentation removed. */
|
|
8
15
|
export declare function inlineChartText(text: string): string;
|
|
9
16
|
export {};
|
package/dist/element.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `<barmark-chart>`: renders itself from a `src` URL
|
|
3
|
-
* `<script type="text/barmark">`, and again whenever
|
|
4
|
-
* shadow root rendered on the server is reused. Importing this
|
|
5
|
-
* registers the element (once) and the font (once per document).
|
|
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
6
|
*/
|
|
7
7
|
import { FONT_FAMILY, fontFaceCss } from "./font.js";
|
|
8
8
|
import { loadErrorContent, shadowContent, THEMES } from "./html.js";
|
|
@@ -14,6 +14,14 @@ export function optionsFromAttributes(el) {
|
|
|
14
14
|
rehearsalLetters: el.hasAttribute("rehearsal-letters"),
|
|
15
15
|
};
|
|
16
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
|
+
}
|
|
17
25
|
/** Chart text from a script element: blank edges and common indentation removed. */
|
|
18
26
|
export function inlineChartText(text) {
|
|
19
27
|
const lines = text.replace(/^\s*\n|\n\s*$/g, "").split("\n");
|
|
@@ -43,7 +51,14 @@ function defineElement() {
|
|
|
43
51
|
#scheduled = false;
|
|
44
52
|
/** The text fetched for `src`, kept so a change of options does not fetch again. */
|
|
45
53
|
#fetched = null;
|
|
54
|
+
#text = null;
|
|
46
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
|
+
}
|
|
47
62
|
registerFont(this.ownerDocument);
|
|
48
63
|
this.#observer.observe(this, { childList: true, subtree: true, characterData: true });
|
|
49
64
|
this.#schedule();
|
|
@@ -55,6 +70,15 @@ function defineElement() {
|
|
|
55
70
|
if (this.isConnected)
|
|
56
71
|
this.#schedule();
|
|
57
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
|
+
}
|
|
58
82
|
get src() {
|
|
59
83
|
return this.getAttribute("src") ?? "";
|
|
60
84
|
}
|
|
@@ -95,27 +119,30 @@ function defineElement() {
|
|
|
95
119
|
async #render() {
|
|
96
120
|
const script = this.querySelector('script[type="text/barmark"]');
|
|
97
121
|
const options = optionsFromAttributes(this);
|
|
122
|
+
const source = chartSource({ text: this.#text, src: this.src, script: script !== null });
|
|
98
123
|
// With no source of its own, a server-rendered chart keeps its music and takes the options it can.
|
|
99
|
-
if (
|
|
124
|
+
if (source === "none")
|
|
100
125
|
return this.#restyle(options);
|
|
101
126
|
const render = ++this.#renders;
|
|
102
|
-
const content = await this.#content(script, options);
|
|
127
|
+
const content = await this.#content(source, script, options);
|
|
103
128
|
if (render !== this.#renders)
|
|
104
129
|
return;
|
|
105
130
|
(this.shadowRoot ?? this.attachShadow({ mode: "open" })).innerHTML = content;
|
|
106
131
|
}
|
|
107
|
-
async #content(script, options) {
|
|
108
|
-
if (
|
|
132
|
+
async #content(source, script, options) {
|
|
133
|
+
if (source === "text")
|
|
134
|
+
return shadowContent(this.#text ?? "", options);
|
|
135
|
+
if (source !== "src")
|
|
109
136
|
return shadowContent(inlineChartText(script?.textContent ?? ""), options);
|
|
110
137
|
try {
|
|
111
|
-
return shadowContent(await this.#
|
|
138
|
+
return shadowContent(await this.#fetch(this.src), options);
|
|
112
139
|
}
|
|
113
140
|
catch (e) {
|
|
114
141
|
this.#fetched = null;
|
|
115
142
|
return loadErrorContent(`Could not load ${this.src}: ${e.message}`, options);
|
|
116
143
|
}
|
|
117
144
|
}
|
|
118
|
-
#
|
|
145
|
+
#fetch(src) {
|
|
119
146
|
if (this.#fetched?.src !== src)
|
|
120
147
|
this.#fetched = { src, text: fetchText(src) };
|
|
121
148
|
return this.#fetched.text;
|
package/dist/html.js
CHANGED
|
@@ -3,9 +3,18 @@ import { escapeHtml } from "./escape.js";
|
|
|
3
3
|
import { fontFaceCss } from "./font.js";
|
|
4
4
|
import { layoutChart } from "./model.js";
|
|
5
5
|
import { speakBar } from "./speech.js";
|
|
6
|
-
import { FIT_STEPS, stylesheet } from "./styles.js";
|
|
6
|
+
import { FIT_STEPS, MIN_BAR, stylesheet } from "./styles.js";
|
|
7
7
|
export function renderChart(text, options = {}) {
|
|
8
|
-
return `${fontStyle(options)}<barmark-chart><template shadowrootmode="open">${shadowContent(text, options)}</template></barmark-chart>`;
|
|
8
|
+
return `${fontStyle(options)}<barmark-chart${elementAttributes(options)}><template shadowrootmode="open">${shadowContent(text, options)}</template></barmark-chart>`;
|
|
9
|
+
}
|
|
10
|
+
/** The options as the element's attributes, so it keeps them when it loads or takes new text. */
|
|
11
|
+
function elementAttributes(options) {
|
|
12
|
+
const theme = themeOf(options);
|
|
13
|
+
return [
|
|
14
|
+
theme === "auto" ? "" : ` theme="${theme}"`,
|
|
15
|
+
options.barNumbers === false ? ' bar-numbers="off"' : "",
|
|
16
|
+
options.rehearsalLetters ? " rehearsal-letters" : "",
|
|
17
|
+
].join("");
|
|
9
18
|
}
|
|
10
19
|
export const THEMES = ["auto", "light", "dark"];
|
|
11
20
|
/** Only a known theme reaches the markup: hosts may pass any string through. */
|
|
@@ -32,16 +41,16 @@ function headerHtml(model) {
|
|
|
32
41
|
return "";
|
|
33
42
|
return `<header class="bm-header">${title}${meta ? `<p class="bm-meta" part="meta">${meta}</p>` : ""}</header>`;
|
|
34
43
|
}
|
|
35
|
-
/** "D major · 4/4 · ♩ = 145 · swing", escaped. */
|
|
36
|
-
function metaText(d) {
|
|
44
|
+
/** "D major · 4/4 · ♩ = 145 · swing", escaped; labelled, "Key D major · Time 4/4 · …". */
|
|
45
|
+
function metaText(d, labelled = false) {
|
|
37
46
|
return [
|
|
38
|
-
d.key ? `${glyphs(d.key.tonic)} ${d.key.mode}` : "",
|
|
39
|
-
d.time ? `${d.time.n}/${d.time.d}` : "",
|
|
40
|
-
d.tempo ? `♩ = ${d.tempo}` : "",
|
|
41
|
-
d.feel ?? "",
|
|
47
|
+
["Key", d.key ? `${glyphs(d.key.tonic)} ${d.key.mode}` : ""],
|
|
48
|
+
["Time", d.time ? `${d.time.n}/${d.time.d}` : ""],
|
|
49
|
+
["Tempo", d.tempo ? `♩ = ${d.tempo}` : ""],
|
|
50
|
+
["Feel", d.feel ?? ""],
|
|
42
51
|
]
|
|
43
|
-
.filter(
|
|
44
|
-
.map(escapeHtml)
|
|
52
|
+
.filter(([, value]) => value)
|
|
53
|
+
.map(([label, value]) => escapeHtml(labelled ? `${label} ${value}` : (value ?? "")))
|
|
45
54
|
.join(" · ");
|
|
46
55
|
}
|
|
47
56
|
function blockHtml(block, settings) {
|
|
@@ -66,8 +75,8 @@ function fitWidth(block) {
|
|
|
66
75
|
}
|
|
67
76
|
/** Em per bar: padding, plus each slot's text and the gap after it. */
|
|
68
77
|
const cellWidth = (cell) => cell.kind === "fermata"
|
|
69
|
-
?
|
|
70
|
-
: Math.max(
|
|
78
|
+
? MIN_BAR
|
|
79
|
+
: Math.max(MIN_BAR, 1.5 + (cell.meter ? 0.8 : 0) + cell.slots.reduce((w, s) => w + slotWidth(s), 0));
|
|
71
80
|
/** Roughly: 0.65em a baseline character, 0.4em a superscript one, 0.6em between slots. */
|
|
72
81
|
function slotWidth(slot) {
|
|
73
82
|
if (slot.kind === "group")
|
|
@@ -104,7 +113,8 @@ function itemHtml(item, columns, settings) {
|
|
|
104
113
|
case "text":
|
|
105
114
|
return `<p class="bm-text${item.note ? " bm-note" : ""}" part="text">${escapeHtml(item.text)}</p>`;
|
|
106
115
|
case "directive":
|
|
107
|
-
|
|
116
|
+
// Mid-chart, a change names itself: "→ Time 3/4", never a lone "3/4".
|
|
117
|
+
return `<p class="bm-directive" part="meta"><span class="bm-change" aria-hidden="true">→</span><span>${metaText(item.set, true)}</span></p>`;
|
|
108
118
|
case "error":
|
|
109
119
|
return errorHtml(item.source, item.messages);
|
|
110
120
|
}
|
package/dist/styles.d.ts
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* The chart's styles. Everything is sized in em from `--barmark-size`, so
|
|
3
|
-
* text size and zoom scale the chart; colours come from the palettes below
|
|
4
|
-
* unless the page sets the `--barmark-*` custom properties.
|
|
5
|
-
*/
|
|
1
|
+
export declare const MIN_BAR = 4.68;
|
|
6
2
|
export type Palette = {
|
|
7
3
|
ink: string;
|
|
8
4
|
paper: string;
|
package/dist/styles.js
CHANGED
|
@@ -1,4 +1,47 @@
|
|
|
1
1
|
import { FONT_FAMILY } from "./font.js";
|
|
2
|
+
/**
|
|
3
|
+
* The chart's styles. Everything is sized in em from `--barmark-size`, so
|
|
4
|
+
* text size and zoom scale the chart; colours come from the palettes below
|
|
5
|
+
* unless the page sets the `--barmark-*` custom properties.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Text sizes as fractions of the chord size, `--barmark-size` (1rem unless
|
|
9
|
+
* the page sets it). Chosen on barmark.dev, 2026-11-01: at the default size
|
|
10
|
+
* nothing a reader needs is under 12px. The superscript is a fraction of its
|
|
11
|
+
* chord and the octave of its note name; everything else of the chord size.
|
|
12
|
+
*/
|
|
13
|
+
const SIZES = {
|
|
14
|
+
title: 1.16,
|
|
15
|
+
meta: 1,
|
|
16
|
+
directive: 0.97,
|
|
17
|
+
heading: 0.77,
|
|
18
|
+
text: 0.9,
|
|
19
|
+
noChord: 1.03,
|
|
20
|
+
noteNames: 0.9,
|
|
21
|
+
superscript: 0.84,
|
|
22
|
+
octave: 0.79,
|
|
23
|
+
tuplet: 0.84,
|
|
24
|
+
repeat: 0.97,
|
|
25
|
+
diagnostics: 0.97,
|
|
26
|
+
barNumber: 0.84,
|
|
27
|
+
meter: 0.77,
|
|
28
|
+
cut: 0.77,
|
|
29
|
+
};
|
|
30
|
+
/** Lengths, in chord sizes: the bar-number gutter, the narrowest bar (a D7sus4 bar), the gap under a heading. */
|
|
31
|
+
const GUTTER = 2.06;
|
|
32
|
+
export const MIN_BAR = 4.68;
|
|
33
|
+
const HEADING_GAP = 0.645;
|
|
34
|
+
/**
|
|
35
|
+
* The triplet bracket, in chord sizes: its line sits this far below the top
|
|
36
|
+
* of the number, with hooks this long, drawn either side of the number so
|
|
37
|
+
* nothing is painted over it and any paper shows through.
|
|
38
|
+
*/
|
|
39
|
+
const BRACKET = { top: 0.6, hook: 0.3, line: 0.06 };
|
|
40
|
+
/** The single note's ring, in its own em: padding, and the border it spills past. */
|
|
41
|
+
const RING = { y: 0.21, x: 0.5, border: 0.07 };
|
|
42
|
+
const em = (n) => `${Number(n.toFixed(3))}em`;
|
|
43
|
+
/** A length in chord sizes, in the em of text at `size`. */
|
|
44
|
+
const at = (size, length) => em(length / size);
|
|
2
45
|
/** Both pass WCAG AA on their paper (tests/contrast.test.ts). */
|
|
3
46
|
export const PALETTES = {
|
|
4
47
|
light: { ink: "#1d1d1f", paper: "#fffdf8", muted: "#6a6052", rule: "#4a4a4a", accent: "#9a4a12" },
|
|
@@ -10,12 +53,17 @@ const colours = (p) => Object.keys(p)
|
|
|
10
53
|
.map((k) => `--bm-${k}:var(--barmark-${k},${p[k]});`)
|
|
11
54
|
.join("");
|
|
12
55
|
const SANS = `system-ui,-apple-system,"Segoe UI",sans-serif`;
|
|
56
|
+
/** The meta line, directives, headings and diagnostics. */
|
|
57
|
+
const LABEL = `var(--barmark-label-font,${SANS})`;
|
|
58
|
+
/** Lyrics and note lines: a serif every system has, with a true italic (Edwin has none). */
|
|
59
|
+
const TEXT = "var(--barmark-text-font,Georgia,serif)";
|
|
60
|
+
const CHART_FONT = `var(--barmark-font,"${FONT_FAMILY}","Edwin",Georgia,serif)`;
|
|
13
61
|
const css = `
|
|
14
62
|
:host{all:initial;display:block}
|
|
15
63
|
.bm-chart{${colours(PALETTES.light)}
|
|
16
64
|
container:bm/inline-size;
|
|
17
|
-
font-family
|
|
18
|
-
font-size:var(--barmark-size,
|
|
65
|
+
font-family:${CHART_FONT};
|
|
66
|
+
font-size:var(--barmark-size,1rem);
|
|
19
67
|
line-height:1.3;color:var(--bm-ink);background:var(--bm-paper);
|
|
20
68
|
padding:1em 1.2em;box-sizing:border-box;text-align:start}
|
|
21
69
|
.bm-chart *{box-sizing:border-box}
|
|
@@ -24,55 +72,62 @@ const css = `
|
|
|
24
72
|
@media print{.bm-chart[data-theme]{${colours(PALETTES.light)}color-scheme:light}}
|
|
25
73
|
.bm-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
|
|
26
74
|
.bm-header{margin-bottom:.8em}
|
|
27
|
-
.bm-title{font-size
|
|
28
|
-
.bm-meta
|
|
29
|
-
.bm-directive{grid-column:1/-1;margin:.9em 0 .3em}
|
|
75
|
+
.bm-title{font-size:${em(SIZES.title)};font-weight:normal;margin:0 0 .15em}
|
|
76
|
+
.bm-meta{font-family:${LABEL};font-size:${em(SIZES.meta)};color:var(--bm-muted);margin:0}
|
|
77
|
+
.bm-directive{grid-column:1/-1;display:flex;align-items:baseline;gap:.4em;margin:.9em 0 .3em ${at(SIZES.directive, GUTTER)};font-family:${LABEL};font-size:${em(SIZES.directive)};color:var(--barmark-directive,var(--bm-muted))}
|
|
78
|
+
.bm-directive::after{content:"";flex:1;align-self:center;margin-left:.4em;border-top:.06em solid var(--bm-rule);opacity:.6}
|
|
30
79
|
.bm-section{margin-top:.9em}
|
|
31
|
-
.bm-heading{display:flex;align-items:center;gap:.5em;margin:0 0 .
|
|
80
|
+
.bm-heading{display:flex;align-items:center;gap:.5em;margin:0 0 ${at(SIZES.heading, HEADING_GAP)} ${at(SIZES.heading, GUTTER)};font:600 ${em(SIZES.heading)}/1 ${LABEL};letter-spacing:.08em;text-transform:uppercase;color:var(--bm-ink)}
|
|
32
81
|
.bm-rehearsal{border:.12em solid currentColor;padding:.15em .35em;border-radius:.15em;letter-spacing:0}
|
|
33
82
|
.bm-times{font:italic 1.2em/1 Georgia,serif;text-transform:none;letter-spacing:0;color:var(--bm-muted)}
|
|
34
|
-
.bm-grid{display:grid;grid-template-columns
|
|
83
|
+
.bm-grid{display:grid;grid-template-columns:${em(GUTTER)} repeat(var(--bm-cols),auto) auto;width:max-content;max-width:100%}
|
|
35
84
|
.bm-chart[data-bar-numbers="off"] .bm-grid{grid-template-columns:repeat(var(--bm-cols),auto) auto}
|
|
36
85
|
.bm-row{display:contents}
|
|
37
|
-
.bm-bar-number{font
|
|
38
|
-
.bm-bar{
|
|
86
|
+
.bm-bar-number{display:flex;flex:none;align-items:baseline;width:${at(SIZES.barNumber, GUTTER)};padding-top:${at(SIZES.barNumber, 0.65)};font:${em(SIZES.barNumber)}/1 ${SANS};color:var(--bm-muted)}
|
|
87
|
+
.bm-bar-number::before{content:"\\200b";font:${em(1 / SIZES.barNumber)}/1.3 ${CHART_FONT}}
|
|
88
|
+
.bm-chart[data-bar-numbers="off"] :is(.bm-heading,.bm-text,.bm-directive){margin-left:0}
|
|
89
|
+
.bm-bar{position:relative;display:flex;align-items:baseline;min-width:${em(MIN_BAR)};min-height:2.2em;padding:.65em .7em .25em;border-right:.075em solid var(--bm-rule)}
|
|
39
90
|
.bm-row>div.bm-bar:first-of-type{border-left:.075em solid var(--bm-rule)}
|
|
40
91
|
.bm-row>div.bm-bar[data-repeat-start]{border-left:.25em double var(--bm-rule)}
|
|
41
92
|
.bm-row>div.bm-bar[data-repeat-end]{border-right:.25em double var(--bm-rule)}
|
|
42
|
-
.bm-bar-body{display:flex;flex:1;align-items:baseline;
|
|
93
|
+
.bm-bar-body{display:flex;flex:1;align-items:baseline;gap:.6em}
|
|
94
|
+
.bm-bar-body>:not(.bm-meter,.bm-cut){flex:1 1 0;min-width:max-content}
|
|
43
95
|
.bm-repeat{display:flex;align-items:center;padding-left:.4em;white-space:nowrap}
|
|
44
|
-
.bm-repeat .bm-times{font-size
|
|
96
|
+
.bm-repeat .bm-times{font-size:${em(SIZES.repeat)}}
|
|
45
97
|
.bm-chord{white-space:nowrap}
|
|
46
|
-
.bm-chord sup{font-size
|
|
47
|
-
.bm-chord sub{font-size
|
|
98
|
+
.bm-chord sup{font-size:${em(SIZES.superscript)};vertical-align:.55em;line-height:0}
|
|
99
|
+
.bm-chord sub{font-size:${em(SIZES.octave)};line-height:0}
|
|
48
100
|
.bm-hold,.bm-unknown,.bm-brace{color:var(--bm-muted)}
|
|
49
|
-
.bm-nc{font-size
|
|
101
|
+
.bm-nc{font-size:${em(SIZES.noChord)};letter-spacing:.06em}
|
|
50
102
|
.bm-silence{display:inline-block;width:.9em;height:.32em;background:var(--bm-ink);vertical-align:.25em}
|
|
51
|
-
.bm-ring{
|
|
103
|
+
.bm-ring,.bm-notes{font-size:${em(SIZES.noteNames)}}
|
|
104
|
+
.bm-ring{display:inline-block;min-width:1.4em;padding:${em(RING.y)} ${em(RING.x)};margin:${em(-(RING.y + RING.border))} 0;border:${em(RING.border)} solid currentColor;border-radius:1em;text-align:center}
|
|
52
105
|
.bm-notes{display:inline-flex;gap:.3em;align-items:baseline}
|
|
53
106
|
.bm-notes u{text-decoration-thickness:.08em;text-underline-offset:.15em}
|
|
54
107
|
.bm-group{position:relative;display:inline-flex;gap:.45em;align-items:baseline}
|
|
55
|
-
.bm-
|
|
56
|
-
.bm-tuplet{
|
|
57
|
-
.bm-
|
|
108
|
+
.bm-tuplet{position:absolute;left:0;right:0;top:-1.25em;display:flex;justify-content:center;font:italic 600 ${em(SIZES.tuplet)}/1 Georgia,serif;color:var(--bm-muted)}
|
|
109
|
+
.bm-tuplet::before,.bm-tuplet::after{content:"";flex:1;align-self:flex-start;margin-top:${at(SIZES.tuplet, BRACKET.top)};height:${at(SIZES.tuplet, BRACKET.hook)};border:${at(SIZES.tuplet, BRACKET.line)} solid var(--bm-muted);border-bottom:none}
|
|
110
|
+
.bm-tuplet::before{border-right:none;margin-right:.3em}
|
|
111
|
+
.bm-tuplet::after{border-left:none;margin-left:.3em}
|
|
112
|
+
.bm-meter{display:inline-flex;flex-direction:column;align-items:center;align-self:center;font:700 ${em(SIZES.meter)}/.95 ${SANS};color:var(--bm-muted)}
|
|
58
113
|
.bm-fermata-glyph{display:block;color:var(--bm-ink)}
|
|
59
114
|
.bm-bar[data-cut]{outline:.08em dashed var(--bm-muted);outline-offset:-.25em}
|
|
60
|
-
.bm-cut{position:absolute;top:.3em;right:.45em;font
|
|
61
|
-
.bm-text{grid-column:1/-1;margin:.2em 0 .5em;font-size
|
|
62
|
-
.bm-note{font-
|
|
115
|
+
.bm-cut{position:absolute;top:.3em;right:.45em;font:${em(SIZES.cut)}/1 ${SANS};color:var(--bm-muted)}
|
|
116
|
+
.bm-text{grid-column:1/-1;margin:.2em 0 .5em ${at(SIZES.text, GUTTER)};font-family:${TEXT};font-size:${em(SIZES.text)}}
|
|
117
|
+
.bm-note{font-style:italic;color:var(--barmark-note,var(--bm-ink))}
|
|
63
118
|
.bm-error{grid-column:1/-1;margin:.3em 0}
|
|
64
119
|
.bm-error code{font:.7em/1.4 ui-monospace,Menlo,Consolas,monospace;text-decoration:underline wavy var(--bm-accent);text-decoration-thickness:.08em;text-underline-offset:.25em;white-space:pre-wrap}
|
|
65
|
-
.bm-diagnostics{margin:1em 0 0;padding-left:1.2em;font
|
|
120
|
+
.bm-diagnostics{margin:1em 0 0;padding-left:1.2em;font:${em(SIZES.diagnostics)}/1.5 ${LABEL};color:var(--bm-muted)}
|
|
66
121
|
.bm-diagnostics .bm-error{color:var(--bm-accent)}
|
|
67
122
|
@media (forced-colors:active){
|
|
68
123
|
.bm-bar,.bm-row>div.bm-bar:first-of-type,.bm-row>div.bm-bar[data-repeat-start],.bm-row>div.bm-bar[data-repeat-end]{border-color:CanvasText}
|
|
69
|
-
.bm-ring,.bm-
|
|
124
|
+
.bm-ring,.bm-tuplet::before,.bm-tuplet::after{border-color:CanvasText}
|
|
70
125
|
.bm-silence{background:CanvasText;forced-color-adjust:none}
|
|
71
126
|
.bm-bar[data-cut]{outline-color:CanvasText}
|
|
72
127
|
}
|
|
73
128
|
${FIT_STEPS.map((w) => {
|
|
74
129
|
const g = `.bm-grid[data-fit="${w}"]`;
|
|
75
|
-
return `@container bm (max-width:${w}em){${g}{display:block}${g}>.bm-row{display:flex;flex-wrap:wrap}${g} .bm-empty{display:none}${g} .bm-bar{flex:0 1 auto;max-width:100%;border-left:.075em solid var(--bm-rule);margin-left:-.075em}${g} :is(.bm-bar-body,.bm-group,.bm-notes){flex-wrap:wrap}}`;
|
|
130
|
+
return `@container bm (max-width:${w}em){${g}{display:block}${g}>.bm-row{display:flex;flex-wrap:wrap}${g} .bm-empty{display:none}${g} .bm-bar{flex:0 1 auto;max-width:100%;border-left:.075em solid var(--bm-rule);margin-left:-.075em}${g} :is(.bm-bar-body,.bm-group,.bm-notes){flex-wrap:wrap}${g} .bm-bar-body>*{flex:1 1 auto;min-width:auto}}`;
|
|
76
131
|
}).join("\n")}
|
|
77
132
|
`;
|
|
78
133
|
/** Collapse the readable source above: whitespace around punctuation and between rules. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barmark/render",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Render barmark chord charts as a clean, accessible bar grid: HTML with Declarative Shadow DOM, or the <barmark-chart> web component.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"url": "https://github.com/kevinpeckham/barmark/issues"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"barmark": "^0.
|
|
50
|
+
"barmark": "^0.3.0"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|