@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.
- package/README.md +17 -0
- package/dist/{batch-NEM7JXHV.js → batch-3PHPNX2P.js} +4 -4
- package/dist/{batch-NEM7JXHV.js.map → batch-3PHPNX2P.js.map} +1 -1
- package/dist/{chunk-DOEFSS66.js → chunk-FGMULPL2.js} +3 -3
- package/dist/{chunk-DOEFSS66.js.map → chunk-FGMULPL2.js.map} +1 -1
- package/dist/{chunk-Z6A3CP5V.js → chunk-I5GJWSD2.js} +3 -3
- package/dist/{chunk-Z6A3CP5V.js.map → chunk-I5GJWSD2.js.map} +1 -1
- package/dist/{chunk-SEN266BY.js → chunk-JEJYMKKZ.js} +26 -3
- package/dist/chunk-JEJYMKKZ.js.map +1 -0
- package/dist/cli.cjs +1 -1
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +2 -2
- package/dist/convert/index.cjs +1 -1
- package/dist/convert/index.cjs.map +1 -1
- package/dist/convert/index.js +2 -2
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/player/index.cjs +436 -50
- package/dist/player/index.cjs.map +1 -1
- package/dist/player/index.d.cts +76 -11
- package/dist/player/index.d.ts +76 -11
- package/dist/player/index.js +412 -51
- package/dist/player/index.js.map +1 -1
- package/dist/{schedule-BaovCtmf.d.ts → schedule-BUopqmWm.d.ts} +27 -1
- package/dist/{schedule-CU-VECDH.d.cts → schedule-IBu8lr-3.d.cts} +27 -1
- package/dist/spec/index.cjs +1 -1
- package/dist/spec/index.cjs.map +1 -1
- package/dist/spec/index.d.cts +1 -1
- package/dist/spec/index.d.ts +1 -1
- package/dist/spec/index.js +1 -1
- package/dist/viewer/index.cjs +149 -7
- package/dist/viewer/index.cjs.map +1 -1
- package/dist/viewer/index.d.cts +21 -4
- package/dist/viewer/index.d.ts +21 -4
- package/dist/viewer/index.js +149 -8
- package/dist/viewer/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-SEN266BY.js.map +0 -1
package/dist/viewer/index.d.cts
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
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 };
|
package/dist/viewer/index.d.ts
CHANGED
|
@@ -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-
|
|
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
|
-
|
|
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 };
|
package/dist/viewer/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isArcRaw, glideTargets, buildSchedule, drumPattern } from '../chunk-
|
|
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
|
-
|
|
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
|
|
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
|