@barmark/render 0.11.0 → 0.12.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 +25 -1
- package/README.md +4 -2
- package/dist/element.js +13 -2
- package/dist/html.js +19 -13
- package/dist/model.d.ts +3 -1
- package/dist/model.js +4 -2
- package/dist/speech.js +0 -2
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,29 @@ package uses [semantic versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.12.0] - 2026-11-03
|
|
10
|
+
|
|
11
|
+
Needs `barmark` 0.14.1.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- **Breaking:** the element's one-part attribute is now `show-part`
|
|
16
|
+
(property `showPart`), not `part`. HTML keeps `part` on every element
|
|
17
|
+
for `::part()` styling, so a page styling `<barmark-chart part="…">`
|
|
18
|
+
got a one-part view by accident, and `el.part` could not return the
|
|
19
|
+
label. `renderChart`'s option stays `part`.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- A system shows and speaks what its rows share once, on its first drawn
|
|
24
|
+
row: the bar number, "repeat start", the ending's number and ×N. Every
|
|
25
|
+
row still draws its repeat bar lines. A system whose first part's line
|
|
26
|
+
has an error numbers the next row.
|
|
27
|
+
- Tacet rows carry `bm-tacet` in a part's own view too, and a tacet bar
|
|
28
|
+
cut in a `->` replay is marked and spoken as cut.
|
|
29
|
+
- The "no part labelled X" warning is about the whole chart: its line is
|
|
30
|
+
0, and the diagnostics list shows it without a line number.
|
|
31
|
+
|
|
9
32
|
## [0.11.0] - 2026-11-03
|
|
10
33
|
|
|
11
34
|
Needs `barmark` 0.14.0.
|
|
@@ -294,7 +317,8 @@ that needs no script or as the `<barmark-chart>` web component. Requires
|
|
|
294
317
|
zoom without sideways scrolling, the element renders and re-renders, and
|
|
295
318
|
a screen reader hears "bar 9: D". Run them with `bun run test:browser`.
|
|
296
319
|
|
|
297
|
-
[Unreleased]: https://github.com/kevinpeckham/barmark/compare/render-v0.
|
|
320
|
+
[Unreleased]: https://github.com/kevinpeckham/barmark/compare/render-v0.12.0...HEAD
|
|
321
|
+
[0.12.0]: https://github.com/kevinpeckham/barmark/compare/render-v0.11.0...render-v0.12.0
|
|
298
322
|
[0.11.0]: https://github.com/kevinpeckham/barmark/compare/render-v0.10.0...render-v0.11.0
|
|
299
323
|
[0.10.0]: https://github.com/kevinpeckham/barmark/compare/render-v0.9.1...render-v0.10.0
|
|
300
324
|
[0.9.1]: https://github.com/kevinpeckham/barmark/compare/render-v0.9.0...render-v0.9.1
|
package/README.md
CHANGED
|
@@ -48,7 +48,9 @@ The element renders from its `text` property, its `src` URL, or a child
|
|
|
48
48
|
whenever one changes; it fetches a `src` once, and renders once however
|
|
49
49
|
many properties and attributes change together. A chart rendered on the
|
|
50
50
|
server by `renderChart` keeps its music until it has a source of its own,
|
|
51
|
-
and still follows `theme` and `
|
|
51
|
+
and still follows `theme`, `bar-numbers` and `wrap`; a change to
|
|
52
|
+
`numerals`, `rehearsal-letters` or `show-part` needs the chart's text, so
|
|
53
|
+
it waits for a source.
|
|
52
54
|
|
|
53
55
|
For a live editor, set `text`:
|
|
54
56
|
|
|
@@ -77,7 +79,7 @@ the element loads, with `<style data-barmark-font>` holding
|
|
|
77
79
|
| `rehearsalLetters` | `rehearsal-letters` (`rehearsalLetters`) | `false` | boxed A, B, C… on each section as played |
|
|
78
80
|
| `numerals` | `numerals` (`numerals`) | none | `"nashville"` (6m⁷) or `"roman"` (vi⁷): each chord as its degree in its bar's key |
|
|
79
81
|
| `wrap` | `wrap="off"` (`wrap`) | `true` | wrap a section's rows when it is wider than the chart's box; off, rows keep their columns and the chart overflows, for its page to scroll |
|
|
80
|
-
| `part` | `part` (`
|
|
82
|
+
| `part` | `show-part` (`showPart`) | none | draw only the part with this label, as one player reads it: tutti bars, its own bars, and "tacet" where it rests. The attribute isn't `part`, which HTML keeps for `::part()` styling |
|
|
81
83
|
| `diagnostics` | | `false` | list the chart's errors and warnings below it |
|
|
82
84
|
| `fontUrl` | | none | emit Edwin's `@font-face` rule before the chart |
|
|
83
85
|
| (the `text` argument) | (`text`) | `null` | the chart to draw; wins over `src` and the script child |
|
package/dist/element.js
CHANGED
|
@@ -10,7 +10,8 @@ import { loadErrorContent, shadowContent, THEMES } from "./html.js";
|
|
|
10
10
|
export function optionsFromAttributes(el) {
|
|
11
11
|
const theme = el.getAttribute("theme");
|
|
12
12
|
const numerals = NUMERALS.find((n) => n === el.getAttribute("numerals"));
|
|
13
|
-
|
|
13
|
+
// Not `part`: that global attribute names an element for ::part() styling.
|
|
14
|
+
const part = el.getAttribute("show-part");
|
|
14
15
|
return {
|
|
15
16
|
theme: THEMES.find((t) => t === theme) ?? "auto",
|
|
16
17
|
barNumbers: el.getAttribute("bar-numbers") !== "off",
|
|
@@ -58,7 +59,7 @@ function defineElement() {
|
|
|
58
59
|
"rehearsal-letters",
|
|
59
60
|
"numerals",
|
|
60
61
|
"wrap",
|
|
61
|
-
"part",
|
|
62
|
+
"show-part",
|
|
62
63
|
];
|
|
63
64
|
#observer = new MutationObserver(() => this.#schedule());
|
|
64
65
|
#renders = 0;
|
|
@@ -139,6 +140,16 @@ function defineElement() {
|
|
|
139
140
|
else
|
|
140
141
|
this.setAttribute("numerals", v);
|
|
141
142
|
}
|
|
143
|
+
/** The label of the part to draw on its own, or null for the whole chart. */
|
|
144
|
+
get showPart() {
|
|
145
|
+
return this.getAttribute("show-part");
|
|
146
|
+
}
|
|
147
|
+
set showPart(v) {
|
|
148
|
+
if (v === null)
|
|
149
|
+
this.removeAttribute("show-part");
|
|
150
|
+
else
|
|
151
|
+
this.setAttribute("show-part", v);
|
|
152
|
+
}
|
|
142
153
|
/** Every change in one task (an upgrade sets each attribute in turn) renders once. */
|
|
143
154
|
#schedule() {
|
|
144
155
|
if (this.#scheduled)
|
package/dist/html.js
CHANGED
|
@@ -7,7 +7,7 @@ import { printDirection, printPasses, } from "barmark";
|
|
|
7
7
|
import { glyphs, NUMERALS, splitPitch, typesetChord, typesetNumeral, } from "./chord.js";
|
|
8
8
|
import { escapeHtml } from "./escape.js";
|
|
9
9
|
import { fontFaceCss } from "./font.js";
|
|
10
|
-
import { layoutChart } from "./model.js";
|
|
10
|
+
import { isTacet as isTacetCell, layoutChart } from "./model.js";
|
|
11
11
|
import { speakBar } from "./speech.js";
|
|
12
12
|
import { FIT_STEPS, MIN_BAR, stylesheet } from "./styles.js";
|
|
13
13
|
export function renderChart(text, options = {}) {
|
|
@@ -22,7 +22,7 @@ function elementAttributes(options) {
|
|
|
22
22
|
options.rehearsalLetters ? " rehearsal-letters" : "",
|
|
23
23
|
numeralsOf(options) ? ` numerals="${numeralsOf(options)}"` : "",
|
|
24
24
|
options.wrap === false ? ' wrap="off"' : "",
|
|
25
|
-
options.part ? ` part="${escapeHtml(options.part)}"` : "",
|
|
25
|
+
options.part ? ` show-part="${escapeHtml(options.part)}"` : "",
|
|
26
26
|
].join("");
|
|
27
27
|
}
|
|
28
28
|
export const THEMES = ["auto", "light", "dark"];
|
|
@@ -171,9 +171,11 @@ function errorHtml(source, messages) {
|
|
|
171
171
|
/** A system: its parts' rows stacked, sharing the section's columns (barmark's grammar 7.4). */
|
|
172
172
|
function systemHtml(item, columns, settings) {
|
|
173
173
|
const names = escapeHtml(item.rows.map((p) => p.name).join(", "));
|
|
174
|
+
// The first drawn row carries what the system shares: its number, ×N, the ending's number.
|
|
175
|
+
const lead = item.rows.findIndex((p) => p.row.kind === "row");
|
|
174
176
|
const rows = item.rows.map((p, i) => p.row.kind === "error"
|
|
175
177
|
? errorHtml(p.row.source, p.row.messages)
|
|
176
|
-
: rowHtml(p.row, columns, settings, { label: p.label, name: p.name, first: i ===
|
|
178
|
+
: rowHtml(p.row, columns, settings, { label: p.label, name: p.name, first: i === lead }));
|
|
177
179
|
return `<div class="bm-system" role="group" aria-label="${names}">${rows.join("")}</div>`;
|
|
178
180
|
}
|
|
179
181
|
/** The left column: the bar number, and in a system the part's label. */
|
|
@@ -186,16 +188,14 @@ function gutterHtml(row, settings, part) {
|
|
|
186
188
|
: "";
|
|
187
189
|
return `<span class="bm-bar-number" part="bar-number" aria-hidden="true">${label}${number}</span>`;
|
|
188
190
|
}
|
|
189
|
-
const isTacetCell = (c) => c.kind === "slots" && c.tacet === true;
|
|
190
191
|
/** A row's cells; a tacet row says "tacet" in its first bar. */
|
|
191
192
|
function rowCells(row, numerals, ending) {
|
|
192
193
|
let n = row.barNumber;
|
|
193
194
|
const firstTacet = row.cells.findIndex(isTacetCell);
|
|
194
195
|
return row.cells.map((cell, i) => {
|
|
195
196
|
const marks = repeatMarks(row, i) + (i === 0 ? ending : "");
|
|
196
|
-
const spoken = `<span class="bm-sr">bar ${n ?? ""}: tacet</span>`;
|
|
197
197
|
const html = isTacetCell(cell)
|
|
198
|
-
? tacetHtml(
|
|
198
|
+
? tacetHtml(cell, n, marks, i === firstTacet)
|
|
199
199
|
: cellHtml(cell, n, marks, numerals);
|
|
200
200
|
if (n !== null && cell.kind === "slots")
|
|
201
201
|
n++;
|
|
@@ -208,7 +208,7 @@ function rowClasses(row, part) {
|
|
|
208
208
|
const classes = ["bm-row"];
|
|
209
209
|
if (part)
|
|
210
210
|
classes.push("bm-part-row");
|
|
211
|
-
if (
|
|
211
|
+
if (row.cells.some(isTacetCell))
|
|
212
212
|
classes.push("bm-tacet");
|
|
213
213
|
if (row.ending)
|
|
214
214
|
classes.push("bm-ending");
|
|
@@ -216,15 +216,17 @@ function rowClasses(row, part) {
|
|
|
216
216
|
}
|
|
217
217
|
/** What a screen reader hears before a row's bars: its part, a repeat start, its ending. */
|
|
218
218
|
function rowOpening(row, part, ending) {
|
|
219
|
-
const opens = row.opens || typeof row.repeat === "number" || row.repeat === "open";
|
|
219
|
+
const opens = leads(part) && (row.opens || typeof row.repeat === "number" || row.repeat === "open");
|
|
220
220
|
return sr(part ? escapeHtml(part.name) : "") + sr(opens ? "repeat start" : "") + sr(ending);
|
|
221
221
|
}
|
|
222
|
+
/** Whether a row says what its system shares: a row on its own, or a system's first drawn row. */
|
|
223
|
+
const leads = (part) => !part || part.first;
|
|
222
224
|
function rowHtml(row, columns, settings, part) {
|
|
223
|
-
const label = row.ending ? endingLabel(row.ending) : null;
|
|
225
|
+
const label = row.ending && leads(part) ? endingLabel(row.ending) : null;
|
|
224
226
|
const cells = rowCells(row, settings.numerals, label ? ` data-ending="${label.shown}"` : "");
|
|
225
227
|
const empty = `<span class="bm-empty" aria-hidden="true"></span>`.repeat(Math.max(0, columns - row.cells.length));
|
|
226
228
|
const said = rowOpening(row, part, label?.spoken ?? "");
|
|
227
|
-
return `<div class="${rowClasses(row, part)}">${said}${gutterHtml(row, settings, part)}${cells.join("")}${empty}<span class="bm-repeat">${rowRepeatHtml(row)}</span></div>`;
|
|
229
|
+
return `<div class="${rowClasses(row, part)}">${said}${gutterHtml(row, settings, part)}${cells.join("")}${empty}<span class="bm-repeat">${leads(part) ? rowRepeatHtml(row) : ""}</span></div>`;
|
|
228
230
|
}
|
|
229
231
|
/** An ending's passes as printed, "1.", "1.–3.", "1., 3.", and spoken, "ending for passes 1 to 3". */
|
|
230
232
|
function endingLabel(passes) {
|
|
@@ -268,8 +270,10 @@ function barBody(cell, view) {
|
|
|
268
270
|
return [`${meter}${slots}${cut}`, end];
|
|
269
271
|
}
|
|
270
272
|
/** A bar where the part rests; the first of a row says so, in its body. */
|
|
271
|
-
function tacetHtml(
|
|
272
|
-
|
|
273
|
+
function tacetHtml(cell, n, marks, first) {
|
|
274
|
+
const cut = cell.kind === "slots" && cell.cut;
|
|
275
|
+
const spoken = `<span class="bm-sr">bar ${n ?? ""}: tacet${cut ? ", cut on this pass" : ""}</span>`;
|
|
276
|
+
return `<div class="bm-bar bm-tacet-bar" part="bar"${marks}${cut ? " data-cut" : ""}>${spoken}<span class="bm-bar-body" aria-hidden="true">${first ? "tacet" : ""}</span></div>`;
|
|
273
277
|
}
|
|
274
278
|
/** A bar's slots, each under any words before it, and the words after the last slot. */
|
|
275
279
|
function slotsWithWords(cell, view) {
|
|
@@ -380,5 +384,7 @@ function notesHtml(record) {
|
|
|
380
384
|
function diagnosticsHtml(model) {
|
|
381
385
|
if (!model.diagnostics.length)
|
|
382
386
|
return "";
|
|
383
|
-
|
|
387
|
+
// Line 0: about the whole chart, not a line of it.
|
|
388
|
+
const at = (line) => (line > 0 ? `line ${line}: ` : "");
|
|
389
|
+
return `<ul class="bm-diagnostics">${model.diagnostics.map((d) => `<li class="bm-${d.severity}">${at(d.line)}${escapeHtml(d.message)}</li>`).join("")}</ul>`;
|
|
384
390
|
}
|
package/dist/model.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import type { ChartModel, LayoutOptions } from "./types.js";
|
|
1
|
+
import type { CellModel, ChartModel, LayoutOptions } from "./types.js";
|
|
2
2
|
export declare function layoutChart(text: string, options?: LayoutOptions): ChartModel;
|
|
3
|
+
/** A bar where the part rests, in a system or a part's own view (barmark's grammar 7.4). */
|
|
4
|
+
export declare const isTacet: (c: CellModel) => boolean;
|
package/dist/model.js
CHANGED
|
@@ -34,7 +34,8 @@ function parseFor(text, part) {
|
|
|
34
34
|
return one ?? parseChart(text);
|
|
35
35
|
const whole = parseChart(text);
|
|
36
36
|
const missing = {
|
|
37
|
-
|
|
37
|
+
// Line 0: the warning is about the chart, not one of its lines.
|
|
38
|
+
line: 0,
|
|
38
39
|
severity: "warning",
|
|
39
40
|
message: `no part labelled ${part}`,
|
|
40
41
|
token: part,
|
|
@@ -242,7 +243,8 @@ const lastOfSystem = (item) => {
|
|
|
242
243
|
const first = item.rows.find((p) => p.row.kind === "row" && !p.row.cells.some(isTacet))?.row;
|
|
243
244
|
return first?.kind === "row" ? lastSlots(first.cells) : null;
|
|
244
245
|
};
|
|
245
|
-
|
|
246
|
+
/** A bar where the part rests, in a system or a part's own view (barmark's grammar 7.4). */
|
|
247
|
+
export const isTacet = (c) => c.kind === "slots" && c.tacet === true;
|
|
246
248
|
/** A row per declared part, in score order: its own line, its error, or tacet bars. */
|
|
247
249
|
function layoutSystem(lines, occurrence, previous, ctx) {
|
|
248
250
|
const ref = lines.find((l) => l.kind === "bars" && !ctx.errors.has(l.line));
|
package/dist/speech.js
CHANGED
|
@@ -149,8 +149,6 @@ export function speakBar(barNumber, cell, numerals = false) {
|
|
|
149
149
|
if (cell.kind === "rehearsal")
|
|
150
150
|
return `rehearsal ${cell.label}`;
|
|
151
151
|
const bar = `bar${barNumber === null ? "" : ` ${barNumber}`}`;
|
|
152
|
-
if (cell.tacet)
|
|
153
|
-
return `${bar}: tacet`;
|
|
154
152
|
if (cell.simile)
|
|
155
153
|
return `${bar}: each part repeats its bar before`;
|
|
156
154
|
const meter = cell.meter ? `, in ${cell.meter.n}/${cell.meter.d}` : "";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barmark/render",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
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.14.
|
|
50
|
+
"barmark": "^0.14.1"
|
|
51
51
|
},
|
|
52
52
|
"scripts": {
|
|
53
53
|
"build": "rm -rf dist && tsc -p tsconfig.build.json",
|