chart2music
Version:
Turns charts into music so the blind can hear data
1,209 lines (1,186 loc) • 338 kB
JavaScript
var c2mChart = (function () {
'use strict';
var AudioNotificationType;
(function (AudioNotificationType) {
AudioNotificationType["Annotation"] = "Annotation";
})(AudioNotificationType || (AudioNotificationType = {}));
const C3 = 130.8128;
const G3 = 195.9977;
const C4 = C3 * 2;
const G4 = G3 * 2;
const G5 = G4 * 2;
class OscillatorAudioEngine {
constructor(context) {
this._audioContext = context;
this._masterCompressor = context.createDynamicsCompressor();
this._masterCompressor.connect(this._audioContext.destination);
this._masterCompressor.threshold.value = -50;
this._masterCompressor.knee.value = 40;
this._masterCompressor.ratio.value = 12;
this._masterCompressor.attack.value = 0;
this._masterCompressor.release.value = 0.25;
this._masterGain = this._audioContext.createGain();
this._masterGain.gain.value = 0.5;
this._masterCompressor.connect(this._masterGain);
this._masterGain.connect(this._audioContext.destination);
}
get masterGain() {
return this._masterGain.gain.value;
}
set masterGain(value) {
this._masterGain.gain.value = value;
}
playDataPoint(frequency, panning, duration = 0.2) {
this._playDataPoint(frequency, panning, duration, this._masterCompressor);
}
playNotification(notificationType, panning = 0, duration = 0.15) {
if (notificationType === AudioNotificationType.Annotation) {
this._playAnnotation(panning, duration);
}
}
_playDataPoint(frequency, panning, duration, destinationNode) {
const t = this._audioContext.currentTime;
const mainFreq = this._audioContext.createOscillator();
mainFreq.frequency.value = frequency;
mainFreq.start();
const { carrier: c1, amp: a1, modulator: m1, filter: f1, adsr: adsr1 } = createOperator(this._audioContext, frequency * 0.5, frequency * 3, frequency * 2);
c1.type = "triangle";
adsr1.gain.setValueCurveAtTime([0.2, 0.1], t, duration * 0.75);
f1.frequency.value = frequency;
f1.type = "lowpass";
const adsr = this._audioContext.createGain();
adsr.gain.setValueCurveAtTime([0.5, 1, 0.5, 0.5, 0.5, 0.1, 0.0001], t, duration);
const panner = this._audioContext.createStereoPanner();
panner.pan.value = panning;
mainFreq.connect(adsr);
adsr1.connect(adsr);
adsr.connect(panner);
panner.connect(destinationNode);
setTimeout(() => {
panner.disconnect();
adsr.disconnect();
adsr1.disconnect();
mainFreq.stop();
mainFreq.disconnect();
m1.stop();
m1.disconnect();
c1.stop();
c1.disconnect();
a1.disconnect();
f1.disconnect();
}, duration * 1000 * 2);
}
_playAnnotation(panning, duration) {
const panner = this._audioContext.createStereoPanner();
panner.pan.value = panning;
const gain = this._audioContext.createGain();
gain.gain.value = 0.5;
gain.connect(panner);
panner.connect(this._masterCompressor);
this._playDataPoint(C3, 0, duration / 4, gain);
this._playDataPoint(C4, 0, duration / 4, gain);
setTimeout(() => {
this._playDataPoint(G3, 0, duration / 4, gain);
this._playDataPoint(G4, 0, duration / 4, gain);
this._playDataPoint(G5, 0, duration / 4, gain);
}, duration * 1000 * 0.25);
setTimeout(() => {
this._playDataPoint(C3, 0, duration / 4, gain);
this._playDataPoint(C4, 0, duration / 4, gain);
}, duration * 1000 * 0.5);
setTimeout(() => {
this._playDataPoint(G3, 0, duration / 4, gain);
this._playDataPoint(G4, 0, duration / 4, gain);
this._playDataPoint(G5, 0, duration / 4, gain);
}, duration * 1000 * 0.75);
setTimeout(() => {
gain.disconnect();
}, duration * 1000 * 2);
}
}
function createOperator(context, carrierFrequency, modulatorFrequency, modulatorDepth) {
const c = context.createOscillator();
const a = context.createGain();
const m = context.createOscillator();
const f = context.createBiquadFilter();
const adsr = context.createGain();
c.frequency.value = carrierFrequency;
m.frequency.value = modulatorFrequency;
a.gain.value = modulatorDepth;
m.connect(a);
a.connect(c.frequency);
c.connect(f);
f.connect(adsr);
c.start();
m.start();
return { carrier: c, amp: a, modulator: m, filter: f, adsr: adsr };
}
const HERTZ = [
16.3516, 17.32391, 18.35405, 19.44544, 20.60172, 21.82676, 23.12465, 24.49971, 25.95654, 27.5, 29.13524, 30.86771,
32.7032, 34.64783, 36.7081, 38.89087, 41.20344, 43.65353, 46.2493, 48.99943, 51.91309, 55, 58.27047, 61.73541,
65.40639, 69.29566, 73.41619, 77.78175, 82.40689, 87.30706, 92.49861, 97.99886, 103.8262, 110, 116.5409, 123.4708,
130.8128, 138.5913, 146.8324, 155.5635, 164.8138, 174.6141, 184.9972, 195.9977, 207.6523, 220, 233.0819, 246.9417,
261.6256, 277.1826, 293.6648, 311.127, 329.6276, 349.2282, 369.9944, 391.9954, 415.3047, 440, 466.1638, 493.8833,
523.2511, 554.3653, 587.3295, 622.254, 659.2551, 698.4565, 739.9888, 783.9909, 830.6094, 880, 932.3275, 987.7666,
1046.502, 1108.731, 1174.659, 1244.508, 1318.51, 1396.913, 1479.978, 1567.982, 1661.219, 1760, 1864.655, 1975.533,
2093.005, 2217.461, 2349.318, 2489.016, 2637.02, 2793.826, 2959.955, 3135.963, 3322.438, 3520, 3729.31, 3951.066,
4186.009, 4434.922, 4698.636, 4978.032, 5274.041, 5587.652, 5919.911, 6271.927, 6644.875, 7040, 7458.62, 7902.133,
];
const SPEEDS = [1000, 250, 100, 50, 25];
const NOTE_LENGTH = 0.25;
const keyboardEventToString = (e) => {
return `${e.altKey ? "Alt+" : ""}${e.ctrlKey ? "Ctrl+" : ""}${e.shiftKey ? "Shift+" : ""}${e.key}`;
};
class KeyboardEventManager {
constructor(target, modifyHelpDialogText, modifyHelpDialogKeyboardListing) {
this.modifyHelpDialogText = modifyHelpDialogText;
this.modifyHelpDialogKeyboardListing = modifyHelpDialogKeyboardListing;
this._handler = (event) => {
this._handleKeyEvents(event);
};
this._keyMap = {};
this._target = target;
this._target.addEventListener("keydown", this._handler);
if (!this._target.hasAttribute("tabIndex")) {
this._target.setAttribute("tabIndex", "0");
}
this._dialog = null;
}
cleanup() {
this._target.removeEventListener("keydown", this._handler);
if (this._dialog !== null) {
document.body.removeChild(this._dialog);
}
}
_handleKeyEvents(event) {
const keyPress = keyboardEventToString(event);
if (keyPress in this._keyMap) {
this._keyMap[keyPress].callback();
event.preventDefault();
}
else if (keyPress.toUpperCase() in this._keyMap) {
this._keyMap[keyPress.toUpperCase()].callback();
event.preventDefault();
}
}
registerKeyEvent({ key, callback, title = "", description = "", force = false, keyDescription, caseSensitive = true, order = 100 }) {
const checkKey = caseSensitive ? key : key.toUpperCase();
if (!force && checkKey in this._keyMap) {
return;
}
this._keyMap[checkKey] = {
title,
description,
callback,
keyDescription,
order
};
}
registerKeyEvents(keyRegistrationList) {
keyRegistrationList.forEach((kr, order) => {
this.registerKeyEvent({ order, ...kr });
});
}
generateHelpDialog(lang, translationCallback, keyboardListing) {
const dialog = document.createElement("dialog");
dialog.classList.add("chart2music-dialog");
dialog.classList.add("chart2music-help-dialog");
dialog.setAttribute("lang", lang);
const closeButton = document.createElement("button");
closeButton.textContent = "X";
closeButton.ariaLabel = translationCallback("close");
closeButton.style.position = "absolute";
closeButton.style.top = "10px";
closeButton.style.right = "10px";
closeButton.addEventListener("click", () => {
dialog.close();
});
dialog.appendChild(closeButton);
const heading = translationCallback("kbmg-title");
const h1 = document.createElement("h1");
h1.textContent = heading;
dialog.setAttribute("aria-live", heading);
dialog.appendChild(h1);
const frontMatter = document.createElement("p");
frontMatter.textContent = this.modifyHelpDialogText(lang, translationCallback("help-dialog-front-matter"));
dialog.appendChild(frontMatter);
const table = document.createElement("table");
const thead = document.createElement("thead");
const tr1 = document.createElement("tr");
(keyboardListing.at(0) ?? []).forEach((txt) => {
const th = document.createElement("th");
th.setAttribute("scope", "col");
th.textContent = txt;
tr1.appendChild(th);
});
thead.appendChild(tr1);
table.appendChild(thead);
const tbody = document.createElement("tbody");
keyboardListing.slice(1).forEach((row) => {
const tr = document.createElement("tr");
row.forEach((cell) => {
const td = document.createElement("td");
td.textContent = cell;
tr.appendChild(td);
});
tbody.appendChild(tr);
});
table.appendChild(tbody);
dialog.appendChild(table);
const footer = document.createElement("p");
footer.appendChild(document.createTextNode(translationCallback("help_dialog_footer")));
const a = document.createElement("a");
a.setAttribute("href", "https://www.chart2music.com/");
a.textContent = "www.chart2music.com";
footer.appendChild(a);
footer.appendChild(document.createTextNode("."));
dialog.appendChild(footer);
return dialog;
}
launchHelpDialog(lang, translationCallback) {
const headings = [
"Keyboard Shortcut",
"Description",
"Common Alternate Keyboard Shortcut"
];
const listing = Object.entries(this._keyMap)
.sort((left, right) => {
if (left[1].order < right[1].order) {
return -1;
}
if (left[1].order > right[1].order) {
return 1;
}
return 0;
})
.map(([key, { title, keyDescription, description }]) => [
title,
keyDescription ?? key,
description
]);
if (this._dialog === null) {
this._dialog = this.generateHelpDialog(lang, translationCallback, this.modifyHelpDialogKeyboardListing(lang, headings, listing));
document.body.appendChild(this._dialog);
}
this._dialog.showModal();
this._dialog.focus();
}
}
class ScreenReaderBridge {
static addAriaAttributes(element, ariaLive = "assertive") {
element.setAttribute("aria-live", ariaLive);
element.setAttribute("role", "status");
element.setAttribute("aria-atomic", "true");
element.setAttribute("aria-relevant", "additions text");
}
constructor(captionElement) {
this._maxNumPaddingCharacters = 3;
this._numPaddingCharacters = 0;
this._element = captionElement;
this._lastCreatedElement = null;
}
get lastCreatedElement() {
return this._lastCreatedElement;
}
clear() {
this._element.textContent = "";
}
render(text) {
const paddedText = this._creatPaddedText(text);
const divElement = document.createElement("div");
divElement.textContent = paddedText;
divElement.setAttribute(ScreenReaderBridge.ORIGINAL_TEXT_ATTRIBUTE, text);
divElement.setAttribute("data-created", Date.now().toString());
if (this.lastCreatedElement) {
this._removeOldElements();
this.lastCreatedElement.style.display = "none";
}
this._element.appendChild(divElement);
this._lastCreatedElement = divElement;
}
_creatPaddedText(text) {
let padding = "";
for (let i = 0; i < this._numPaddingCharacters; i++) {
padding += ScreenReaderBridge.PADDING_CHARACTER;
}
this._numPaddingCharacters =
(this._numPaddingCharacters + 1) % this._maxNumPaddingCharacters;
return text + padding;
}
_removeOldElements() {
const curTime = Date.now();
Array.from(this._element.children).forEach((kid) => {
const time = Number(kid.getAttribute("data-time"));
if (curTime - time > ScreenReaderBridge.REMOVAL_DELAY) {
this._element.removeChild(kid);
}
});
}
}
ScreenReaderBridge.PADDING_CHARACTER = "\u00A0";
ScreenReaderBridge.REMOVAL_DELAY = 25;
ScreenReaderBridge.ORIGINAL_TEXT_ATTRIBUTE = "data-original-text";
function isDataPoint(obj) {
return typeof obj === "object" && "x" in obj;
}
function isSimpleDataPoint(obj) {
return isDataPoint(obj) && "y" in obj;
}
function isAlternateAxisDataPoint(obj) {
return isDataPoint(obj) && "y2" in obj;
}
function isHighLowDataPoint(obj) {
return isDataPoint(obj) && "high" in obj && "low" in obj;
}
function isOHLCDataPoint(obj) {
return isHighLowDataPoint(obj) && "open" in obj && "close" in obj;
}
function isBoxDataPoint(obj) {
return (isHighLowDataPoint(obj) && "q1" in obj && "q3" in obj && "median" in obj);
}
const interpolateBin = ({ point, min, max, bins, scale }) => {
return scale === "linear"
? interpolateBinLinear({ point, min, max, bins })
: interpolateBinLog({
pointRaw: point,
minRaw: min,
maxRaw: max,
bins
});
};
const interpolateBinLinear = ({ point, min, max, bins }) => {
const pct = (point - min) / (max - min);
return Math.floor(bins * pct);
};
const interpolateBinLog = ({ pointRaw, minRaw, maxRaw, bins }) => {
const point = Math.log10(pointRaw);
const min = Math.log10(minRaw);
const max = Math.log10(maxRaw);
const pct = (point - min) / (max - min);
return Math.floor(bins * pct);
};
const calcPan = (pct) => (isNaN(pct) ? 0 : (pct * 2 - 1) * 0.98);
const isNotNull = (tmp) => tmp !== null;
const calculateAxisMinimum = ({ data, prop, filterGroupIndex }) => {
let dataToProcess = data.flat().filter(isNotNull);
if (filterGroupIndex >= 0 && filterGroupIndex < data.length) {
dataToProcess = data.at(filterGroupIndex);
}
const values = dataToProcess
.map((point) => {
if (isSimpleDataPoint(point)) {
if (prop === "x" || prop === "y") {
return point[prop];
}
}
else if (isAlternateAxisDataPoint(point)) {
if (prop === "x" || prop === "y2") {
return point[prop];
}
}
else if (isOHLCDataPoint(point)) {
if (prop === "x") {
return point.x;
}
if (prop === "y") {
return Math.min(point.high, point.low, point.open, point.close);
}
}
else if (isHighLowDataPoint(point)) {
if (prop === "x") {
return point.x;
}
if (prop === "y") {
return Math.min(point.high, point.low);
}
}
return NaN;
})
.filter((num) => !isNaN(num));
if (values.length === 0) {
return NaN;
}
return Math.min(...values);
};
const calculateAxisMaximum = ({ data, prop, filterGroupIndex }) => {
let dataToProcess = data.flat().filter(isNotNull);
if (filterGroupIndex >= 0 && filterGroupIndex < data.length) {
dataToProcess = data.at(filterGroupIndex);
}
const values = dataToProcess
.map((point) => {
if (isSimpleDataPoint(point)) {
if (prop === "x" || prop === "y") {
return point[prop];
}
}
else if (isAlternateAxisDataPoint(point)) {
if (prop === "x" || prop === "y2") {
return point[prop];
}
}
else if (isOHLCDataPoint(point)) {
if (prop === "x") {
return point.x;
}
if (prop === "y") {
return Math.max(point.high, point.low, point.open, point.close);
}
}
else if (isHighLowDataPoint(point)) {
if (prop === "x") {
return point.x;
}
if (prop === "y") {
return Math.max(point.high, point.low);
}
}
return NaN;
})
.filter((num) => !isNaN(num));
if (values.length === 0) {
return NaN;
}
return Math.max(...values);
};
const defaultFormat = (value) => `${value}`;
const generatePointDescription = ({ point, xFormat = defaultFormat, yFormat = defaultFormat, stat, outlierIndex = null, announcePointLabelFirst = false, translationCallback }) => {
if (isOHLCDataPoint(point)) {
if (typeof stat !== "undefined") {
return translationCallback("point-xy", {
x: xFormat(point.x),
y: yFormat(point[stat])
});
}
return translationCallback("point-xohlc", {
x: xFormat(point.x),
open: yFormat(point.open),
high: yFormat(point.high),
low: yFormat(point.low),
close: yFormat(point.close)
});
}
if (isBoxDataPoint(point) && outlierIndex !== null) {
return translationCallback("point-outlier", {
x: xFormat(point.x),
y: point.outlier.at(outlierIndex),
index: outlierIndex + 1,
count: point.outlier.length
});
}
if (isBoxDataPoint(point) || isHighLowDataPoint(point)) {
if (typeof stat !== "undefined") {
return translationCallback("point-xy", {
x: xFormat(point.x),
y: yFormat(point[stat])
});
}
const { x, high, low } = point;
const formattedPoint = {
x: xFormat(x),
high: yFormat(high),
low: yFormat(low)
};
if ("outlier" in point && point.outlier?.length > 0) {
return translationCallback("point-xhl-outlier", {
...formattedPoint,
count: point.outlier.length
});
}
return translationCallback("point-xhl", formattedPoint);
}
if (isSimpleDataPoint(point)) {
const details = [xFormat(point.x), yFormat(point.y)];
if (point.label) {
if (announcePointLabelFirst) {
details.unshift(point.label);
}
else {
details.push(point.label);
}
}
return details.join(", ");
}
if (isAlternateAxisDataPoint(point)) {
return translationCallback("point-xy", {
x: xFormat(point.x),
y: yFormat(point.y2)
});
}
return "";
};
const usesAxis = ({ data, axisName }) => {
const firstUseOfAxis = data.filter(isNotNull).find((row) => {
return row.find((point) => axisName in point);
});
return typeof firstUseOfAxis !== "undefined";
};
const calculateMetadataByGroup = (data) => {
return data.map((row, index) => {
if (row === null) {
return {
index,
minimumPointIndex: null,
maximumPointIndex: null,
minimumValue: NaN,
maximumValue: NaN,
tenths: NaN,
availableStats: [],
statIndex: -1,
inputType: null,
size: 0
};
}
let yValues = [];
let availableStats = [];
if (isSimpleDataPoint(row.at(0))) {
yValues = row.map(({ y }) => y);
}
else if (isAlternateAxisDataPoint(row.at(0))) {
yValues = row.map(({ y2 }) => y2);
}
else if (isOHLCDataPoint(row.at(0))) {
availableStats = ["open", "high", "low", "close"];
}
else if (isBoxDataPoint(row.at(0))) {
availableStats = ["high", "q3", "median", "q1", "low", "outlier"];
}
else if (isHighLowDataPoint(row.at(0))) {
availableStats = ["high", "low"];
}
const filteredYValues = yValues.filter((num) => !isNaN(num));
const [min, max] = filteredYValues.length > 0
? [Math.min(...filteredYValues), Math.max(...filteredYValues)]
: [-1, -1];
const tenths = Math.round(row.length / 10);
return {
index,
minimumPointIndex: yValues.indexOf(min),
maximumPointIndex: yValues.indexOf(max),
minimumValue: min,
maximumValue: max,
tenths,
availableStats,
statIndex: -1,
inputType: detectDataPointType(row.at(0)),
size: row.length
};
});
};
const initializeAxis = ({ data, axisName, userAxis, filterGroupIndex }) => {
const format = userAxis?.format ??
("valueLabels" in userAxis
? (index) => userAxis.valueLabels[index]
: defaultFormat);
return {
minimum: userAxis?.minimum ??
calculateAxisMinimum({ data, prop: axisName, filterGroupIndex }),
maximum: userAxis?.maximum ??
calculateAxisMaximum({ data, prop: axisName, filterGroupIndex }),
label: userAxis?.label ?? "",
type: userAxis?.type ?? "linear",
format,
continuous: userAxis.continuous ?? false
};
};
const detectDataPointType = (query) => {
if (typeof query === "number") {
return "number";
}
if (typeof query !== "object") {
return "unknown";
}
if (isSimpleDataPoint(query)) {
return "SimpleDataPoint";
}
if (isAlternateAxisDataPoint(query)) {
return "AlternativeAxisDataPoint";
}
if (isOHLCDataPoint(query)) {
return "OHLCDataPoint";
}
if (isBoxDataPoint(query)) {
return "BoxDataPoint";
}
if (isHighLowDataPoint(query)) {
return "HighLowDataPoint";
}
return "unknown";
};
const convertDataRow = (row) => {
if (row === null) {
return null;
}
return row.map((point, index) => {
if (typeof point === "number") {
return {
x: index,
y: point
};
}
return point;
});
};
const formatWrapper = ({ axis, translationCallback }) => {
const format = (num) => {
if (isNaN(num)) {
return translationCallback("missing");
}
if (typeof axis.minimum === "number" && num < axis.minimum) {
return translationCallback("tooLow");
}
if (typeof axis.maximum === "number" && num > axis.maximum) {
return translationCallback("tooHigh");
}
return axis.format(num);
};
return format;
};
const generateChartSummary = ({ title, groupCount, live = false, hierarchy = false, translationCallback }) => {
const text = ["summ", "chart"];
if (live) {
text.push("live");
}
if (hierarchy) {
text.push("hier");
}
if (groupCount > 1) {
text.push("group");
}
if (title.length > 0) {
text.push("title");
}
return translationCallback(text.join("-"), {
groupCount,
title
});
};
const axisDescriptions = {
x: "X",
y: "Y",
y2: "Alternate Y"
};
const generateAxisSummary = ({ axisLetter, axis, translationCallback }) => {
const code = ["axis", "desc"];
if (axis.type === "log10") {
code.push("log");
}
if (axisLetter === "x" && axis.continuous) {
code.push("con");
}
return translationCallback(code.join("-"), {
letter: axisDescriptions[axisLetter],
label: axis.label ?? "",
min: axis.format(axis.minimum),
max: axis.format(axis.maximum)
});
};
const generateInstructions = ({ hierarchy, live, hasNotes, translationCallback }) => {
const keyboardMessage = filteredJoin([
translationCallback("instructionArrows"),
hierarchy && translationCallback("instructionHierarchy"),
live && translationCallback("instructionLive"),
translationCallback("instructionHotkeys")
], " ");
const info = [keyboardMessage];
if (hasNotes) {
info.unshift("Has notes.");
}
return info.join(" ");
};
const isUnplayable = (yValue, yAxis) => {
return isNaN(yValue) || yValue < yAxis.minimum || yValue > yAxis.maximum;
};
const prepChartElement = ({ elem, title, translationCallback, addCleanupTask }) => {
if (!elem.hasAttribute("alt") && !elem.hasAttribute("aria-label")) {
const label = title
? translationCallback("description", { title })
: translationCallback("description-untitled");
elem.setAttribute("aria-label", label);
addCleanupTask(() => elem.removeAttribute("aria-label"));
}
if (!elem.hasAttribute("role")) {
elem.setAttribute("role", "application");
addCleanupTask(() => elem.removeAttribute("role"));
}
};
const checkForNumberInput = (metadataByGroup, data) => {
if (Array.isArray(data) && typeof data[0] === "number") {
metadataByGroup[0].inputType = "number";
}
else {
let index = 0;
for (const group in data) {
const row = data[group];
if (row !== null &&
Array.isArray(row) &&
detectDataPointType(row.at(0)) === "number") {
metadataByGroup[index].inputType = "number";
}
index++;
}
}
return metadataByGroup;
};
const detectIfMobile = () => {
const toMatch = [
/Android/i,
/webOS/i,
/iPhone/i,
/iPad/i,
/iPod/i,
/BlackBerry/i,
/Windows Phone/i
];
return toMatch.some((toMatchItem) => {
return navigator.userAgent.match(toMatchItem);
});
};
const filteredJoin = (arr, joiner) => arr.filter((item) => Boolean(item)).join(joiner);
const determineCC = (containerElement, cleanUpFnCallback, providedCC) => {
if (providedCC) {
return providedCC;
}
const generatedCC = document.createElement("div");
containerElement.appendChild(generatedCC);
cleanUpFnCallback(() => {
generatedCC.remove();
});
return generatedCC;
};
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
var extendStatics = function(d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
function __extends(d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
}
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
}
function __spreadArray(to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
//
// Main
//
function memoize(fn, options) {
var cache = options && options.cache ? options.cache : cacheDefault;
var serializer = options && options.serializer ? options.serializer : serializerDefault;
var strategy = options && options.strategy ? options.strategy : strategyDefault;
return strategy(fn, {
cache: cache,
serializer: serializer,
});
}
//
// Strategy
//
function isPrimitive(value) {
return (value == null || typeof value === 'number' || typeof value === 'boolean'); // || typeof value === "string" 'unsafe' primitive for our needs
}
function monadic(fn, cache, serializer, arg) {
var cacheKey = isPrimitive(arg) ? arg : serializer(arg);
var computedValue = cache.get(cacheKey);
if (typeof computedValue === 'undefined') {
computedValue = fn.call(this, arg);
cache.set(cacheKey, computedValue);
}
return computedValue;
}
function variadic(fn, cache, serializer) {
var args = Array.prototype.slice.call(arguments, 3);
var cacheKey = serializer(args);
var computedValue = cache.get(cacheKey);
if (typeof computedValue === 'undefined') {
computedValue = fn.apply(this, args);
cache.set(cacheKey, computedValue);
}
return computedValue;
}
function assemble(fn, context, strategy, cache, serialize) {
return strategy.bind(context, fn, cache, serialize);
}
function strategyDefault(fn, options) {
var strategy = fn.length === 1 ? monadic : variadic;
return assemble(fn, this, strategy, options.cache.create(), options.serializer);
}
function strategyVariadic(fn, options) {
return assemble(fn, this, variadic, options.cache.create(), options.serializer);
}
//
// Serializer
//
var serializerDefault = function () {
return JSON.stringify(arguments);
};
//
// Cache
//
var ObjectWithoutPrototypeCache = /** @class */ (function () {
function ObjectWithoutPrototypeCache() {
this.cache = Object.create(null);
}
ObjectWithoutPrototypeCache.prototype.get = function (key) {
return this.cache[key];
};
ObjectWithoutPrototypeCache.prototype.set = function (key, value) {
this.cache[key] = value;
};
return ObjectWithoutPrototypeCache;
}());
var cacheDefault = {
create: function create() {
return new ObjectWithoutPrototypeCache();
},
};
var strategies = {
variadic: strategyVariadic};
var ErrorKind;
(function (ErrorKind) {
/** Argument is unclosed (e.g. `{0`) */
ErrorKind[ErrorKind["EXPECT_ARGUMENT_CLOSING_BRACE"] = 1] = "EXPECT_ARGUMENT_CLOSING_BRACE";
/** Argument is empty (e.g. `{}`). */
ErrorKind[ErrorKind["EMPTY_ARGUMENT"] = 2] = "EMPTY_ARGUMENT";
/** Argument is malformed (e.g. `{foo!}``) */
ErrorKind[ErrorKind["MALFORMED_ARGUMENT"] = 3] = "MALFORMED_ARGUMENT";
/** Expect an argument type (e.g. `{foo,}`) */
ErrorKind[ErrorKind["EXPECT_ARGUMENT_TYPE"] = 4] = "EXPECT_ARGUMENT_TYPE";
/** Unsupported argument type (e.g. `{foo,foo}`) */
ErrorKind[ErrorKind["INVALID_ARGUMENT_TYPE"] = 5] = "INVALID_ARGUMENT_TYPE";
/** Expect an argument style (e.g. `{foo, number, }`) */
ErrorKind[ErrorKind["EXPECT_ARGUMENT_STYLE"] = 6] = "EXPECT_ARGUMENT_STYLE";
/** The number skeleton is invalid. */
ErrorKind[ErrorKind["INVALID_NUMBER_SKELETON"] = 7] = "INVALID_NUMBER_SKELETON";
/** The date time skeleton is invalid. */
ErrorKind[ErrorKind["INVALID_DATE_TIME_SKELETON"] = 8] = "INVALID_DATE_TIME_SKELETON";
/** Exepct a number skeleton following the `::` (e.g. `{foo, number, ::}`) */
ErrorKind[ErrorKind["EXPECT_NUMBER_SKELETON"] = 9] = "EXPECT_NUMBER_SKELETON";
/** Exepct a date time skeleton following the `::` (e.g. `{foo, date, ::}`) */
ErrorKind[ErrorKind["EXPECT_DATE_TIME_SKELETON"] = 10] = "EXPECT_DATE_TIME_SKELETON";
/** Unmatched apostrophes in the argument style (e.g. `{foo, number, 'test`) */
ErrorKind[ErrorKind["UNCLOSED_QUOTE_IN_ARGUMENT_STYLE"] = 11] = "UNCLOSED_QUOTE_IN_ARGUMENT_STYLE";
/** Missing select argument options (e.g. `{foo, select}`) */
ErrorKind[ErrorKind["EXPECT_SELECT_ARGUMENT_OPTIONS"] = 12] = "EXPECT_SELECT_ARGUMENT_OPTIONS";
/** Expecting an offset value in `plural` or `selectordinal` argument (e.g `{foo, plural, offset}`) */
ErrorKind[ErrorKind["EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE"] = 13] = "EXPECT_PLURAL_ARGUMENT_OFFSET_VALUE";
/** Offset value in `plural` or `selectordinal` is invalid (e.g. `{foo, plural, offset: x}`) */
ErrorKind[ErrorKind["INVALID_PLURAL_ARGUMENT_OFFSET_VALUE"] = 14] = "INVALID_PLURAL_ARGUMENT_OFFSET_VALUE";
/** Expecting a selector in `select` argument (e.g `{foo, select}`) */
ErrorKind[ErrorKind["EXPECT_SELECT_ARGUMENT_SELECTOR"] = 15] = "EXPECT_SELECT_ARGUMENT_SELECTOR";
/** Expecting a selector in `plural` or `selectordinal` argument (e.g `{foo, plural}`) */
ErrorKind[ErrorKind["EXPECT_PLURAL_ARGUMENT_SELECTOR"] = 16] = "EXPECT_PLURAL_ARGUMENT_SELECTOR";
/** Expecting a message fragment after the `select` selector (e.g. `{foo, select, apple}`) */
ErrorKind[ErrorKind["EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT"] = 17] = "EXPECT_SELECT_ARGUMENT_SELECTOR_FRAGMENT";
/**
* Expecting a message fragment after the `plural` or `selectordinal` selector
* (e.g. `{foo, plural, one}`)
*/
ErrorKind[ErrorKind["EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT"] = 18] = "EXPECT_PLURAL_ARGUMENT_SELECTOR_FRAGMENT";
/** Selector in `plural` or `selectordinal` is malformed (e.g. `{foo, plural, =x {#}}`) */
ErrorKind[ErrorKind["INVALID_PLURAL_ARGUMENT_SELECTOR"] = 19] = "INVALID_PLURAL_ARGUMENT_SELECTOR";
/**
* Duplicate selectors in `plural` or `selectordinal` argument.
* (e.g. {foo, plural, one {#} one {#}})
*/
ErrorKind[ErrorKind["DUPLICATE_PLURAL_ARGUMENT_SELECTOR"] = 20] = "DUPLICATE_PLURAL_ARGUMENT_SELECTOR";
/** Duplicate selectors in `select` argument.
* (e.g. {foo, select, apple {apple} apple {apple}})
*/
ErrorKind[ErrorKind["DUPLICATE_SELECT_ARGUMENT_SELECTOR"] = 21] = "DUPLICATE_SELECT_ARGUMENT_SELECTOR";
/** Plural or select argument option must have `other` clause. */
ErrorKind[ErrorKind["MISSING_OTHER_CLAUSE"] = 22] = "MISSING_OTHER_CLAUSE";
/** The tag is malformed. (e.g. `<bold!>foo</bold!>) */
ErrorKind[ErrorKind["INVALID_TAG"] = 23] = "INVALID_TAG";
/** The tag name is invalid. (e.g. `<123>foo</123>`) */
ErrorKind[ErrorKind["INVALID_TAG_NAME"] = 25] = "INVALID_TAG_NAME";
/** The closing tag does not match the opening tag. (e.g. `<bold>foo</italic>`) */
ErrorKind[ErrorKind["UNMATCHED_CLOSING_TAG"] = 26] = "UNMATCHED_CLOSING_TAG";
/** The opening tag has unmatched closing tag. (e.g. `<bold>foo`) */
ErrorKind[ErrorKind["UNCLOSED_TAG"] = 27] = "UNCLOSED_TAG";
})(ErrorKind || (ErrorKind = {}));
var TYPE;
(function (TYPE) {
/**
* Raw text
*/
TYPE[TYPE["literal"] = 0] = "literal";
/**
* Variable w/o any format, e.g `var` in `this is a {var}`
*/
TYPE[TYPE["argument"] = 1] = "argument";
/**
* Variable w/ number format
*/
TYPE[TYPE["number"] = 2] = "number";
/**
* Variable w/ date format
*/
TYPE[TYPE["date"] = 3] = "date";
/**
* Variable w/ time format
*/
TYPE[TYPE["time"] = 4] = "time";
/**
* Variable w/ select format
*/
TYPE[TYPE["select"] = 5] = "select";
/**
* Variable w/ plural format
*/
TYPE[TYPE["plural"] = 6] = "plural";
/**
* Only possible within plural argument.
* This is the `#` symbol that will be substituted with the count.
*/
TYPE[TYPE["pound"] = 7] = "pound";
/**
* XML-like tag
*/
TYPE[TYPE["tag"] = 8] = "tag";
})(TYPE || (TYPE = {}));
var SKELETON_TYPE;
(function (SKELETON_TYPE) {
SKELETON_TYPE[SKELETON_TYPE["number"] = 0] = "number";
SKELETON_TYPE[SKELETON_TYPE["dateTime"] = 1] = "dateTime";
})(SKELETON_TYPE || (SKELETON_TYPE = {}));
/**
* Type Guards
*/
function isLiteralElement(el) {
return el.type === TYPE.literal;
}
function isArgumentElement(el) {
return el.type === TYPE.argument;
}
function isNumberElement(el) {
return el.type === TYPE.number;
}
function isDateElement(el) {
return el.type === TYPE.date;
}
function isTimeElement(el) {
return el.type === TYPE.time;
}
function isSelectElement(el) {
return el.type === TYPE.select;
}
function isPluralElement(el) {
return el.type === TYPE.plural;
}
function isPoundElement(el) {
return el.type === TYPE.pound;
}
function isTagElement(el) {
return el.type === TYPE.tag;
}
function isNumberSkeleton(el) {
return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.number);
}
function isDateTimeSkeleton(el) {
return !!(el && typeof el === 'object' && el.type === SKELETON_TYPE.dateTime);
}
// @generated from regex-gen.ts
var SPACE_SEPARATOR_REGEX = /[ \xA0\u1680\u2000-\u200A\u202F\u205F\u3000]/;
/**
* https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
* Credit: https://github.com/caridy/intl-datetimeformat-pattern/blob/master/index.js
* with some tweaks
*/
var DATE_TIME_REGEX = /(?:[Eec]{1,6}|G{1,5}|[Qq]{1,5}|(?:[yYur]+|U{1,5})|[ML]{1,5}|d{1,2}|D{1,3}|F{1}|[abB]{1,5}|[hkHK]{1,2}|w{1,2}|W{1}|m{1,2}|s{1,2}|[zZOvVxX]{1,4})(?=([^']*'[^']*')*[^']*$)/g;
/**
* Parse Date time skeleton into Intl.DateTimeFormatOptions
* Ref: https://unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
* @public
* @param skeleton skeleton string
*/
function parseDateTimeSkeleton(skeleton) {
var result = {};
skeleton.replace(DATE_TIME_REGEX, function (match) {
var len = match.length;
switch (match[0]) {
// Era
case 'G':
result.era = len === 4 ? 'long' : len === 5 ? 'narrow' : 'short';
break;
// Year
case 'y':
result.year = len === 2 ? '2-digit' : 'numeric';
break;
case 'Y':
case 'u':
case 'U':
case 'r':
throw new RangeError('`Y/u/U/r` (year) patterns are not supported, use `y` instead');
// Quarter
case 'q':
case 'Q':
throw new RangeError('`q/Q` (quarter) patterns are not supported');
// Month
case 'M':
case 'L':
result.month = ['numeric', '2-digit', 'short', 'long', 'narrow'][len - 1];
break;
// Week
case 'w':
case 'W':
throw new RangeError('`w/W` (week) patterns are not supported');
case 'd':
result.day = ['numeric', '2-digit'][len - 1];
break;
case 'D':
case 'F':
case 'g':
throw new RangeError('`D/F/g` (day) patterns are not supported, use `d` instead');
// Weekday
case 'E':
result.weekday = len === 4 ? 'long' : len === 5 ? 'narrow' : 'short';
break;
case 'e':
if (len < 4) {
throw new RangeError('`e..eee` (weekday) patterns are not supported');
}
result.weekday = ['short', 'long', 'narrow', 'short'][len - 4];
break;
case 'c':
if (len < 4) {
throw new RangeError('`c..ccc` (weekday) patterns are not supported');
}
result.weekday = ['short', 'long', 'narrow', 'short'][len - 4];
break;
// Period
case 'a': // AM, PM
result.hour12 = true;
break;
case 'b': // am, pm, noon, midnight
case 'B': // flexible day periods
throw new RangeError('`b/B` (period) patterns are not supported, use `a` instead');
// Hour
case 'h':
result.hourCycle = 'h12';
result.hour = ['numeric', '2-digit'][len - 1];
break;
case 'H':
result.hourCycle = 'h23';
result.hour = ['numeric', '2-digit'][len - 1];
break;
case 'K':
result.hourCycle = 'h11';
result.hour = ['numeric', '2-digit'][len - 1];
break;
case 'k':
result.hourCycle = 'h24';
result.hour = ['numeric', '2-digit'][len - 1];
break;
case 'j':
case 'J':
case 'C':
throw new RangeError('`j/J/C` (hour) patterns are not supported, use `h/H/K/k` instead');
// Minute
case 'm':
result.minute = ['numeric', '2-digit'][len - 1];
break;
// Second
case 's':
result.second = ['numeric', '2-digit'][len - 1];
break;
case 'S':
case 'A':
throw new RangeError('`S/A` (second) patterns are not supported, use `s` instead');
// Zone
case 'z': // 1..3, 4: specific non-location format
result.timeZoneName = len < 4 ? 'short' : 'long';
break;
case 'Z': // 1..3, 4, 5: The ISO8601 varios formats
case 'O': // 1, 4: milliseconds in day short, long
case 'v': // 1, 4: generic non-location format
case 'V': // 1, 2, 3, 4: time zone ID or city
case 'X': // 1, 2, 3, 4: The ISO8601 varios formats
case 'x': // 1, 2, 3, 4: The ISO8601 varios formats
throw new RangeError('`Z/O/v/V/X/x` (timeZone) patterns are not supported, use `z` instead');
}
return '';
});
return result;
}
// @generated from regex-gen.ts
var WHITE_SPACE_REGEX = /[\t-\r \x85\u200E\u200F\u2028\u2029]/i;
function parseNumberSkeletonFromString(skeleton) {
if (skeleton.length === 0) {
throw new Error('Number skeleton cannot be empty');
}
// Parse the skeleton
var stringTokens = skeleton
.split(WHITE_SPACE_REGEX)
.filter(function (x) { return x.length > 0; });
var tokens = [];
for (var _i = 0, stringTokens_1 = stringTokens; _i < stringTokens_1.length; _i++) {
var stringToken = stringTokens_1[_i];
var stemAndOptions = stringToken.split('/');
if (stemAndOptions.length === 0) {
throw new Error('Invalid number skeleton');
}
var stem = stemAndOptions[0], options = stemAndOptions.slice(1);
for (var _a = 0, options_1 = options; _a < options_1.length; _a++) {
var option = options_1[_a];
if (option.length === 0) {
throw new Error('Invalid number skeleton');
}
}
tokens.push({ stem: stem, options: options });
}
return tokens;
}
function icuUnitToEcma(unit) {
return unit.replace(/^(.*?)-/, '');
}
var FRACTION_PRECISION_REGEX = /^\.(?:(0+)(\*)?|(#+)|(0+)(#+))$/g;
var SIGNIFICANT_PRECISION_REGEX = /^(@+)?(\+|#+)?[rs]?$/g;
var INTEGER_WIDTH_REGEX = /(\*)(0+)|(#