ssml-builder-js 2.10.0 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/react.mjs CHANGED
@@ -45,7 +45,7 @@ import {
45
45
  updateTagAttribute,
46
46
  validateAzureSsml,
47
47
  validateSsml
48
- } from "./chunk-BYIZQL2W.mjs";
48
+ } from "./chunk-FHDFRM2F.mjs";
49
49
  import "./chunk-6S5ODO6A.mjs";
50
50
 
51
51
  // packages/ssml-editor-react/src/SsmlEditor.tsx
@@ -1840,6 +1840,175 @@ function updateNodesAtPath(nodes, path, update) {
1840
1840
  function updateTextAtPath(document2, path, value) {
1841
1841
  return { ...document2, children: updateNodesAtPath(document2.children ?? [], path, () => value) };
1842
1842
  }
1843
+ function getNodeAtPath(nodes, path) {
1844
+ let current;
1845
+ let currentNodes = nodes;
1846
+ for (const index of path) {
1847
+ current = currentNodes[index];
1848
+ if (current === void 0) return void 0;
1849
+ if (!isElement(current)) {
1850
+ currentNodes = [];
1851
+ } else {
1852
+ currentNodes = current.children ?? [];
1853
+ }
1854
+ }
1855
+ return current;
1856
+ }
1857
+ function updateElementAtPath(document2, path, update) {
1858
+ return {
1859
+ ...document2,
1860
+ children: updateNodesAtPath(document2.children ?? [], path, (node) => isElement(node) ? update(node) : node)
1861
+ };
1862
+ }
1863
+ function updateElementText(element, value) {
1864
+ return { ...element, children: [value] };
1865
+ }
1866
+ function updateOptionalElementProperty(document2, path, property, value) {
1867
+ return updateElementAtPath(document2, path, (element) => {
1868
+ const next = { ...element };
1869
+ if (value.trim()) next[property] = value;
1870
+ else delete next[property];
1871
+ return next;
1872
+ });
1873
+ }
1874
+ var VISUAL_ELEMENT_FIELDS = {
1875
+ voice: [
1876
+ { key: "name", label: "Voice name" },
1877
+ { key: "effect", label: "Effect" }
1878
+ ],
1879
+ prosody: [
1880
+ { key: "rate", label: "Rate" },
1881
+ { key: "pitch", label: "Pitch" },
1882
+ { key: "volume", label: "Volume" },
1883
+ { key: "contour", label: "Contour" },
1884
+ { key: "range", label: "Range" }
1885
+ ],
1886
+ "express-as": [
1887
+ { key: "style", label: "Style" },
1888
+ { key: "styleDegree", label: "Style degree" },
1889
+ { key: "role", label: "Role" }
1890
+ ],
1891
+ expressAs: [
1892
+ { key: "style", label: "Style" },
1893
+ { key: "styleDegree", label: "Style degree" },
1894
+ { key: "role", label: "Role" }
1895
+ ],
1896
+ "mstts:express-as": [
1897
+ { key: "style", label: "Style" },
1898
+ { key: "styleDegree", label: "Style degree" },
1899
+ { key: "role", label: "Role" }
1900
+ ],
1901
+ "say-as": [
1902
+ { key: "interpretAs", label: "Interpret as" },
1903
+ { key: "format", label: "Format" },
1904
+ { key: "detail", label: "Detail" }
1905
+ ],
1906
+ sayAs: [
1907
+ { key: "interpretAs", label: "Interpret as" },
1908
+ { key: "format", label: "Format" },
1909
+ { key: "detail", label: "Detail" }
1910
+ ],
1911
+ phoneme: [
1912
+ { key: "alphabet", label: "Alphabet" },
1913
+ { key: "ph", label: "Pronunciation" }
1914
+ ],
1915
+ emphasis: [{ key: "level", label: "Level" }],
1916
+ audio: [
1917
+ { key: "src", label: "Source URL" },
1918
+ { key: "desc", label: "Description" },
1919
+ { key: "clipBegin", label: "Clip begin" },
1920
+ { key: "clipEnd", label: "Clip end" },
1921
+ { key: "speed", label: "Speed" },
1922
+ { key: "repeatCount", label: "Repeat count" },
1923
+ { key: "repeatDuration", label: "Repeat duration" },
1924
+ { key: "soundLevel", label: "Sound level" }
1925
+ ],
1926
+ mark: [{ key: "name", label: "Mark name" }],
1927
+ bookmark: [{ key: "mark", label: "Bookmark name" }],
1928
+ sub: [{ key: "alias", label: "Alias" }],
1929
+ lang: [{ key: "lang", label: "Language" }],
1930
+ lexicon: [{ key: "uri", label: "Lexicon URI" }],
1931
+ "mstts:silence": [
1932
+ { key: "typeValue", label: "Silence type" },
1933
+ { key: "value", label: "Value" }
1934
+ ],
1935
+ silence: [
1936
+ { key: "typeValue", label: "Silence type" },
1937
+ { key: "value", label: "Value" }
1938
+ ],
1939
+ "mstts:audioduration": [{ key: "value", label: "Duration" }],
1940
+ "mstts:ttsembedding": [{ key: "speakerProfileId", label: "Speaker profile ID" }],
1941
+ "mstts:embedding": [
1942
+ { key: "id", label: "Embedding ID" },
1943
+ { key: "speakerProfileId", label: "Speaker profile ID" }
1944
+ ],
1945
+ "mstts:voiceconversion": [
1946
+ { key: "url", label: "Source URL" },
1947
+ { key: "profile", label: "Profile" },
1948
+ { key: "speakerProfileId", label: "Speaker profile ID" }
1949
+ ]
1950
+ };
1951
+ function getElementFields(element) {
1952
+ return VISUAL_ELEMENT_FIELDS[elementLabel(element)] ?? [];
1953
+ }
1954
+ function VisualElementInspector({
1955
+ document: document2,
1956
+ element,
1957
+ path,
1958
+ readOnly,
1959
+ commit
1960
+ }) {
1961
+ const fields = getElementFields(element);
1962
+ return /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, `<${elementLabel(element)}>`), fields.length === 0 ? /* @__PURE__ */ React.createElement("p", null, "This element is preserved in the visual tree. Edit its attributes in Code mode.") : fields.map((field) => {
1963
+ const value = element[field.key];
1964
+ const inputValue = value === void 0 ? "" : String(value);
1965
+ const inputId = `ssml-visual-${field.key}`;
1966
+ return /* @__PURE__ */ React.createElement("label", { key: field.key, htmlFor: inputId }, field.label, field.multiline ? /* @__PURE__ */ React.createElement(
1967
+ "textarea",
1968
+ {
1969
+ id: inputId,
1970
+ value: inputValue,
1971
+ readOnly,
1972
+ onChange: (event) => commit(updateOptionalElementProperty(document2, path, field.key, event.target.value))
1973
+ }
1974
+ ) : /* @__PURE__ */ React.createElement(
1975
+ "input",
1976
+ {
1977
+ id: inputId,
1978
+ value: inputValue,
1979
+ readOnly,
1980
+ onChange: (event) => commit(updateOptionalElementProperty(document2, path, field.key, event.target.value))
1981
+ }
1982
+ ));
1983
+ }));
1984
+ }
1985
+ function getElementAncestors(nodes, path, ancestors = []) {
1986
+ const [index, ...rest] = path;
1987
+ const node = nodes[index];
1988
+ if (!node || !isElement(node)) return ancestors;
1989
+ if (rest.length === 0) return ancestors;
1990
+ return getElementAncestors(node.children ?? [], rest, [...ancestors, node]);
1991
+ }
1992
+ function buildVisualPreview(document2, leaf, selection) {
1993
+ const start = Math.max(0, Math.min(selection.start, leaf.value.length));
1994
+ const end = Math.max(start, Math.min(selection.end, leaf.value.length));
1995
+ const value = start === end ? leaf.value : leaf.value.slice(start, end);
1996
+ const ancestors = getElementAncestors(document2.children ?? [], leaf.path);
1997
+ const children = ancestors.reduceRight(
1998
+ (current, ancestor) => [{ ...ancestor, children: current }],
1999
+ [value]
2000
+ );
2001
+ return buildSsml({ ...document2, children });
2002
+ }
2003
+ function addDialogTurn(document2, path) {
2004
+ return updateElementAtPath(document2, path, (element) => ({
2005
+ ...element,
2006
+ children: [
2007
+ ...element.children ?? [],
2008
+ { type: "mstts:turn", voice: "en-US-JennyNeural", children: ["New dialog turn"] }
2009
+ ]
2010
+ }));
2011
+ }
1843
2012
  function wrapTextAtPath(document2, path, start, end, tag, attributes) {
1844
2013
  return {
1845
2014
  ...document2,
@@ -1891,6 +2060,7 @@ function VisualSsmlEditor({
1891
2060
  const [selection, setSelection] = useState2({ start: 0, end: 0 });
1892
2061
  const textLeaves = useMemo(() => collectTextLeaves(document2.children ?? []), [document2]);
1893
2062
  const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
2063
+ const selectedElement = selectedPath ? getNodeAtPath(document2.children ?? [], selectedPath) : void 0;
1894
2064
  const diagnostics = useMemo(() => validateAzureSsml(buildSsml(document2)), [document2]);
1895
2065
  const commit = (nextDocument) => onChange?.(nextDocument);
1896
2066
  const applyWrapper = (tag, attributes) => {
@@ -1908,7 +2078,79 @@ function VisualSsmlEditor({
1908
2078
  selectedPath,
1909
2079
  onSelect: setSelectedPath
1910
2080
  }
1911
- )))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-form" }, selectedLeaf ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Text", /* @__PURE__ */ React.createElement(
2081
+ )))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-form" }, selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:dialog" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, "Dialog"), /* @__PURE__ */ React.createElement("p", null, "Add and edit speaker turns in this dialog."), /* @__PURE__ */ React.createElement(
2082
+ "button",
2083
+ {
2084
+ type: "button",
2085
+ disabled: readOnly,
2086
+ onClick: () => commit(addDialogTurn(document2, selectedPath ?? []))
2087
+ },
2088
+ "Add turn"
2089
+ )) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:turn" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, "Dialog turn"), /* @__PURE__ */ React.createElement("label", null, "Voice", /* @__PURE__ */ React.createElement(
2090
+ "input",
2091
+ {
2092
+ value: selectedElement.voice ?? "",
2093
+ readOnly,
2094
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "voice", event.target.value))
2095
+ }
2096
+ )), /* @__PURE__ */ React.createElement("label", null, "Speaker", /* @__PURE__ */ React.createElement(
2097
+ "input",
2098
+ {
2099
+ value: selectedElement.speaker ?? "",
2100
+ readOnly,
2101
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "speaker", event.target.value))
2102
+ }
2103
+ )), /* @__PURE__ */ React.createElement("label", null, "Turn text", /* @__PURE__ */ React.createElement(
2104
+ "textarea",
2105
+ {
2106
+ value: selectedElement.children?.filter((child) => typeof child === "string").join("") ?? "",
2107
+ readOnly,
2108
+ onChange: (event) => commit(
2109
+ updateElementAtPath(
2110
+ document2,
2111
+ selectedPath ?? [],
2112
+ (element) => updateElementText(element, event.target.value)
2113
+ )
2114
+ )
2115
+ }
2116
+ ))) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:backgroundaudio" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, "Background audio"), /* @__PURE__ */ React.createElement("label", null, "Source URL", /* @__PURE__ */ React.createElement(
2117
+ "input",
2118
+ {
2119
+ value: selectedElement.src ?? "",
2120
+ readOnly,
2121
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "src", event.target.value))
2122
+ }
2123
+ )), /* @__PURE__ */ React.createElement("label", null, "Volume", /* @__PURE__ */ React.createElement(
2124
+ "input",
2125
+ {
2126
+ value: selectedElement.volume === void 0 ? "" : String(selectedElement.volume),
2127
+ readOnly,
2128
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "volume", event.target.value))
2129
+ }
2130
+ )), /* @__PURE__ */ React.createElement("label", null, "Fade in", /* @__PURE__ */ React.createElement(
2131
+ "input",
2132
+ {
2133
+ value: selectedElement.fadeIn === void 0 ? "" : String(selectedElement.fadeIn),
2134
+ readOnly,
2135
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeIn", event.target.value))
2136
+ }
2137
+ )), /* @__PURE__ */ React.createElement("label", null, "Fade out", /* @__PURE__ */ React.createElement(
2138
+ "input",
2139
+ {
2140
+ value: selectedElement.fadeOut === void 0 ? "" : String(selectedElement.fadeOut),
2141
+ readOnly,
2142
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeOut", event.target.value))
2143
+ }
2144
+ ))) : selectedElement && isElement(selectedElement) ? /* @__PURE__ */ React.createElement(
2145
+ VisualElementInspector,
2146
+ {
2147
+ document: document2,
2148
+ element: selectedElement,
2149
+ path: selectedPath ?? [],
2150
+ readOnly,
2151
+ commit
2152
+ }
2153
+ ) : selectedLeaf ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Text", /* @__PURE__ */ React.createElement(
1912
2154
  "textarea",
1913
2155
  {
1914
2156
  value: selectedLeaf.value,
@@ -1936,7 +2178,7 @@ function VisualSsmlEditor({
1936
2178
  "button",
1937
2179
  {
1938
2180
  type: "button",
1939
- onClick: () => onPreviewSelection(buildSsml({ ...document2, children: [selectedLeaf.value] }))
2181
+ onClick: () => onPreviewSelection(buildVisualPreview(document2, selectedLeaf, selection))
1940
2182
  },
1941
2183
  "Preview selection"
1942
2184
  ))) : /* @__PURE__ */ React.createElement("p", null, "Select an element or text node to edit it."))));