ssml-builder-js 2.12.0 → 2.14.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
@@ -26,9 +26,8 @@ import {
26
26
  SILENCE_VALUE_DESCRIPTIONS,
27
27
  SILENCE_VALUE_PRESETS,
28
28
  SSML_ATTRIBUTE_PRESETS,
29
+ SSML_ELEMENT_COPY,
29
30
  SSML_HOVER_COPY,
30
- buildPartialSsml,
31
- buildSsml,
32
31
  clearSsmlDocument,
33
32
  createSsmlInsertionEdit,
34
33
  findActiveSsmlTags,
@@ -42,10 +41,14 @@ import {
42
41
  registerSsmlCompletionProvider,
43
42
  resolveExpressAsStyles,
44
43
  updateEditableText,
45
- updateTagAttribute,
44
+ updateTagAttribute
45
+ } from "./chunk-UZSCXPC5.mjs";
46
+ import {
47
+ buildPartialSsml,
48
+ buildSsml,
46
49
  validateAzureSsml,
47
50
  validateSsml
48
- } from "./chunk-FHDFRM2F.mjs";
51
+ } from "./chunk-QFIBPCO4.mjs";
49
52
  import "./chunk-6S5ODO6A.mjs";
50
53
 
51
54
  // packages/ssml-editor-react/src/SsmlEditor.tsx
@@ -1951,19 +1954,116 @@ var VISUAL_ELEMENT_FIELDS = {
1951
1954
  function getElementFields(element) {
1952
1955
  return VISUAL_ELEMENT_FIELDS[elementLabel(element)] ?? [];
1953
1956
  }
1957
+ function localizedElementLabel(element, locale) {
1958
+ return SSML_ELEMENT_COPY[locale][elementLabel(element)]?.label ?? elementLabel(element);
1959
+ }
1960
+ function filteredVoices(voiceCatalog, locale, region, style) {
1961
+ return (voiceCatalog ?? []).filter((voice) => {
1962
+ if (locale && voice.locale.toLowerCase() !== locale.toLowerCase()) return false;
1963
+ const regions = [voice.region, ...voice.regions ?? []].filter(
1964
+ (candidate) => Boolean(candidate)
1965
+ );
1966
+ if (region && regions.length > 0 && !regions.some((candidate) => candidate.toLowerCase() === region.toLowerCase()))
1967
+ return false;
1968
+ if (style && !voice.styles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase())) return false;
1969
+ return true;
1970
+ });
1971
+ }
1972
+ function VoiceCapabilityMatrix({
1973
+ voice,
1974
+ locale
1975
+ }) {
1976
+ const status = voice.status ?? (voice.preview ? "preview" : void 0);
1977
+ return /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-voice-capabilities", "data-voice-capabilities": "" }, /* @__PURE__ */ React.createElement("div", null, locale === "ja" ? "\u30B9\u30C6\u30FC\u30BF\u30B9" : "Status", ": ", status?.toUpperCase() ?? (locale === "ja" ? "GA" : "GA"), status === "preview" || status === "deprecated" ? /* @__PURE__ */ React.createElement("span", { role: "status", "aria-label": status, className: "ssml-editor-voice-warning-badge" }, status === "preview" ? "\u26A0 Preview" : "\u26A0 Deprecated") : null), (voice.regions?.length || voice.region) && /* @__PURE__ */ React.createElement("div", null, locale === "ja" ? "\u30EA\u30FC\u30B8\u30E7\u30F3" : "Regions", ":", " ", [...voice.regions ?? [], ...voice.region ? [voice.region] : []].filter((region, index, all) => all.indexOf(region) === index).join(", ")), voice.supportedTags && voice.supportedTags.length > 0 && /* @__PURE__ */ React.createElement("div", null, locale === "ja" ? "\u5BFE\u5FDC\u30BF\u30B0" : "Supported tags", ": ", voice.supportedTags.join(", ")), voice.unsupportedTags && voice.unsupportedTags.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-voice-unsupported" }, locale === "ja" ? "\u975E\u5BFE\u5FDC\u30BF\u30B0" : "Unsupported tags", ": ", voice.unsupportedTags.join(", ")));
1978
+ }
1979
+ function DefaultVoiceSelector({
1980
+ value,
1981
+ voices,
1982
+ selectedVoice,
1983
+ readOnly,
1984
+ locale,
1985
+ onChange
1986
+ }) {
1987
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
1988
+ "select",
1989
+ {
1990
+ "aria-label": locale === "ja" ? "\u97F3\u58F0" : "Voice",
1991
+ value,
1992
+ disabled: readOnly,
1993
+ onChange: (event) => onChange(event.target.value)
1994
+ },
1995
+ /* @__PURE__ */ React.createElement("option", { value: "" }, locale === "ja" ? "\u97F3\u58F0\u3092\u9078\u629E" : "Select a voice"),
1996
+ voices.map((voice) => /* @__PURE__ */ React.createElement("option", { key: voice.name, value: voice.name }, voice.name, voice.preview || voice.status === "preview" ? " (preview)" : ""))
1997
+ ), selectedVoice ? /* @__PURE__ */ React.createElement(VoiceCapabilityMatrix, { voice: selectedVoice, locale }) : null);
1998
+ }
1954
1999
  function VisualElementInspector({
1955
2000
  document: document2,
1956
2001
  element,
1957
2002
  path,
1958
2003
  readOnly,
1959
- commit
2004
+ commit,
2005
+ locale,
2006
+ customInspector,
2007
+ renderVoiceSelector,
2008
+ voiceCatalog,
2009
+ voiceLocale,
2010
+ voiceRegion,
2011
+ voiceStyle
1960
2012
  }) {
2013
+ if (customInspector) {
2014
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, customInspector({ document: document2, element, path, readOnly, onChange: commit, locale }));
2015
+ }
1961
2016
  const fields = getElementFields(element);
2017
+ const availableVoices = filteredVoices(voiceCatalog, voiceLocale, voiceRegion, voiceStyle);
2018
+ const selectedVoice = voiceCatalog?.find(
2019
+ (voice) => voice.name === (element.type === "voice" ? element.name : void 0)
2020
+ );
2021
+ const renderSelector = renderVoiceSelector ?? DefaultVoiceSelector;
1962
2022
  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
2023
  const value = element[field.key];
1964
2024
  const inputValue = value === void 0 ? "" : String(value);
1965
2025
  const inputId = `ssml-visual-${field.key}`;
1966
- return /* @__PURE__ */ React.createElement("label", { key: field.key, htmlFor: inputId }, field.label, field.multiline ? /* @__PURE__ */ React.createElement(
2026
+ return /* @__PURE__ */ React.createElement("label", { key: field.key, htmlFor: inputId }, locale === "ja" ? {
2027
+ name: "\u540D\u524D",
2028
+ effect: "\u52B9\u679C",
2029
+ rate: "\u901F\u5EA6",
2030
+ pitch: "\u30D4\u30C3\u30C1",
2031
+ volume: "\u97F3\u91CF",
2032
+ contour: "\u30D4\u30C3\u30C1\u66F2\u7DDA",
2033
+ range: "\u7BC4\u56F2",
2034
+ style: "\u30B9\u30BF\u30A4\u30EB",
2035
+ styleDegree: "\u30B9\u30BF\u30A4\u30EB\u5F37\u5EA6",
2036
+ role: "\u5F79\u5272",
2037
+ interpretAs: "\u89E3\u91C8",
2038
+ alphabet: "\u30A2\u30EB\u30D5\u30A1\u30D9\u30C3\u30C8",
2039
+ ph: "\u767A\u97F3",
2040
+ level: "\u5F37\u8ABF\u30EC\u30D9\u30EB",
2041
+ src: "\u97F3\u58F0 URL",
2042
+ desc: "\u8AAC\u660E",
2043
+ clipBegin: "\u958B\u59CB\u4F4D\u7F6E",
2044
+ clipEnd: "\u7D42\u4E86\u4F4D\u7F6E",
2045
+ speed: "\u901F\u5EA6",
2046
+ repeatCount: "\u7E70\u308A\u8FD4\u3057\u56DE\u6570",
2047
+ repeatDuration: "\u7E70\u308A\u8FD4\u3057\u6642\u9593",
2048
+ soundLevel: "\u97F3\u91CF\u30EC\u30D9\u30EB",
2049
+ mark: "\u30D6\u30C3\u30AF\u30DE\u30FC\u30AF\u540D",
2050
+ alias: "\u5225\u540D",
2051
+ lang: "\u8A00\u8A9E",
2052
+ uri: "\u8F9E\u66F8 URI",
2053
+ typeValue: "\u7121\u97F3\u306E\u7A2E\u985E",
2054
+ value: "\u5024",
2055
+ id: "\u57CB\u3081\u8FBC\u307F ID",
2056
+ speakerProfileId: "\u8A71\u8005\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB ID",
2057
+ url: "\u97F3\u58F0\u5909\u63DB URL",
2058
+ profile: "\u30D7\u30ED\u30D5\u30A1\u30A4\u30EB"
2059
+ }[field.key] ?? field.label : field.label, field.key === "name" && elementLabel(element) === "voice" && voiceCatalog ? renderSelector({
2060
+ value: inputValue,
2061
+ voices: availableVoices,
2062
+ selectedVoice,
2063
+ readOnly,
2064
+ locale,
2065
+ onChange: (value2) => commit(updateOptionalElementProperty(document2, path, field.key, value2))
2066
+ }) : field.multiline ? /* @__PURE__ */ React.createElement(
1967
2067
  "textarea",
1968
2068
  {
1969
2069
  id: inputId,
@@ -2054,13 +2154,21 @@ function VisualSsmlEditor({
2054
2154
  document: document2,
2055
2155
  readOnly = false,
2056
2156
  onChange,
2057
- onPreviewSelection
2157
+ onPreviewSelection,
2158
+ locale = "en",
2159
+ customInspectors,
2160
+ renderVoiceSelector,
2161
+ voiceCatalog,
2162
+ voiceLocale,
2163
+ voiceRegion,
2164
+ voiceStyle
2058
2165
  }) {
2059
2166
  const [selectedPath, setSelectedPath] = useState2(null);
2060
2167
  const [selection, setSelection] = useState2({ start: 0, end: 0 });
2061
2168
  const textLeaves = useMemo(() => collectTextLeaves(document2.children ?? []), [document2]);
2062
2169
  const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
2063
2170
  const selectedElement = selectedPath ? getNodeAtPath(document2.children ?? [], selectedPath) : void 0;
2171
+ const selectedCustomInspector = selectedElement && isElement(selectedElement) ? customInspectors?.[elementLabel(selectedElement)] ?? customInspectors?.[selectedElement.type] : void 0;
2064
2172
  const diagnostics = useMemo(() => validateAzureSsml(buildSsml(document2)), [document2]);
2065
2173
  const commit = (nextDocument) => onChange?.(nextDocument);
2066
2174
  const applyWrapper = (tag, attributes) => {
@@ -2078,29 +2186,44 @@ function VisualSsmlEditor({
2078
2186
  selectedPath,
2079
2187
  onSelect: setSelectedPath
2080
2188
  }
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(
2189
+ )))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-form" }, selectedCustomInspector && selectedElement && isElement(selectedElement) ? selectedCustomInspector({
2190
+ document: document2,
2191
+ element: selectedElement,
2192
+ path: selectedPath ?? [],
2193
+ readOnly,
2194
+ onChange: commit,
2195
+ locale
2196
+ }) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:dialog" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, localizedElementLabel(selectedElement, locale)), /* @__PURE__ */ React.createElement("p", null, SSML_ELEMENT_COPY[locale][elementLabel(selectedElement)]?.description), /* @__PURE__ */ React.createElement(
2082
2197
  "button",
2083
2198
  {
2084
2199
  type: "button",
2085
2200
  disabled: readOnly,
2086
2201
  onClick: () => commit(addDialogTurn(document2, selectedPath ?? []))
2087
2202
  },
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(
2203
+ locale === "ja" ? "\u30BF\u30FC\u30F3\u3092\u8FFD\u52A0" : "Add turn"
2204
+ )) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:turn" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, localizedElementLabel(selectedElement, locale)), /* @__PURE__ */ React.createElement("label", { htmlFor: "ssml-visual-turn-voice" }, locale === "ja" ? "\u97F3\u58F0" : "Voice", voiceCatalog ? (renderVoiceSelector ?? DefaultVoiceSelector)({
2205
+ value: selectedElement.voice ?? "",
2206
+ voices: filteredVoices(voiceCatalog, voiceLocale, voiceRegion, voiceStyle),
2207
+ selectedVoice: voiceCatalog?.find((voice) => voice.name === selectedElement.voice),
2208
+ readOnly,
2209
+ locale,
2210
+ onChange: (value) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "voice", value))
2211
+ }) : /* @__PURE__ */ React.createElement(
2090
2212
  "input",
2091
2213
  {
2214
+ id: "ssml-visual-turn-voice",
2092
2215
  value: selectedElement.voice ?? "",
2093
2216
  readOnly,
2094
2217
  onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "voice", event.target.value))
2095
2218
  }
2096
- )), /* @__PURE__ */ React.createElement("label", null, "Speaker", /* @__PURE__ */ React.createElement(
2219
+ )), /* @__PURE__ */ React.createElement("label", null, locale === "ja" ? "\u8A71\u8005" : "Speaker", /* @__PURE__ */ React.createElement(
2097
2220
  "input",
2098
2221
  {
2099
2222
  value: selectedElement.speaker ?? "",
2100
2223
  readOnly,
2101
2224
  onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "speaker", event.target.value))
2102
2225
  }
2103
- )), /* @__PURE__ */ React.createElement("label", null, "Turn text", /* @__PURE__ */ React.createElement(
2226
+ )), /* @__PURE__ */ React.createElement("label", null, locale === "ja" ? "\u767A\u8A71\u30C6\u30AD\u30B9\u30C8" : "Turn text", /* @__PURE__ */ React.createElement(
2104
2227
  "textarea",
2105
2228
  {
2106
2229
  value: selectedElement.children?.filter((child) => typeof child === "string").join("") ?? "",
@@ -2113,28 +2236,28 @@ function VisualSsmlEditor({
2113
2236
  )
2114
2237
  )
2115
2238
  }
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(
2239
+ ))) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:backgroundaudio" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, localizedElementLabel(selectedElement, locale)), /* @__PURE__ */ React.createElement("label", null, locale === "ja" ? "\u97F3\u58F0 URL" : "Source URL", /* @__PURE__ */ React.createElement(
2117
2240
  "input",
2118
2241
  {
2119
2242
  value: selectedElement.src ?? "",
2120
2243
  readOnly,
2121
2244
  onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "src", event.target.value))
2122
2245
  }
2123
- )), /* @__PURE__ */ React.createElement("label", null, "Volume", /* @__PURE__ */ React.createElement(
2246
+ )), /* @__PURE__ */ React.createElement("label", null, locale === "ja" ? "\u97F3\u91CF" : "Volume", /* @__PURE__ */ React.createElement(
2124
2247
  "input",
2125
2248
  {
2126
2249
  value: selectedElement.volume === void 0 ? "" : String(selectedElement.volume),
2127
2250
  readOnly,
2128
2251
  onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "volume", event.target.value))
2129
2252
  }
2130
- )), /* @__PURE__ */ React.createElement("label", null, "Fade in", /* @__PURE__ */ React.createElement(
2253
+ )), /* @__PURE__ */ React.createElement("label", null, locale === "ja" ? "\u30D5\u30A7\u30FC\u30C9\u30A4\u30F3" : "Fade in", /* @__PURE__ */ React.createElement(
2131
2254
  "input",
2132
2255
  {
2133
2256
  value: selectedElement.fadeIn === void 0 ? "" : String(selectedElement.fadeIn),
2134
2257
  readOnly,
2135
2258
  onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeIn", event.target.value))
2136
2259
  }
2137
- )), /* @__PURE__ */ React.createElement("label", null, "Fade out", /* @__PURE__ */ React.createElement(
2260
+ )), /* @__PURE__ */ React.createElement("label", null, locale === "ja" ? "\u30D5\u30A7\u30FC\u30C9\u30A2\u30A6\u30C8" : "Fade out", /* @__PURE__ */ React.createElement(
2138
2261
  "input",
2139
2262
  {
2140
2263
  value: selectedElement.fadeOut === void 0 ? "" : String(selectedElement.fadeOut),
@@ -2148,7 +2271,14 @@ function VisualSsmlEditor({
2148
2271
  element: selectedElement,
2149
2272
  path: selectedPath ?? [],
2150
2273
  readOnly,
2151
- commit
2274
+ commit,
2275
+ locale,
2276
+ customInspector: customInspectors?.[elementLabel(selectedElement)] ?? customInspectors?.[selectedElement.type],
2277
+ renderVoiceSelector,
2278
+ voiceCatalog,
2279
+ voiceLocale,
2280
+ voiceRegion,
2281
+ voiceStyle
2152
2282
  }
2153
2283
  ) : selectedLeaf ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Text", /* @__PURE__ */ React.createElement(
2154
2284
  "textarea",
@@ -2766,6 +2896,12 @@ var SsmlEditor = forwardRef(function SsmlEditor2({
2766
2896
  insertionGroups,
2767
2897
  customInsertions,
2768
2898
  additionalInsertions,
2899
+ customInspectors,
2900
+ renderVoiceSelector,
2901
+ voiceCatalog,
2902
+ voiceLocale,
2903
+ voiceRegion,
2904
+ voiceStyle,
2769
2905
  emotionStyles,
2770
2906
  className,
2771
2907
  style,
@@ -3157,7 +3293,14 @@ var SsmlEditor = forwardRef(function SsmlEditor2({
3157
3293
  document: draftDocument,
3158
3294
  readOnly: isReadOnly,
3159
3295
  onChange: commit,
3160
- onPreviewSelection
3296
+ onPreviewSelection,
3297
+ locale: localeProp ?? languageProp,
3298
+ customInspectors,
3299
+ renderVoiceSelector,
3300
+ voiceCatalog,
3301
+ voiceLocale,
3302
+ voiceRegion,
3303
+ voiceStyle
3161
3304
  }
3162
3305
  )) : /* @__PURE__ */ React.createElement("div", { style: { ...editorStyles.editor, minHeight: editorMinHeight } }, /* @__PURE__ */ React.createElement(
3163
3306
  Editor,
@@ -3217,6 +3360,7 @@ var SsmlEditor = forwardRef(function SsmlEditor2({
3217
3360
  export {
3218
3361
  EDITOR_COPY,
3219
3362
  INLINE_BADGE_COPY,
3363
+ SSML_ELEMENT_COPY,
3220
3364
  SSML_HOVER_COPY,
3221
3365
  SSML_INSERTIONS,
3222
3366
  SsmlEditor,