@kodezen/player 1.0.2 → 1.0.3
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 +21 -1
- package/dist/index.cjs +199 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +199 -9
- package/dist/index.js.map +1 -1
- package/dist/react/index.cjs +203 -11
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.d.cts +2 -2
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +203 -11
- package/dist/react/index.js.map +1 -1
- package/dist/styles.css +56 -0
- package/dist/{types-TFltFgey.d.cts → types-30yBj4ll.d.cts} +20 -1
- package/dist/{types-TFltFgey.d.ts → types-30yBj4ll.d.ts} +20 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,7 +70,8 @@ import '@kodezen/player/styles.css';
|
|
|
70
70
|
`createPlayer(container, options)` → instance.
|
|
71
71
|
|
|
72
72
|
**Options:** `source {type,url,id,poster,mime}`, `resumeAt`, `lockSeek`,
|
|
73
|
-
`autoplay`, `muted`, `speeds[]`, `controls{}`, `className
|
|
73
|
+
`autoplay`, `muted`, `speeds[]`, `controls{}`, `className`,
|
|
74
|
+
`tracks[] {src,label,srclang,default}`.
|
|
74
75
|
|
|
75
76
|
**Instance:** `play()`, `pause()`, `toggle()`, `seek(s)`, `getState()`,
|
|
76
77
|
`on(event, cb)`, `off(event, cb)`, `destroy()`.
|
|
@@ -80,6 +81,25 @@ import '@kodezen/player/styles.css';
|
|
|
80
81
|
(`percent` = furthest / duration, so it reflects watched coverage, not the
|
|
81
82
|
scrub position).
|
|
82
83
|
|
|
84
|
+
## Subtitles / captions
|
|
85
|
+
|
|
86
|
+
Pass WebVTT files as `tracks`. The player fetches and renders them itself in
|
|
87
|
+
an overlay, so the same `.vtt` works for `html5`, `youtube` and `vimeo`
|
|
88
|
+
(YouTube/Vimeo iframes can't load an external caption file). A CC button
|
|
89
|
+
toggles a single track, or opens a menu when there are several. A track with
|
|
90
|
+
`default: true` is shown as soon as it loads; otherwise captions start off.
|
|
91
|
+
|
|
92
|
+
```js
|
|
93
|
+
createPlayer(el, {
|
|
94
|
+
source: { type: 'youtube', id: 'dQw4w9WgXcQ' },
|
|
95
|
+
tracks: [{ src: '/uploads/lesson-1.vtt', label: 'English', srclang: 'en', default: true }],
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
The file must be same-origin or served with CORS. Cue settings
|
|
100
|
+
(position/alignment) and styling blocks are ignored — cues render as plain
|
|
101
|
+
centred text. `parseVtt(text)` is exported if you need the cues yourself.
|
|
102
|
+
|
|
83
103
|
## Host integration notes
|
|
84
104
|
|
|
85
105
|
- **Resume** = pass `resumeAt`; player seeks on `ready` unless within 2s of end.
|
package/dist/index.cjs
CHANGED
|
@@ -447,13 +447,124 @@ function createProvider(source) {
|
|
|
447
447
|
}
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
+
// src/core/Captions.ts
|
|
451
|
+
var TIMING = /^\s*((?:\d+:)?\d{1,2}:\d{2}[.,]\d{1,3})\s+-->\s+((?:\d+:)?\d{1,2}:\d{2}[.,]\d{1,3})/;
|
|
452
|
+
var ENTITIES = {
|
|
453
|
+
amp: "&",
|
|
454
|
+
lt: "<",
|
|
455
|
+
gt: ">",
|
|
456
|
+
quot: '"',
|
|
457
|
+
apos: "'",
|
|
458
|
+
nbsp: "\xA0",
|
|
459
|
+
lrm: "\u200E",
|
|
460
|
+
rlm: "\u200F"
|
|
461
|
+
};
|
|
462
|
+
function toSeconds(stamp) {
|
|
463
|
+
const [clock = "", fraction = "0"] = stamp.replace(",", ".").split(".");
|
|
464
|
+
const seconds = clock.split(":").reduce((total, part) => total * 60 + Number(part), 0);
|
|
465
|
+
return seconds + Number(`0.${fraction}`);
|
|
466
|
+
}
|
|
467
|
+
function plainText(text) {
|
|
468
|
+
return text.replace(/<[^>]*>/g, "").replace(/&(amp|lt|gt|quot|apos|nbsp|lrm|rlm);/g, (_, name) => {
|
|
469
|
+
var _a;
|
|
470
|
+
return (_a = ENTITIES[name]) != null ? _a : "";
|
|
471
|
+
}).replace(/&#(\d+);/g, (_, code) => String.fromCodePoint(Number(code)));
|
|
472
|
+
}
|
|
473
|
+
function parseVtt(input) {
|
|
474
|
+
var _a;
|
|
475
|
+
const cues = [];
|
|
476
|
+
const blocks = input.replace(/^/, "").replace(/\r\n?/g, "\n").split(/\n{2,}/);
|
|
477
|
+
for (const block of blocks) {
|
|
478
|
+
const lines = block.split("\n");
|
|
479
|
+
const timingAt = lines.findIndex((line) => line.includes("-->"));
|
|
480
|
+
const timing = TIMING.exec((_a = lines[timingAt]) != null ? _a : "");
|
|
481
|
+
if (!(timing == null ? void 0 : timing[1]) || !timing[2]) continue;
|
|
482
|
+
const text = plainText(lines.slice(timingAt + 1).join("\n")).trim();
|
|
483
|
+
if (!text) continue;
|
|
484
|
+
cues.push({ start: toSeconds(timing[1]), end: toSeconds(timing[2]), text });
|
|
485
|
+
}
|
|
486
|
+
return cues.sort((a, b) => a.start - b.start);
|
|
487
|
+
}
|
|
488
|
+
var Captions = class {
|
|
489
|
+
/** `onChange` fires when the active track or track availability changes. */
|
|
490
|
+
constructor(tracks, onChange) {
|
|
491
|
+
this.onChange = onChange;
|
|
492
|
+
this.active = -1;
|
|
493
|
+
this.time = 0;
|
|
494
|
+
this.abort = new AbortController();
|
|
495
|
+
this.el = document.createElement("div");
|
|
496
|
+
this.el.className = "kzp-captions";
|
|
497
|
+
this.el.setAttribute("aria-live", "off");
|
|
498
|
+
this.text = document.createElement("span");
|
|
499
|
+
this.text.className = "kzp-captions-text";
|
|
500
|
+
this.text.hidden = true;
|
|
501
|
+
this.el.appendChild(this.text);
|
|
502
|
+
this.tracks = tracks.map((info) => ({ info, cues: null, failed: false }));
|
|
503
|
+
const preferred = tracks.findIndex((t) => t.default);
|
|
504
|
+
this.tracks.forEach((track, index) => {
|
|
505
|
+
void this.load(track).then(() => {
|
|
506
|
+
if (index === preferred && this.active === -1 && track.cues) this.select(index);
|
|
507
|
+
else this.onChange();
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
async load(track) {
|
|
512
|
+
try {
|
|
513
|
+
const res = await fetch(track.info.src, { signal: this.abort.signal, credentials: "same-origin" });
|
|
514
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
515
|
+
track.cues = parseVtt(await res.text());
|
|
516
|
+
} catch (err) {
|
|
517
|
+
if (this.abort.signal.aborted) return;
|
|
518
|
+
track.failed = true;
|
|
519
|
+
console.warn(`@kodezen/player: could not load captions from ${track.info.src}`, err);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
/** Labels for the menu, one per track (falls back to the language, then a number). */
|
|
523
|
+
labels() {
|
|
524
|
+
return this.tracks.map((t, i) => t.info.label || t.info.srclang || `Track ${i + 1}`);
|
|
525
|
+
}
|
|
526
|
+
/** Whether each track loaded (a failed track is hidden from the controls). */
|
|
527
|
+
available() {
|
|
528
|
+
return this.tracks.map((t) => !t.failed);
|
|
529
|
+
}
|
|
530
|
+
getActive() {
|
|
531
|
+
return this.active;
|
|
532
|
+
}
|
|
533
|
+
/** Show track `index`, or turn captions off with -1. */
|
|
534
|
+
select(index) {
|
|
535
|
+
var _a;
|
|
536
|
+
const track = this.tracks[index];
|
|
537
|
+
this.active = track && !track.failed ? index : -1;
|
|
538
|
+
this.el.lang = (_a = track == null ? void 0 : track.info.srclang) != null ? _a : "";
|
|
539
|
+
this.render();
|
|
540
|
+
this.onChange();
|
|
541
|
+
}
|
|
542
|
+
/** Called with the playhead position on every tick and after a seek. */
|
|
543
|
+
update(time) {
|
|
544
|
+
this.time = time;
|
|
545
|
+
this.render();
|
|
546
|
+
}
|
|
547
|
+
render() {
|
|
548
|
+
var _a, _b;
|
|
549
|
+
const cues = (_b = (_a = this.tracks[this.active]) == null ? void 0 : _a.cues) != null ? _b : [];
|
|
550
|
+
const text = cues.filter((cue) => this.time >= cue.start && this.time < cue.end).map((cue) => cue.text).join("\n");
|
|
551
|
+
if (text !== this.text.textContent) this.text.textContent = text;
|
|
552
|
+
this.text.hidden = !text;
|
|
553
|
+
}
|
|
554
|
+
destroy() {
|
|
555
|
+
this.abort.abort();
|
|
556
|
+
this.el.remove();
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
450
560
|
// src/core/Controls.ts
|
|
451
561
|
var ICONS = {
|
|
452
562
|
play: '<path d="M8 5v14l11-7z"/>',
|
|
453
563
|
pause: '<path d="M6 5h4v14H6zM14 5h4v14h-4z"/>',
|
|
454
564
|
volume: '<path d="M3 10v4h4l5 5V5L7 10H3zm13.5 2a4.5 4.5 0 0 0-2.5-4v8a4.5 4.5 0 0 0 2.5-4z"/>',
|
|
455
565
|
muted: '<path d="M3 10v4h4l5 5V5L7 10H3zm13 2 3 3 1-1-3-3 3-3-1-1-3 3-3-3-1 1 3 3-3 3 1 1z"/>',
|
|
456
|
-
full: '<path d="M4 4h6v2H6v4H4V4zm10 0h6v6h-2V6h-4V4zM4 14h2v4h4v2H4v-6zm14 0h2v6h-6v-2h4v-4z"/>'
|
|
566
|
+
full: '<path d="M4 4h6v2H6v4H4V4zm10 0h6v6h-2V6h-4V4zM4 14h2v4h4v2H4v-6zm14 0h2v6h-6v-2h4v-4z"/>',
|
|
567
|
+
captions: '<path d="M19 4H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm-8 7H9.5v-.5h-2v3h2V13H11v1a1 1 0 0 1-1 1H7a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zm7 0h-1.5v-.5h-2v3h2V13H18v1a1 1 0 0 1-1 1h-3a1 1 0 0 1-1-1v-4a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1z"/>'
|
|
457
568
|
};
|
|
458
569
|
function icon(paths) {
|
|
459
570
|
return `<svg viewBox="0 0 24 24" aria-hidden="true">${paths}</svg>`;
|
|
@@ -473,20 +584,23 @@ var DEFAULT_CONFIG = {
|
|
|
473
584
|
time: true,
|
|
474
585
|
volume: true,
|
|
475
586
|
speed: true,
|
|
587
|
+
captions: true,
|
|
476
588
|
fullscreen: true
|
|
477
589
|
};
|
|
478
590
|
var Controls = class {
|
|
479
591
|
constructor(cb, opts) {
|
|
480
592
|
this.cb = cb;
|
|
593
|
+
this.ccActive = -1;
|
|
481
594
|
this.scrubbing = false;
|
|
482
595
|
this.seekableMax = Infinity;
|
|
596
|
+
var _a;
|
|
483
597
|
this.config = { ...DEFAULT_CONFIG, ...opts.config };
|
|
484
598
|
this.lockSeek = opts.lockSeek;
|
|
485
599
|
this.el = document.createElement("div");
|
|
486
600
|
this.el.className = "kzp-controls";
|
|
487
|
-
this.build(opts.speeds);
|
|
601
|
+
this.build(opts.speeds, (_a = opts.captionLabels) != null ? _a : []);
|
|
488
602
|
}
|
|
489
|
-
build(speeds) {
|
|
603
|
+
build(speeds, captionLabels) {
|
|
490
604
|
if (this.config.playPause) {
|
|
491
605
|
this.playBtn = this.button("kzp-play", "Play", ICONS.play);
|
|
492
606
|
this.playBtn.addEventListener("click", () => this.cb.onToggle());
|
|
@@ -563,16 +677,72 @@ var Controls = class {
|
|
|
563
677
|
}
|
|
564
678
|
this.speedBtn.addEventListener("click", () => {
|
|
565
679
|
this.speedMenu.hidden = !this.speedMenu.hidden;
|
|
680
|
+
if (this.ccMenu) this.ccMenu.hidden = true;
|
|
566
681
|
});
|
|
567
682
|
wrap.append(this.speedBtn, this.speedMenu);
|
|
568
683
|
this.el.appendChild(wrap);
|
|
569
684
|
}
|
|
685
|
+
if (this.config.captions && captionLabels.length) {
|
|
686
|
+
this.buildCaptions(captionLabels);
|
|
687
|
+
}
|
|
570
688
|
if (this.config.fullscreen) {
|
|
571
689
|
const btn = this.button("kzp-full", "Fullscreen", ICONS.full);
|
|
572
690
|
btn.addEventListener("click", () => this.cb.onFullscreen());
|
|
573
691
|
this.el.appendChild(btn);
|
|
574
692
|
}
|
|
575
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* One track: the CC button toggles it. Several: it opens a menu (Off + each
|
|
696
|
+
* track), styled like the speed menu. Sits left of the speed control.
|
|
697
|
+
*/
|
|
698
|
+
buildCaptions(labels) {
|
|
699
|
+
const wrap = document.createElement("div");
|
|
700
|
+
wrap.className = "kzp-cc";
|
|
701
|
+
this.ccBtn = this.button("kzp-cc-btn", "Subtitles/closed captions", ICONS.captions);
|
|
702
|
+
this.ccBtn.setAttribute("aria-pressed", "false");
|
|
703
|
+
wrap.appendChild(this.ccBtn);
|
|
704
|
+
if (labels.length === 1) {
|
|
705
|
+
this.ccBtn.addEventListener("click", () => this.cb.onCaptions(this.ccActive === -1 ? 0 : -1));
|
|
706
|
+
} else {
|
|
707
|
+
this.ccMenu = document.createElement("div");
|
|
708
|
+
this.ccMenu.className = "kzp-speed-menu kzp-cc-menu";
|
|
709
|
+
this.ccMenu.hidden = true;
|
|
710
|
+
const items = [{ index: -1, label: "Off" }, ...labels.map((label, index) => ({ index, label }))];
|
|
711
|
+
for (const { index, label } of items) {
|
|
712
|
+
const item = document.createElement("button");
|
|
713
|
+
item.type = "button";
|
|
714
|
+
item.textContent = label;
|
|
715
|
+
item.dataset.track = String(index);
|
|
716
|
+
item.addEventListener("click", () => {
|
|
717
|
+
this.cb.onCaptions(index);
|
|
718
|
+
this.ccMenu.hidden = true;
|
|
719
|
+
});
|
|
720
|
+
this.ccMenu.appendChild(item);
|
|
721
|
+
}
|
|
722
|
+
this.ccBtn.addEventListener("click", () => {
|
|
723
|
+
this.ccMenu.hidden = !this.ccMenu.hidden;
|
|
724
|
+
if (this.speedMenu) this.speedMenu.hidden = true;
|
|
725
|
+
});
|
|
726
|
+
wrap.appendChild(this.ccMenu);
|
|
727
|
+
}
|
|
728
|
+
this.ccWrap = wrap;
|
|
729
|
+
const speed = this.el.querySelector(".kzp-speed");
|
|
730
|
+
this.el.insertBefore(wrap, speed);
|
|
731
|
+
}
|
|
732
|
+
/** Reflect the active caption track, and hide tracks (or the whole control) that failed to load. */
|
|
733
|
+
setCaptions(active, available) {
|
|
734
|
+
var _a;
|
|
735
|
+
this.ccActive = active;
|
|
736
|
+
if (!this.ccBtn || !this.ccWrap) return;
|
|
737
|
+
this.ccWrap.hidden = !available.some(Boolean);
|
|
738
|
+
this.ccBtn.classList.toggle("kzp-cc-btn--on", active !== -1);
|
|
739
|
+
this.ccBtn.setAttribute("aria-pressed", String(active !== -1));
|
|
740
|
+
(_a = this.ccMenu) == null ? void 0 : _a.querySelectorAll("button").forEach((item) => {
|
|
741
|
+
const index = Number(item.dataset.track);
|
|
742
|
+
item.hidden = index !== -1 && !available[index];
|
|
743
|
+
item.classList.toggle("kzp-menu-item--active", index === active);
|
|
744
|
+
});
|
|
745
|
+
}
|
|
576
746
|
button(cls, label, svg) {
|
|
577
747
|
const b = document.createElement("button");
|
|
578
748
|
b.type = "button";
|
|
@@ -622,13 +792,14 @@ var Player = class {
|
|
|
622
792
|
this.container = container;
|
|
623
793
|
this.options = options;
|
|
624
794
|
this.emitter = new Emitter();
|
|
795
|
+
this.captions = null;
|
|
625
796
|
this.furthest = 0;
|
|
626
797
|
this.lastTick = 0;
|
|
627
798
|
this.duration = 0;
|
|
628
799
|
this.playing = false;
|
|
629
800
|
this.ready = false;
|
|
630
801
|
this.disposers = [];
|
|
631
|
-
var _a, _b, _c, _d, _e, _f, _g;
|
|
802
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
632
803
|
const speeds = (_a = options.speeds) != null ? _a : DEFAULT_SPEEDS;
|
|
633
804
|
this.root = document.createElement("div");
|
|
634
805
|
this.root.className = "kzp" + (options.className ? ` ${options.className}` : "");
|
|
@@ -643,6 +814,10 @@ var Player = class {
|
|
|
643
814
|
this.stage.appendChild(hitLayer);
|
|
644
815
|
this.provider = createProvider(options.source);
|
|
645
816
|
const caps = this.provider.capabilities;
|
|
817
|
+
if ((_b = options.tracks) == null ? void 0 : _b.length) {
|
|
818
|
+
this.captions = new Captions(options.tracks, () => this.syncCaptions());
|
|
819
|
+
this.root.appendChild(this.captions.el);
|
|
820
|
+
}
|
|
646
821
|
this.controls = new Controls(
|
|
647
822
|
{
|
|
648
823
|
onToggle: () => this.toggle(),
|
|
@@ -660,17 +835,22 @@ var Player = class {
|
|
|
660
835
|
this.provider.setRate(r);
|
|
661
836
|
this.render();
|
|
662
837
|
},
|
|
838
|
+
onCaptions: (index) => {
|
|
839
|
+
var _a2;
|
|
840
|
+
return (_a2 = this.captions) == null ? void 0 : _a2.select(index);
|
|
841
|
+
},
|
|
663
842
|
onFullscreen: () => this.toggleFullscreen()
|
|
664
843
|
},
|
|
665
844
|
{
|
|
666
845
|
config: {
|
|
667
846
|
...options.controls,
|
|
668
|
-
seek: ((
|
|
669
|
-
speed: ((
|
|
670
|
-
volume: ((
|
|
847
|
+
seek: ((_d = (_c = options.controls) == null ? void 0 : _c.seek) != null ? _d : true) && caps.seekable,
|
|
848
|
+
speed: ((_f = (_e = options.controls) == null ? void 0 : _e.speed) != null ? _f : true) && caps.rate,
|
|
849
|
+
volume: ((_h = (_g = options.controls) == null ? void 0 : _g.volume) != null ? _h : true) && caps.volume
|
|
671
850
|
},
|
|
672
851
|
speeds,
|
|
673
|
-
lockSeek: !!options.lockSeek
|
|
852
|
+
lockSeek: !!options.lockSeek,
|
|
853
|
+
captionLabels: (_i = this.captions) == null ? void 0 : _i.labels()
|
|
674
854
|
}
|
|
675
855
|
);
|
|
676
856
|
this.root.appendChild(this.controls.el);
|
|
@@ -723,6 +903,7 @@ var Player = class {
|
|
|
723
903
|
this.render();
|
|
724
904
|
}
|
|
725
905
|
onTimeUpdate(current) {
|
|
906
|
+
var _a;
|
|
726
907
|
const advance = current - this.lastTick;
|
|
727
908
|
if (advance > 0 && advance <= NATURAL_ADVANCE_MAX && current > this.furthest) {
|
|
728
909
|
this.furthest = current;
|
|
@@ -730,9 +911,14 @@ var Player = class {
|
|
|
730
911
|
this.provider.seek(this.furthest);
|
|
731
912
|
}
|
|
732
913
|
this.lastTick = current;
|
|
914
|
+
(_a = this.captions) == null ? void 0 : _a.update(current);
|
|
733
915
|
this.emitter.emit("timeupdate", this.snapshot());
|
|
734
916
|
this.render();
|
|
735
917
|
}
|
|
918
|
+
syncCaptions() {
|
|
919
|
+
if (!this.captions) return;
|
|
920
|
+
this.controls.setCaptions(this.captions.getActive(), this.captions.available());
|
|
921
|
+
}
|
|
736
922
|
snapshot() {
|
|
737
923
|
const current = this.provider.getCurrentTime();
|
|
738
924
|
const duration = this.duration || this.provider.getDuration();
|
|
@@ -766,10 +952,12 @@ var Player = class {
|
|
|
766
952
|
else this.provider.play();
|
|
767
953
|
}
|
|
768
954
|
seek(seconds) {
|
|
955
|
+
var _a;
|
|
769
956
|
if (!this.provider.capabilities.seekable) return;
|
|
770
957
|
const target = this.options.lockSeek && seconds > this.furthest ? this.furthest : Math.max(0, seconds);
|
|
771
958
|
this.provider.seek(target);
|
|
772
959
|
this.lastTick = target;
|
|
960
|
+
(_a = this.captions) == null ? void 0 : _a.update(target);
|
|
773
961
|
this.render();
|
|
774
962
|
}
|
|
775
963
|
setLockSeek(locked) {
|
|
@@ -788,9 +976,11 @@ var Player = class {
|
|
|
788
976
|
this.emitter.off(event, cb);
|
|
789
977
|
}
|
|
790
978
|
destroy() {
|
|
979
|
+
var _a;
|
|
791
980
|
for (const dispose of this.disposers) dispose();
|
|
792
981
|
this.disposers = [];
|
|
793
982
|
this.provider.destroy();
|
|
983
|
+
(_a = this.captions) == null ? void 0 : _a.destroy();
|
|
794
984
|
this.controls.destroy();
|
|
795
985
|
this.emitter.clear();
|
|
796
986
|
this.root.remove();
|
|
@@ -804,5 +994,6 @@ function createPlayer(container, options) {
|
|
|
804
994
|
|
|
805
995
|
exports.Player = Player;
|
|
806
996
|
exports.createPlayer = createPlayer;
|
|
997
|
+
exports.parseVtt = parseVtt;
|
|
807
998
|
//# sourceMappingURL=index.cjs.map
|
|
808
999
|
//# sourceMappingURL=index.cjs.map
|