ssml-builder-js 2.9.0 → 2.11.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.js CHANGED
@@ -57,6 +57,7 @@ var ssmlPresets_exports = {};
57
57
  __export(ssmlPresets_exports, {
58
58
  AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
59
59
  AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
60
+ BACKGROUND_AUDIO_DURATION_PRESETS: () => BACKGROUND_AUDIO_DURATION_PRESETS,
60
61
  BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
61
62
  BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
62
63
  BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
@@ -138,6 +139,9 @@ var AZURE_VOICE_STYLE_MAP = {
138
139
  ],
139
140
  "es-ES-ElviraNeural": [],
140
141
  "fil-PH-AngeloNeural": [],
142
+ "fil-PH-Angelo:DragonHDLatestNeural": [],
143
+ "fil-PH-BlessicaNeural": [],
144
+ "fil-PH-Blessica:DragonHDLatestNeural": [],
141
145
  "fr-FR-DeniseNeural": ["cheerful", "sad"],
142
146
  "fr-FR-HenriNeural": ["cheerful", "sad"],
143
147
  "id-ID-GadisNeural": [],
@@ -415,6 +419,7 @@ var SAY_AS_PRESETS = [
415
419
  var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
416
420
  var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
417
421
  var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
422
+ var BACKGROUND_AUDIO_DURATION_PRESETS = ["0", "500", "1000", "3000", "10000"];
418
423
  var SILENCE_TYPE_PRESETS = [
419
424
  "Leading",
420
425
  "Tailing",
@@ -476,8 +481,8 @@ var SSML_ATTRIBUTE_PRESETS = {
476
481
  },
477
482
  "mstts:backgroundaudio": {
478
483
  volume: PROSODY_VOLUME_PRESETS,
479
- fadein: AUDIO_DURATION_PRESETS,
480
- fadeout: AUDIO_DURATION_PRESETS
484
+ fadein: BACKGROUND_AUDIO_DURATION_PRESETS,
485
+ fadeout: BACKGROUND_AUDIO_DURATION_PRESETS
481
486
  },
482
487
  silence: {
483
488
  type: SILENCE_TYPE_PRESETS,
@@ -1724,6 +1729,7 @@ var SSML_ATTRS = {
1724
1729
  MSTTS_XMLNS: "xmlns:mstts",
1725
1730
  NAME: "name",
1726
1731
  VOICE: "voice",
1732
+ SPEAKER: "speaker",
1727
1733
  EFFECT: "effect",
1728
1734
  RATE: "rate",
1729
1735
  PITCH: "pitch",
@@ -1754,6 +1760,11 @@ var SSML_ATTRS = {
1754
1760
  ALIAS: "alias",
1755
1761
  MARK: "mark",
1756
1762
  URI: "uri",
1763
+ ID: "id",
1764
+ MODEL: "model",
1765
+ PROFILE: "profile",
1766
+ URL: "url",
1767
+ SPEAKER_PROFILE_ID: "speakerProfileId",
1757
1768
  TYPE: "type",
1758
1769
  VALUE: "value",
1759
1770
  FADE_IN: "fadein",
@@ -1849,6 +1860,7 @@ function getAttributes(element) {
1849
1860
  break;
1850
1861
  case SSML_TAGS.MSTTS_TURN:
1851
1862
  addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
1863
+ addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
1852
1864
  break;
1853
1865
  case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
1854
1866
  addAttribute(attributes, SSML_ATTRS.SRC, element.src);
@@ -1857,9 +1869,18 @@ function getAttributes(element) {
1857
1869
  addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
1858
1870
  break;
1859
1871
  case SSML_TAGS.MSTTS_DIALOG:
1872
+ break;
1860
1873
  case SSML_TAGS.MSTTS_TTS_EMBEDDING:
1874
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
1875
+ break;
1861
1876
  case SSML_TAGS.MSTTS_EMBEDDING:
1877
+ addAttribute(attributes, SSML_ATTRS.ID, element.id);
1878
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
1879
+ break;
1862
1880
  case SSML_TAGS.MSTTS_VOICE_CONVERSION:
1881
+ addAttribute(attributes, SSML_ATTRS.URL, element.url);
1882
+ addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
1883
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
1863
1884
  break;
1864
1885
  case SSML_TAGS.PARAGRAPH:
1865
1886
  case SSML_TAGS.SENTENCE:
@@ -2432,7 +2453,9 @@ function convertElement(node) {
2432
2453
  case SSML_TAGS.MSTTS_TURN: {
2433
2454
  const element = { type: SSML_TAGS.MSTTS_TURN };
2434
2455
  const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
2456
+ const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
2435
2457
  if (voice !== void 0) element.voice = voice;
2458
+ if (speaker !== void 0) element.speaker = speaker;
2436
2459
  return finishElement(element, node, attributes);
2437
2460
  }
2438
2461
  case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
@@ -2449,14 +2472,26 @@ function convertElement(node) {
2449
2472
  }
2450
2473
  case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
2451
2474
  const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
2475
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
2476
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
2452
2477
  return finishElement(element, node, attributes);
2453
2478
  }
2454
2479
  case SSML_TAGS.MSTTS_EMBEDDING: {
2455
2480
  const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
2481
+ const id = readAttribute(attributes, SSML_ATTRS.ID);
2482
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
2483
+ if (id !== void 0) element.id = id;
2484
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
2456
2485
  return finishElement(element, node, attributes);
2457
2486
  }
2458
2487
  case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
2459
2488
  const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
2489
+ const url = readAttribute(attributes, SSML_ATTRS.URL);
2490
+ const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
2491
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
2492
+ if (url !== void 0) element.url = url;
2493
+ if (profile !== void 0) element.profile = profile;
2494
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
2460
2495
  return finishElement(element, node, attributes);
2461
2496
  }
2462
2497
  default: {
@@ -2638,6 +2673,9 @@ var AZURE_VOICE_DEFINITIONS = [
2638
2673
  },
2639
2674
  { name: "es-ES-ElviraNeural", locale: "es-ES" },
2640
2675
  { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
2676
+ { name: "fil-PH-Angelo:DragonHDLatestNeural", locale: "fil-PH" },
2677
+ { name: "fil-PH-BlessicaNeural", locale: "fil-PH" },
2678
+ { name: "fil-PH-Blessica:DragonHDLatestNeural", locale: "fil-PH" },
2641
2679
  { name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
2642
2680
  { name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
2643
2681
  { name: "id-ID-GadisNeural", locale: "id-ID" },
@@ -2732,6 +2770,18 @@ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
2732
2770
  "Enumerationcomma"
2733
2771
  ]);
2734
2772
  var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
2773
+ var DEFAULT_PREVIEW_TAGS = /* @__PURE__ */ new Set(["mstts:voiceconversion"]);
2774
+ function featureStatusForTag(name, options) {
2775
+ const tagName = canonicalTagName(name);
2776
+ const configured = Object.entries(options.tagStatuses ?? {}).find(
2777
+ ([candidate]) => canonicalTagName(candidate) === tagName
2778
+ )?.[1];
2779
+ if (configured) return configured;
2780
+ if ((options.previewTags ?? [...DEFAULT_PREVIEW_TAGS]).some((candidate) => canonicalTagName(candidate) === tagName))
2781
+ return "preview";
2782
+ if ((options.deprecatedTags ?? []).some((candidate) => canonicalTagName(candidate) === tagName)) return "deprecated";
2783
+ return void 0;
2784
+ }
2735
2785
  function decodeAttribute(value) {
2736
2786
  return value.replace(
2737
2787
  /&(?:amp|apos|gt|lt|quot);/gi,
@@ -2791,11 +2841,14 @@ function tokenizeElements(source) {
2791
2841
  }
2792
2842
  const selfClosing = /\/\s*>$/.test(raw);
2793
2843
  const parent = openElements[openElements.length - 1];
2844
+ const childElementIndex = parent?.childElementCount;
2845
+ if (parent) parent.childElementCount += 1;
2794
2846
  const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
2795
2847
  const tokenName = nameMatch[1];
2796
2848
  const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
2797
2849
  tokens.push({
2798
2850
  attributes,
2851
+ childElementIndex,
2799
2852
  end,
2800
2853
  name: tokenName,
2801
2854
  parentName: parent?.name,
@@ -2805,6 +2858,7 @@ function tokenizeElements(source) {
2805
2858
  });
2806
2859
  if (!selfClosing) {
2807
2860
  openElements.push({
2861
+ childElementCount: 0,
2808
2862
  name: tokenName,
2809
2863
  voiceName: tokenVoiceName
2810
2864
  });
@@ -2843,6 +2897,12 @@ function isValidAzureAudioDuration(value) {
2843
2897
  if (!clock) return false;
2844
2898
  return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
2845
2899
  }
2900
+ function isValidAzureBackgroundAudioDuration(value) {
2901
+ const match = /^(\d+)$/.exec(value.trim());
2902
+ if (!match) return false;
2903
+ const milliseconds = Number(match[1]);
2904
+ return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
2905
+ }
2846
2906
  function attr(token, name) {
2847
2907
  return token.attributes.get(name.toLowerCase());
2848
2908
  }
@@ -2972,9 +3032,16 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
2972
3032
  }
2973
3033
  if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
2974
3034
  addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
2975
- if (options.allowedAudioOrigins && !options.allowedAudioOrigins.includes(parsed.origin))
3035
+ const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
3036
+ try {
3037
+ return new URL(allowedOrigin).origin === parsed.origin;
3038
+ } catch {
3039
+ return allowedOrigin === parsed.origin;
3040
+ }
3041
+ }) ?? false;
3042
+ if (options.allowedAudioOrigins && !isAllowedOrigin)
2976
3043
  addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
2977
- else if (!options.allowExternalAudio)
3044
+ else if (!isAllowedOrigin && !options.allowExternalAudio)
2978
3045
  addDiagnostic(
2979
3046
  diagnostics,
2980
3047
  source,
@@ -2984,6 +3051,25 @@ function validateAudioSource(token, source, diagnostics, options, elementName2)
2984
3051
  }
2985
3052
  function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
2986
3053
  const name = token.name.toLowerCase();
3054
+ const tagStatus = featureStatusForTag(token.name, options);
3055
+ if (tagStatus === "preview")
3056
+ addDiagnostic(
3057
+ diagnostics,
3058
+ source,
3059
+ token.start,
3060
+ `<${token.name}> is an Azure Speech preview feature and may change or require preview access.`,
3061
+ "warning",
3062
+ "azure-preview-tag"
3063
+ );
3064
+ if (tagStatus === "deprecated")
3065
+ addDiagnostic(
3066
+ diagnostics,
3067
+ source,
3068
+ token.start,
3069
+ `<${token.name}> is deprecated by Azure Speech; migrate to a supported alternative.`,
3070
+ "info",
3071
+ "azure-deprecated-tag"
3072
+ );
2987
3073
  if (name === "voice" && !attr(token, "name")?.trim())
2988
3074
  addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
2989
3075
  if (name === "break") {
@@ -3104,28 +3190,40 @@ function validateElement(token, source, diagnostics, voiceName, options, voiceCa
3104
3190
  validateAudioSource(token, source, diagnostics, options, "audio");
3105
3191
  }
3106
3192
  if (name === "mstts:turn") {
3107
- if (!attr(token, "voice")?.trim())
3108
- addDiagnostic(diagnostics, source, token.start, '<mstts:turn> requires a non-empty "voice" attribute.');
3193
+ if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
3194
+ addDiagnostic(
3195
+ diagnostics,
3196
+ source,
3197
+ token.start,
3198
+ '<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
3199
+ );
3109
3200
  if (token.parentName?.toLowerCase() !== "mstts:dialog")
3110
3201
  addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
3111
3202
  }
3112
3203
  if (name === "mstts:backgroundaudio") {
3113
3204
  validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
3114
3205
  const volume = attr(token, "volume");
3115
- if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%))$/i.test(volume.trim()))
3206
+ if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
3116
3207
  addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
3117
3208
  for (const [attribute, value] of [
3118
3209
  ["fadein", attr(token, "fadein")],
3119
3210
  ["fadeout", attr(token, "fadeout")]
3120
3211
  ]) {
3121
- if (value && !isValidAzureAudioDuration(value))
3212
+ if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
3122
3213
  addDiagnostic(
3123
3214
  diagnostics,
3124
3215
  source,
3125
3216
  token.start,
3126
- `<mstts:backgroundaudio ${attribute}> must be a positive duration such as "500ms" or "10s".`
3217
+ `<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
3127
3218
  );
3128
3219
  }
3220
+ if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
3221
+ addDiagnostic(
3222
+ diagnostics,
3223
+ source,
3224
+ token.start,
3225
+ "<mstts:backgroundaudio> must be the first element directly under <speak>."
3226
+ );
3129
3227
  if (!token.selfClosing)
3130
3228
  addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
3131
3229
  }
@@ -3157,6 +3255,16 @@ function validateAzureSsml(ssml, options = {}) {
3157
3255
  const tokens = tokenizeElements(ssml);
3158
3256
  const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
3159
3257
  const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
3258
+ const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
3259
+ for (const [index, token] of backgroundAudioTokens.entries()) {
3260
+ if (index > 0)
3261
+ addDiagnostic(
3262
+ diagnostics,
3263
+ ssml,
3264
+ token.start,
3265
+ "An SSML document can contain at most one <mstts:backgroundaudio> element."
3266
+ );
3267
+ }
3160
3268
  if (!speak || voices.length === 0)
3161
3269
  addDiagnostic(
3162
3270
  diagnostics,
@@ -3173,6 +3281,24 @@ function validateAzureSsml(ssml, options = {}) {
3173
3281
  const name = attr(token, "name")?.trim();
3174
3282
  const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
3175
3283
  const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
3284
+ if (name && definition?.status === "preview")
3285
+ addDiagnostic(
3286
+ diagnostics,
3287
+ ssml,
3288
+ token.start,
3289
+ `Voice "${name}" is an Azure Speech preview voice and may change or require preview access.`,
3290
+ "warning",
3291
+ "azure-preview-voice"
3292
+ );
3293
+ if (name && definition?.status === "deprecated")
3294
+ addDiagnostic(
3295
+ diagnostics,
3296
+ ssml,
3297
+ token.start,
3298
+ `Voice "${name}" is deprecated by Azure Speech; migrate to a supported voice.`,
3299
+ "info",
3300
+ "azure-deprecated-voice"
3301
+ );
3176
3302
  if (name && !definition && policySeverity)
3177
3303
  addDiagnostic(
3178
3304
  diagnostics,
@@ -4860,7 +4986,7 @@ var SSML_COMPLETION_SNIPPETS = [
4860
4986
  },
4861
4987
  {
4862
4988
  label: "mstts:backgroundaudio",
4863
- insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:-3dB}" />`
4989
+ insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:70}" fadein="\${3:1000}" fadeout="\${4:1000}" />`
4864
4990
  },
4865
4991
  {
4866
4992
  label: "mstts:ttsembedding",
@@ -5561,18 +5687,18 @@ var SSML_TAG_DEFINITIONS = [
5561
5687
  },
5562
5688
  {
5563
5689
  name: "volume",
5564
- description: "The background audio volume, for example `-3dB` or `medium`.",
5565
- example: "-3dB"
5690
+ description: "The background audio volume from 0 to 100.",
5691
+ example: "70"
5566
5692
  },
5567
5693
  {
5568
5694
  name: "fadein",
5569
- description: "The fade-in duration, for example `1s`.",
5570
- example: "1s"
5695
+ description: "The fade-in duration in milliseconds from 0 to 10000.",
5696
+ example: "1000"
5571
5697
  },
5572
5698
  {
5573
5699
  name: "fadeout",
5574
- description: "The fade-out duration, for example `500ms`.",
5575
- example: "500ms"
5700
+ description: "The fade-out duration in milliseconds from 0 to 10000.",
5701
+ example: "1000"
5576
5702
  }
5577
5703
  ]
5578
5704
  },
@@ -6226,6 +6352,46 @@ function updateNodesAtPath(nodes, path, update) {
6226
6352
  function updateTextAtPath(document2, path, value) {
6227
6353
  return { ...document2, children: updateNodesAtPath(document2.children ?? [], path, () => value) };
6228
6354
  }
6355
+ function getNodeAtPath(nodes, path) {
6356
+ let current;
6357
+ let currentNodes = nodes;
6358
+ for (const index of path) {
6359
+ current = currentNodes[index];
6360
+ if (current === void 0) return void 0;
6361
+ if (!isElement(current)) {
6362
+ currentNodes = [];
6363
+ } else {
6364
+ currentNodes = current.children ?? [];
6365
+ }
6366
+ }
6367
+ return current;
6368
+ }
6369
+ function updateElementAtPath(document2, path, update) {
6370
+ return {
6371
+ ...document2,
6372
+ children: updateNodesAtPath(document2.children ?? [], path, (node) => isElement(node) ? update(node) : node)
6373
+ };
6374
+ }
6375
+ function updateElementText(element, value) {
6376
+ return { ...element, children: [value] };
6377
+ }
6378
+ function updateOptionalElementProperty(document2, path, property, value) {
6379
+ return updateElementAtPath(document2, path, (element) => {
6380
+ const next = { ...element };
6381
+ if (value.trim()) next[property] = value;
6382
+ else delete next[property];
6383
+ return next;
6384
+ });
6385
+ }
6386
+ function addDialogTurn(document2, path) {
6387
+ return updateElementAtPath(document2, path, (element) => ({
6388
+ ...element,
6389
+ children: [
6390
+ ...element.children ?? [],
6391
+ { type: "mstts:turn", voice: "en-US-JennyNeural", children: ["New dialog turn"] }
6392
+ ]
6393
+ }));
6394
+ }
6229
6395
  function wrapTextAtPath(document2, path, start, end, tag, attributes) {
6230
6396
  return {
6231
6397
  ...document2,
@@ -6277,6 +6443,7 @@ function VisualSsmlEditor({
6277
6443
  const [selection, setSelection] = (0, import_react3.useState)({ start: 0, end: 0 });
6278
6444
  const textLeaves = (0, import_react3.useMemo)(() => collectTextLeaves(document2.children ?? []), [document2]);
6279
6445
  const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
6446
+ const selectedElement = selectedPath ? getNodeAtPath(document2.children ?? [], selectedPath) : void 0;
6280
6447
  const diagnostics = (0, import_react3.useMemo)(() => validateAzureSsml(buildSsml(document2)), [document2]);
6281
6448
  const commit = (nextDocument) => onChange?.(nextDocument);
6282
6449
  const applyWrapper = (tag, attributes) => {
@@ -6294,7 +6461,70 @@ function VisualSsmlEditor({
6294
6461
  selectedPath,
6295
6462
  onSelect: setSelectedPath
6296
6463
  }
6297
- )))), /* @__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(
6464
+ )))), /* @__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(
6465
+ "button",
6466
+ {
6467
+ type: "button",
6468
+ disabled: readOnly,
6469
+ onClick: () => commit(addDialogTurn(document2, selectedPath ?? []))
6470
+ },
6471
+ "Add turn"
6472
+ )) : 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(
6473
+ "input",
6474
+ {
6475
+ value: selectedElement.voice ?? "",
6476
+ readOnly,
6477
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "voice", event.target.value))
6478
+ }
6479
+ )), /* @__PURE__ */ React.createElement("label", null, "Speaker", /* @__PURE__ */ React.createElement(
6480
+ "input",
6481
+ {
6482
+ value: selectedElement.speaker ?? "",
6483
+ readOnly,
6484
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "speaker", event.target.value))
6485
+ }
6486
+ )), /* @__PURE__ */ React.createElement("label", null, "Turn text", /* @__PURE__ */ React.createElement(
6487
+ "textarea",
6488
+ {
6489
+ value: selectedElement.children?.filter((child) => typeof child === "string").join("") ?? "",
6490
+ readOnly,
6491
+ onChange: (event) => commit(
6492
+ updateElementAtPath(
6493
+ document2,
6494
+ selectedPath ?? [],
6495
+ (element) => updateElementText(element, event.target.value)
6496
+ )
6497
+ )
6498
+ }
6499
+ ))) : 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(
6500
+ "input",
6501
+ {
6502
+ value: selectedElement.src ?? "",
6503
+ readOnly,
6504
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "src", event.target.value))
6505
+ }
6506
+ )), /* @__PURE__ */ React.createElement("label", null, "Volume", /* @__PURE__ */ React.createElement(
6507
+ "input",
6508
+ {
6509
+ value: selectedElement.volume === void 0 ? "" : String(selectedElement.volume),
6510
+ readOnly,
6511
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "volume", event.target.value))
6512
+ }
6513
+ )), /* @__PURE__ */ React.createElement("label", null, "Fade in", /* @__PURE__ */ React.createElement(
6514
+ "input",
6515
+ {
6516
+ value: selectedElement.fadeIn === void 0 ? "" : String(selectedElement.fadeIn),
6517
+ readOnly,
6518
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeIn", event.target.value))
6519
+ }
6520
+ )), /* @__PURE__ */ React.createElement("label", null, "Fade out", /* @__PURE__ */ React.createElement(
6521
+ "input",
6522
+ {
6523
+ value: selectedElement.fadeOut === void 0 ? "" : String(selectedElement.fadeOut),
6524
+ readOnly,
6525
+ onChange: (event) => commit(updateOptionalElementProperty(document2, selectedPath ?? [], "fadeOut", event.target.value))
6526
+ }
6527
+ ))) : selectedLeaf ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Text", /* @__PURE__ */ React.createElement(
6298
6528
  "textarea",
6299
6529
  {
6300
6530
  value: selectedLeaf.value,