@ugfoundation/swaralipi-js 0.9.1 → 0.10.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.
Files changed (39) hide show
  1. package/README.md +17 -0
  2. package/dist/{batch-NEM7JXHV.js → batch-3PHPNX2P.js} +4 -4
  3. package/dist/{batch-NEM7JXHV.js.map → batch-3PHPNX2P.js.map} +1 -1
  4. package/dist/{chunk-DOEFSS66.js → chunk-FGMULPL2.js} +3 -3
  5. package/dist/{chunk-DOEFSS66.js.map → chunk-FGMULPL2.js.map} +1 -1
  6. package/dist/{chunk-Z6A3CP5V.js → chunk-I5GJWSD2.js} +3 -3
  7. package/dist/{chunk-Z6A3CP5V.js.map → chunk-I5GJWSD2.js.map} +1 -1
  8. package/dist/{chunk-SEN266BY.js → chunk-JEJYMKKZ.js} +26 -3
  9. package/dist/chunk-JEJYMKKZ.js.map +1 -0
  10. package/dist/cli.cjs +1 -1
  11. package/dist/cli.cjs.map +1 -1
  12. package/dist/cli.js +2 -2
  13. package/dist/convert/index.cjs +1 -1
  14. package/dist/convert/index.cjs.map +1 -1
  15. package/dist/convert/index.js +2 -2
  16. package/dist/index.cjs +1 -1
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.js +1 -1
  19. package/dist/player/index.cjs +436 -50
  20. package/dist/player/index.cjs.map +1 -1
  21. package/dist/player/index.d.cts +76 -11
  22. package/dist/player/index.d.ts +76 -11
  23. package/dist/player/index.js +412 -51
  24. package/dist/player/index.js.map +1 -1
  25. package/dist/{schedule-BaovCtmf.d.ts → schedule-BUopqmWm.d.ts} +27 -1
  26. package/dist/{schedule-CU-VECDH.d.cts → schedule-IBu8lr-3.d.cts} +27 -1
  27. package/dist/spec/index.cjs +1 -1
  28. package/dist/spec/index.cjs.map +1 -1
  29. package/dist/spec/index.d.cts +1 -1
  30. package/dist/spec/index.d.ts +1 -1
  31. package/dist/spec/index.js +1 -1
  32. package/dist/viewer/index.cjs +149 -7
  33. package/dist/viewer/index.cjs.map +1 -1
  34. package/dist/viewer/index.d.cts +21 -4
  35. package/dist/viewer/index.d.ts +21 -4
  36. package/dist/viewer/index.js +149 -8
  37. package/dist/viewer/index.js.map +1 -1
  38. package/package.json +1 -1
  39. package/dist/chunk-SEN266BY.js.map +0 -1
@@ -1,6 +1,6 @@
1
1
  import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi, T as Taal } from '../schema-Cf_epUIC.cjs';
2
2
  import { L as Lay } from '../lay-CfDlPhH2.cjs';
3
- import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-CU-VECDH.cjs';
3
+ import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-IBu8lr-3.cjs';
4
4
  import 'zod';
5
5
  import 'tone';
6
6
 
@@ -34,6 +34,13 @@ declare const LAY_LABELS: Record<GridLang, Record<Lay, string>>;
34
34
  /** Printed where a row switches to free rhythm (`row.taal: null`) with no
35
35
  * `taalName` of its own. bn is the converter's canonical মুক্তছন্দ. */
36
36
  declare const FREE_TAAL_LABELS: Record<GridLang, string>;
37
+ /** The practice surface's words: the sheet's own accessible name (it says
38
+ * what a tap does), a repeat being on, and the learner's turn to sing. */
39
+ declare const PRACTICE_LABELS: Record<GridLang, {
40
+ sheet: string;
41
+ repeat: string;
42
+ turn: string;
43
+ }>;
37
44
  /** A composed notation cluster: the font's ASCII text plus any residual CSS
38
45
  * classes the font itself cannot express (only the bn ati-octave dot). */
39
46
  interface Cluster {
@@ -209,6 +216,9 @@ declare const ZOOM_STEP = 1.2;
209
216
  * themselves: `<style>${SWARALIPI_CSS}</style>` next to `renderToHtml()`.
210
217
  *
211
218
  * Theming hooks (CSS custom properties, all optional):
219
+ * --sw-loop repeated-line band; --sw-loop-edge its edge, the learner's
220
+ * hollow highlight and "your turn" tag (--sw-turn-ink on it)
221
+ * --sw-row-hover hover tint of a tappable line; --sw-focus the focus ring
212
222
  * --sw-font font stack (default: Noto Sans Bengali + system Bengali)
213
223
  * --sw-size base font size
214
224
  * --sw-cell-gap horizontal gap between source cells
@@ -501,9 +511,16 @@ interface SwaralipiSheetElement extends HTMLElement {
501
511
  doc: Swaralipi | undefined;
502
512
  /** Parse documents against the spec schema before rendering. */
503
513
  validate: boolean;
504
- /** Add the player-highlight class to the note at "part.row.note". */
505
- highlight(path: string): HTMLElement | null;
514
+ /** Add the player-highlight class to the note at "part.row.note" — drawn
515
+ * hollow when it is the learner's turn to sing it. */
516
+ highlight(path: string, yourTurn?: boolean): HTMLElement | null;
506
517
  clearHighlights(): void;
518
+ /** Shade the repeated lines (`part.row`, inclusive; null clears) and mark
519
+ * the learner's turn on them. The player's bindSheet calls this. */
520
+ markLoop(loop: {
521
+ from: string;
522
+ to: string;
523
+ } | null, yourTurn?: boolean): void;
507
524
  /** Open the print dialog (→ "Save as PDF") for the current document. */
508
525
  print(options?: PrintOptions): void;
509
526
  /** Options for the `staff` language (instrument label, tonic, bpm…). */
@@ -551,4 +568,4 @@ type Transliterator = (bengaliText: string) => string;
551
568
  */
552
569
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
553
570
 
554
- export { type AbcMeter, type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, FREE_TAAL_LABELS, type GridLang, LABELS, LAY_LABELS, type Layout, MARKS, MAX_ZOOM, MIN_ZOOM, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, ZOOM_STEP, abcDuration, abcToSvg, alignRows, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, fitSwaralipi, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
571
+ export { type AbcMeter, type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, FREE_TAAL_LABELS, type GridLang, LABELS, LAY_LABELS, type Layout, MARKS, MAX_ZOOM, MIN_ZOOM, NOTATION_FONT_URLS, PRACTICE_LABELS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, ZOOM_STEP, abcDuration, abcToSvg, alignRows, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, fitSwaralipi, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
@@ -1,6 +1,6 @@
1
1
  import { O as Octave, a as SvaraLetter, N as Note, M as Meta, S as Swaralipi, T as Taal } from '../schema-Cf_epUIC.js';
2
2
  import { L as Lay } from '../lay-CZIzR9cM.js';
3
- import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-BaovCtmf.js';
3
+ import { D as DrumVoice, S as Schedule, P as PlayEvent } from '../schedule-BUopqmWm.js';
4
4
  import 'zod';
5
5
  import 'tone';
6
6
 
@@ -34,6 +34,13 @@ declare const LAY_LABELS: Record<GridLang, Record<Lay, string>>;
34
34
  /** Printed where a row switches to free rhythm (`row.taal: null`) with no
35
35
  * `taalName` of its own. bn is the converter's canonical মুক্তছন্দ. */
36
36
  declare const FREE_TAAL_LABELS: Record<GridLang, string>;
37
+ /** The practice surface's words: the sheet's own accessible name (it says
38
+ * what a tap does), a repeat being on, and the learner's turn to sing. */
39
+ declare const PRACTICE_LABELS: Record<GridLang, {
40
+ sheet: string;
41
+ repeat: string;
42
+ turn: string;
43
+ }>;
37
44
  /** A composed notation cluster: the font's ASCII text plus any residual CSS
38
45
  * classes the font itself cannot express (only the bn ati-octave dot). */
39
46
  interface Cluster {
@@ -209,6 +216,9 @@ declare const ZOOM_STEP = 1.2;
209
216
  * themselves: `<style>${SWARALIPI_CSS}</style>` next to `renderToHtml()`.
210
217
  *
211
218
  * Theming hooks (CSS custom properties, all optional):
219
+ * --sw-loop repeated-line band; --sw-loop-edge its edge, the learner's
220
+ * hollow highlight and "your turn" tag (--sw-turn-ink on it)
221
+ * --sw-row-hover hover tint of a tappable line; --sw-focus the focus ring
212
222
  * --sw-font font stack (default: Noto Sans Bengali + system Bengali)
213
223
  * --sw-size base font size
214
224
  * --sw-cell-gap horizontal gap between source cells
@@ -501,9 +511,16 @@ interface SwaralipiSheetElement extends HTMLElement {
501
511
  doc: Swaralipi | undefined;
502
512
  /** Parse documents against the spec schema before rendering. */
503
513
  validate: boolean;
504
- /** Add the player-highlight class to the note at "part.row.note". */
505
- highlight(path: string): HTMLElement | null;
514
+ /** Add the player-highlight class to the note at "part.row.note" — drawn
515
+ * hollow when it is the learner's turn to sing it. */
516
+ highlight(path: string, yourTurn?: boolean): HTMLElement | null;
506
517
  clearHighlights(): void;
518
+ /** Shade the repeated lines (`part.row`, inclusive; null clears) and mark
519
+ * the learner's turn on them. The player's bindSheet calls this. */
520
+ markLoop(loop: {
521
+ from: string;
522
+ to: string;
523
+ } | null, yourTurn?: boolean): void;
507
524
  /** Open the print dialog (→ "Save as PDF") for the current document. */
508
525
  print(options?: PrintOptions): void;
509
526
  /** Options for the `staff` language (instrument label, tonic, bpm…). */
@@ -551,4 +568,4 @@ type Transliterator = (bengaliText: string) => string;
551
568
  */
552
569
  declare function loadLyricTransliterator(lang: ViewerLang): Promise<Transliterator | undefined>;
553
570
 
554
- export { type AbcMeter, type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, FREE_TAAL_LABELS, type GridLang, LABELS, LAY_LABELS, type Layout, MARKS, MAX_ZOOM, MIN_ZOOM, NOTATION_FONT_URLS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, ZOOM_STEP, abcDuration, abcToSvg, alignRows, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, fitSwaralipi, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
571
+ export { type AbcMeter, type Arc, type Cell, type Cluster, type ComposeOptions, DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, FREE_TAAL_LABELS, type GridLang, LABELS, LAY_LABELS, type Layout, MARKS, MAX_ZOOM, MIN_ZOOM, NOTATION_FONT_URLS, PRACTICE_LABELS, type PrintOptions, type RenderOptions, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, type StaffOptions, type SwaralipiSheetElement, type TaalStaffOptions, type Transliterator, type ViewerLang, ZOOM_STEP, abcDuration, abcToSvg, alignRows, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, fitSwaralipi, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
@@ -1,4 +1,4 @@
1
- import { isArcRaw, glideTargets, buildSchedule, drumPattern } from '../chunk-SEN266BY.js';
1
+ import { isArcRaw, glideTargets, buildSchedule, drumPattern } from '../chunk-JEJYMKKZ.js';
2
2
  import { hasKomal, isTaalFerta, sameTaal, TAALS, graceNotes } from '../chunk-MYVIQU4O.js';
3
3
 
4
4
  // src/viewer/glyphs.ts
@@ -54,6 +54,11 @@ var FREE_TAAL_LABELS = {
54
54
  hi: "\u092E\u0941\u0915\u094D\u0924\u091B\u0902\u0926",
55
55
  en: "Free rhythm"
56
56
  };
57
+ var PRACTICE_LABELS = {
58
+ bn: { sheet: "\u09B8\u09CD\u09AC\u09B0\u09B2\u09BF\u09AA\u09BF \u2014 \u09AF\u09C7 \u09B2\u09BE\u0987\u09A8 \u09A5\u09C7\u0995\u09C7 \u09B6\u09C1\u09A8\u09A4\u09C7 \u099A\u09BE\u09A8, \u09B8\u09C7\u0996\u09BE\u09A8\u09C7 \u099B\u09C1\u0981\u09AF\u09BC\u09C7 \u09A6\u09BF\u09A8", repeat: "\u09B2\u09BE\u0987\u09A8\u099F\u09BF \u09AC\u09BE\u09B0\u09AC\u09BE\u09B0 \u09AC\u09BE\u099C\u09AC\u09C7", turn: "\u098F\u09AC\u09BE\u09B0 \u0986\u09AA\u09A8\u09BF \u0997\u09BE\u09A8" },
59
+ hi: { sheet: "\u0938\u094D\u0935\u0930\u0932\u093F\u092A\u093F \u2014 \u091C\u093F\u0938 \u092A\u0902\u0915\u094D\u0924\u093F \u0938\u0947 \u0938\u0941\u0928\u0928\u093E \u0939\u094B, \u0909\u0938\u0947 \u091B\u0941\u090F\u0901", repeat: "\u092A\u0902\u0915\u094D\u0924\u093F \u0926\u094B\u0939\u0930\u093E\u0908 \u091C\u093E \u0930\u0939\u0940 \u0939\u0948", turn: "\u0905\u092C \u0906\u092A \u0917\u093E\u0907\u090F" },
60
+ en: { sheet: "Swaralipi \u2014 tap a line to play from it", repeat: "Repeating the line", turn: "Your turn \u2014 sing" }
61
+ };
57
62
  function composeCluster(letter, opts) {
58
63
  const classes = [];
59
64
  if (opts.lang === "bn") {
@@ -496,7 +501,7 @@ function renderRow(row, partIdx, rowIdx, offset, ctx) {
496
501
  if (below.length > 0)
497
502
  out.push(`<span class="sw-below" style="grid-column:${col}">${renderAnnotation(below, ctx)}</span>`);
498
503
  });
499
- return `<div class="sw-row">${out.join("")}</div>`;
504
+ return `<div class="sw-row" data-row="${partIdx}.${rowIdx}">${out.join("")}</div>`;
500
505
  }
501
506
  function renderHeader(meta, ctx) {
502
507
  return renderMetaHeader(meta, ctx.lang, ctx.tr);
@@ -820,7 +825,10 @@ ${FONT_FACES}
820
825
  font-size: 0.9em;
821
826
  line-height: 0;
822
827
  white-space: pre;
823
- overflow-x: clip;
828
+ /* Cuts the width-constrained runs (-b/-s/-f) at their note's edge. NOT
829
+ * overflow-x:clip \u2014 WebKit clips the block axis with it too, and these
830
+ * boxes are line-height 0, so Safari erased every \u09AE\u09C0\u09A1\u09BC on the sheet. */
831
+ clip-path: inset(-3em 0);
824
832
  }
825
833
  .sw-mo::after, .sw-mob::after, .sw-mos::after { content: 'wyyyyyyyyyyyy'; }
826
834
  /* rtl: a closing run cut short keeps its hook on its right edge */
@@ -865,6 +873,55 @@ ${FONT_FACES}
865
873
  glyph fill/stroke instead (abcjs draws note heads/stems as filled paths). */
866
874
  .sw-staff .sw-hl, .sw-staff .sw-hl * { fill: var(--sw-hl-note, #d68400); stroke: var(--sw-hl-note, #d68400); }
867
875
 
876
+ /* ---- practice: tap a line, repeat it, sing it back ----
877
+ * Only a sheet a player drives is [seekable]. The whole line is the tap
878
+ * target, so it is the line that answers the pointer. A repeat shades its
879
+ * lines and fades the rest (focus, as Soundslice fades outside a loop); on the
880
+ * learner's pass the highlight goes hollow \u2014 it marks where to sing, not what
881
+ * is sounding \u2014 and the repeat's first line wears a "your turn" tag. None of
882
+ * it moves a column: backgrounds, opacity and an out-of-flow tag only. */
883
+ :host([seekable]) .sw-row { cursor: pointer; }
884
+ :host(:focus-visible) { outline: 2px solid var(--sw-focus, currentColor); outline-offset: 0.3em; border-radius: 0.2em; }
885
+ .sw-row { border-radius: 0.3em; }
886
+ @media (hover: hover) {
887
+ :host([seekable]) .sw-row:hover { background: var(--sw-row-hover, rgba(128, 128, 128, 0.08)); }
888
+ }
889
+ .sw-looping .sw-row:not(.sw-loop) { opacity: 0.6; }
890
+ .sw-row.sw-loop {
891
+ background: var(--sw-loop, rgba(255, 196, 64, 0.13));
892
+ box-shadow: inset 0.2em 0 var(--sw-loop-edge, #a85a00);
893
+ }
894
+ .sw-hl.sw-hl-turn { background: none; box-shadow: inset 0 0 0 0.09em var(--sw-loop-edge, #a85a00); }
895
+ /* The tag rides the line's above-band at its right end \u2014 a grid item spanning
896
+ every column, so it sizes no track \u2014 and is sticky, so on a phone where the
897
+ line runs past the screen it waits at the visible edge instead. */
898
+ .sw-row[data-turn]::after {
899
+ content: attr(data-turn);
900
+ grid-column: 1 / -1;
901
+ grid-row: 1;
902
+ justify-self: end;
903
+ align-self: start;
904
+ position: sticky;
905
+ right: 0.2em;
906
+ translate: 0 -0.3em;
907
+ font-size: max(0.6em, 0.7rem);
908
+ font-weight: 600;
909
+ line-height: 1.6;
910
+ padding: 0 0.7em;
911
+ border-radius: 1em;
912
+ color: var(--sw-turn-ink, #fff);
913
+ background: var(--sw-loop-edge, #a85a00);
914
+ }
915
+ .sw-live { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
916
+ @media (prefers-reduced-motion: no-preference) {
917
+ .sw-row { transition: opacity 0.25s, background-color 0.25s; }
918
+ }
919
+ @media print {
920
+ .sw-row { opacity: 1 !important; background: none !important; box-shadow: none !important; }
921
+ .sw-row[data-turn]::after { content: none; }
922
+ .sw-hl { background: none !important; box-shadow: none !important; }
923
+ }
924
+
868
925
  /* ---- element error box ---- */
869
926
  .sw-error {
870
927
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
@@ -1300,6 +1357,17 @@ async function loadLyricTransliterator(lang) {
1300
1357
 
1301
1358
  // src/viewer/element.ts
1302
1359
  var LANGS = /* @__PURE__ */ new Set(["bn", "hi", "en", "staff"]);
1360
+ var KEYS = "Space ArrowUp ArrowDown ArrowLeft ArrowRight Home End R T Escape Shift+ArrowUp Shift+ArrowDown";
1361
+ function nearest(els, x) {
1362
+ let best;
1363
+ let gap = Infinity;
1364
+ for (const el of els) {
1365
+ const r = el.getBoundingClientRect();
1366
+ const d = Math.abs(r.left + r.width / 2 - x);
1367
+ if (d < gap) [best, gap] = [el, d];
1368
+ }
1369
+ return best;
1370
+ }
1303
1371
  function ensureNotationFonts() {
1304
1372
  if (typeof FontFace === "undefined" || typeof document === "undefined") return;
1305
1373
  const present = /* @__PURE__ */ new Set();
@@ -1312,7 +1380,7 @@ function ensureNotationFonts() {
1312
1380
  var sharedSheet;
1313
1381
  function createElementClass() {
1314
1382
  return class SwaralipiSheet extends HTMLElement {
1315
- static observedAttributes = ["src", "lang", "hide-header", "min-spacing", "min-size"];
1383
+ static observedAttributes = ["src", "lang", "hide-header", "min-spacing", "min-size", "seekable"];
1316
1384
  #doc;
1317
1385
  #root;
1318
1386
  #adopted = false;
@@ -1324,10 +1392,20 @@ function createElementClass() {
1324
1392
  #resize;
1325
1393
  #fittedAt = -1;
1326
1394
  #zoom = 1;
1395
+ /** Where the pointer went down, to tell a tap from a drag. */
1396
+ #down;
1397
+ #marks = { loop: null, turn: false };
1398
+ /** Attributes `seekable` added, and so takes away again. */
1399
+ #owned = [];
1327
1400
  validate = false;
1328
1401
  constructor() {
1329
1402
  super();
1330
1403
  this.#root = this.attachShadow({ mode: "open" });
1404
+ this.#root.addEventListener("pointerdown", (e) => {
1405
+ const { clientX, clientY } = e;
1406
+ this.#down = [clientX, clientY];
1407
+ });
1408
+ this.#root.addEventListener("click", (e) => this.#onClick(e));
1331
1409
  if (typeof CSSStyleSheet !== "undefined" && "replaceSync" in CSSStyleSheet.prototype && "adoptedStyleSheets" in this.#root) {
1332
1410
  if (!sharedSheet) {
1333
1411
  sharedSheet = new CSSStyleSheet();
@@ -1361,6 +1439,27 @@ function createElementClass() {
1361
1439
  const attr = this.getAttribute("lang");
1362
1440
  return attr !== null && LANGS.has(attr) ? attr : "bn";
1363
1441
  }
1442
+ get #practice() {
1443
+ const lang = this.#lang;
1444
+ return PRACTICE_LABELS[lang === "staff" ? "en" : lang];
1445
+ }
1446
+ /** A tap on a line plays from the note nearest the tap: the whole line is
1447
+ * the target (WCAG 2.5.8 counts a position inside one target as picking
1448
+ * a value), fired on the up-event like any click (2.5.2). The player bound
1449
+ * by bindSheet answers it; a host can veto it with preventDefault(). */
1450
+ #onClick(e) {
1451
+ if (!this.hasAttribute("seekable") || e.button !== 0 || e.ctrlKey || e.metaKey || e.altKey) return;
1452
+ if (this.#down !== void 0 && Math.hypot(e.clientX - this.#down[0], e.clientY - this.#down[1]) > 6) return;
1453
+ const target = e.target;
1454
+ const row = target.closest(".sw-row");
1455
+ const notes = row === null ? [] : [...row.querySelectorAll("[data-note]:not(.sw-bar)")];
1456
+ const hit = target.closest("[data-note]:not(.sw-bar)") ?? nearest(notes, e.clientX);
1457
+ const path = hit?.getAttribute("data-note");
1458
+ if (path == null) return;
1459
+ this.dispatchEvent(
1460
+ new CustomEvent("swaralipi-seek", { detail: { path, extend: e.shiftKey }, bubbles: true, cancelable: true })
1461
+ );
1462
+ }
1364
1463
  attributeChangedCallback(name, oldValue, newValue) {
1365
1464
  if (oldValue === newValue) return;
1366
1465
  if (name === "src") {
@@ -1371,6 +1470,19 @@ function createElementClass() {
1371
1470
  this.fit();
1372
1471
  return;
1373
1472
  }
1473
+ if (name === "seekable") {
1474
+ for (const a of this.#owned) this.removeAttribute(a);
1475
+ this.#owned = [];
1476
+ if (newValue === null) return;
1477
+ const want = { tabindex: "0", role: "region", "aria-label": this.#practice.sheet, "aria-keyshortcuts": KEYS };
1478
+ for (const [a, v] of Object.entries(want)) {
1479
+ if (this.hasAttribute(a)) continue;
1480
+ this.setAttribute(a, v);
1481
+ this.#owned.push(a);
1482
+ }
1483
+ return;
1484
+ }
1485
+ if (name === "lang" && this.#owned.includes("aria-label")) this.setAttribute("aria-label", this.#practice.sheet);
1374
1486
  if (name === "lang") this.#ensureTransliterator();
1375
1487
  this.#render();
1376
1488
  }
@@ -1483,7 +1595,9 @@ function createElementClass() {
1483
1595
  if (lang === "staff") {
1484
1596
  const doc = this.#doc;
1485
1597
  void renderStaffHtml(doc, this.staffOptions).then((html2) => {
1486
- if (seq === this.#renderSeq) this.#root.innerHTML = this.#styleFallback() + html2;
1598
+ if (seq !== this.#renderSeq) return;
1599
+ this.#root.innerHTML = this.#styleFallback() + html2;
1600
+ this.#applyMarks();
1487
1601
  }).catch(() => void 0);
1488
1602
  return;
1489
1603
  }
@@ -1493,8 +1607,34 @@ function createElementClass() {
1493
1607
  transliterate: this.#translitLang === lang ? this.#translit : void 0
1494
1608
  });
1495
1609
  this.#root.innerHTML = this.#styleFallback() + html;
1610
+ this.#applyMarks();
1496
1611
  this.#fit();
1497
1612
  }
1613
+ markLoop(loop, yourTurn = false) {
1614
+ this.#marks = { loop, turn: yourTurn };
1615
+ this.#applyMarks();
1616
+ }
1617
+ /** Shade the repeated lines and dim the rest; the first of them carries
1618
+ * the "your turn" cue. A polite live region says the same aloud. */
1619
+ #applyMarks() {
1620
+ const { loop, turn } = this.#marks;
1621
+ const rows = [...this.#root.querySelectorAll(".sw-row")];
1622
+ const at = (row) => rows.findIndex((r) => r.dataset.row === row);
1623
+ const [a, b] = loop === null ? [0, -1] : [at(loop.from), at(loop.to)];
1624
+ rows.forEach((r, i) => {
1625
+ r.classList.toggle("sw-loop", i >= a && i <= b);
1626
+ if (turn && i === a) r.dataset.turn = this.#practice.turn;
1627
+ else delete r.dataset.turn;
1628
+ });
1629
+ this.#root.querySelector(".sw-block")?.classList.toggle("sw-looping", loop !== null);
1630
+ let live = this.#root.querySelector(".sw-live");
1631
+ if (live === null) {
1632
+ live = Object.assign(document.createElement("div"), { className: "sw-live" });
1633
+ live.setAttribute("role", "status");
1634
+ this.#root.append(live);
1635
+ }
1636
+ live.textContent = turn ? this.#practice.turn : loop !== null ? this.#practice.repeat : "";
1637
+ }
1498
1638
  #renderError(message) {
1499
1639
  const div = document.createElement("div");
1500
1640
  div.className = "sw-error";
@@ -1502,14 +1642,15 @@ function createElementClass() {
1502
1642
  this.#root.innerHTML = this.#styleFallback();
1503
1643
  this.#root.appendChild(div);
1504
1644
  }
1505
- highlight(path) {
1645
+ highlight(path, yourTurn = false) {
1506
1646
  if (!/^\d+\.\d+\.\d+$/.test(path)) return null;
1507
1647
  const el = this.#root.querySelector(`[data-note="${path}"]`);
1508
1648
  el?.classList.add("sw-hl");
1649
+ el?.classList.toggle("sw-hl-turn", yourTurn);
1509
1650
  return el;
1510
1651
  }
1511
1652
  clearHighlights() {
1512
- for (const el of this.#root.querySelectorAll(".sw-hl")) el.classList.remove("sw-hl");
1653
+ for (const el of this.#root.querySelectorAll(".sw-hl")) el.classList.remove("sw-hl", "sw-hl-turn");
1513
1654
  }
1514
1655
  print(options = {}) {
1515
1656
  if (this.#doc === void 0) return;
@@ -1538,6 +1679,6 @@ function defineSwaralipiSheet(tagName = "swaralipi-sheet") {
1538
1679
  return ctor;
1539
1680
  }
1540
1681
 
1541
- export { DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, FREE_TAAL_LABELS, LABELS, LAY_LABELS, MARKS, MAX_ZOOM, MIN_ZOOM, NOTATION_FONT_URLS, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, ZOOM_STEP, abcDuration, abcToSvg, alignRows, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, fitSwaralipi, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
1682
+ export { DEFAULT_NOTATOR, DEFAULT_WATERMARK, DIGITS, FREE_TAAL_LABELS, LABELS, LAY_LABELS, MARKS, MAX_ZOOM, MIN_ZOOM, NOTATION_FONT_URLS, PRACTICE_LABELS, SWARALIPI_CSS, SWARALIPI_PRINT_CSS, SWARALIPI_STAFF_CSS, ZOOM_STEP, abcDuration, abcToSvg, alignRows, buildAbc, buildTaalAbc, cellMatra, composeCluster, defineSwaralipiSheet, docToAbc, fitSwaralipi, groupRowIntoCells, loadLyricTransliterator, localizeDigits, midiToAbc, printSwaralipi, renderMetaHeader, renderPrintDocument, renderStaffHtml, renderStaffPrintDocument, renderTaalStaffHtml, renderToHtml, resolveArcs };
1542
1683
  //# sourceMappingURL=index.js.map
1543
1684
  //# sourceMappingURL=index.js.map