ssml-builder-js 2.8.1 → 2.10.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
@@ -36,6 +36,7 @@ __export(react_exports, {
36
36
  SSML_HOVER_COPY: () => SSML_HOVER_COPY,
37
37
  SSML_INSERTIONS: () => SSML_INSERTIONS,
38
38
  SsmlEditor: () => SsmlEditor,
39
+ VisualSsmlEditor: () => VisualSsmlEditor,
39
40
  createSsmlEditorInsertionDefinition: () => createSsmlEditorInsertionDefinition,
40
41
  registerSsmlCodeLens: () => registerSsmlCodeLens,
41
42
  updateTagAttribute: () => updateTagAttribute
@@ -43,8 +44,8 @@ __export(react_exports, {
43
44
  module.exports = __toCommonJS(react_exports);
44
45
 
45
46
  // packages/ssml-editor-react/src/SsmlEditor.tsx
46
- var import_react3 = require("react");
47
- var import_react4 = __toESM(require("@monaco-editor/react"));
47
+ var import_react4 = require("react");
48
+ var import_react5 = __toESM(require("@monaco-editor/react"));
48
49
 
49
50
  // packages/ssml-editor-react/src/buttonVisibility.ts
50
51
  function isSsmlEditorButtonVisible(buttonVisibility, button) {
@@ -473,6 +474,11 @@ var SSML_ATTRIBUTE_PRESETS = {
473
474
  "mstts:audioduration": {
474
475
  value: AUDIO_DURATION_PRESETS
475
476
  },
477
+ "mstts:backgroundaudio": {
478
+ volume: PROSODY_VOLUME_PRESETS,
479
+ fadein: AUDIO_DURATION_PRESETS,
480
+ fadeout: AUDIO_DURATION_PRESETS
481
+ },
476
482
  silence: {
477
483
  type: SILENCE_TYPE_PRESETS,
478
484
  value: SILENCE_VALUE_PRESETS
@@ -1289,6 +1295,14 @@ var editorStyles = {
1289
1295
  display: "grid",
1290
1296
  gap: "0.5rem"
1291
1297
  },
1298
+ visual: {
1299
+ display: "grid",
1300
+ gap: "0.75rem",
1301
+ minHeight: "8rem",
1302
+ padding: "0.75rem",
1303
+ border: "1px solid var(--ssml-editor-control-border)",
1304
+ borderRadius: "0.25rem"
1305
+ },
1292
1306
  toolbarIconOnly: {
1293
1307
  justifyContent: "center",
1294
1308
  minWidth: "2.25rem",
@@ -1694,7 +1708,13 @@ var SSML_TAGS = {
1694
1708
  SILENCE: "silence",
1695
1709
  MSTTS_VISEME: "mstts:viseme",
1696
1710
  VISEME: "viseme",
1697
- MSTTS_AUDIO_DURATION: "mstts:audioduration"
1711
+ MSTTS_AUDIO_DURATION: "mstts:audioduration",
1712
+ MSTTS_DIALOG: "mstts:dialog",
1713
+ MSTTS_TURN: "mstts:turn",
1714
+ MSTTS_BACKGROUND_AUDIO: "mstts:backgroundaudio",
1715
+ MSTTS_TTS_EMBEDDING: "mstts:ttsembedding",
1716
+ MSTTS_EMBEDDING: "mstts:embedding",
1717
+ MSTTS_VOICE_CONVERSION: "mstts:voiceconversion"
1698
1718
  };
1699
1719
  var SSML_ATTRS = {
1700
1720
  VERSION: "version",
@@ -1703,6 +1723,8 @@ var SSML_ATTRS = {
1703
1723
  LANG: "lang",
1704
1724
  MSTTS_XMLNS: "xmlns:mstts",
1705
1725
  NAME: "name",
1726
+ VOICE: "voice",
1727
+ SPEAKER: "speaker",
1706
1728
  EFFECT: "effect",
1707
1729
  RATE: "rate",
1708
1730
  PITCH: "pitch",
@@ -1733,8 +1755,15 @@ var SSML_ATTRS = {
1733
1755
  ALIAS: "alias",
1734
1756
  MARK: "mark",
1735
1757
  URI: "uri",
1758
+ ID: "id",
1759
+ MODEL: "model",
1760
+ PROFILE: "profile",
1761
+ URL: "url",
1762
+ SPEAKER_PROFILE_ID: "speakerProfileId",
1736
1763
  TYPE: "type",
1737
- VALUE: "value"
1764
+ VALUE: "value",
1765
+ FADE_IN: "fadein",
1766
+ FADE_OUT: "fadeout"
1738
1767
  };
1739
1768
  function escapeText(value) {
1740
1769
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
@@ -1824,6 +1853,30 @@ function getAttributes(element) {
1824
1853
  case SSML_TAGS.MSTTS_AUDIO_DURATION:
1825
1854
  addAttribute(attributes, SSML_ATTRS.VALUE, element.value);
1826
1855
  break;
1856
+ case SSML_TAGS.MSTTS_TURN:
1857
+ addAttribute(attributes, SSML_ATTRS.VOICE, element.voice);
1858
+ addAttribute(attributes, SSML_ATTRS.SPEAKER, element.speaker);
1859
+ break;
1860
+ case SSML_TAGS.MSTTS_BACKGROUND_AUDIO:
1861
+ addAttribute(attributes, SSML_ATTRS.SRC, element.src);
1862
+ addAttribute(attributes, SSML_ATTRS.VOLUME, element.volume);
1863
+ addAttribute(attributes, SSML_ATTRS.FADE_IN, element.fadeIn ?? element.fadein);
1864
+ addAttribute(attributes, SSML_ATTRS.FADE_OUT, element.fadeOut ?? element.fadeout);
1865
+ break;
1866
+ case SSML_TAGS.MSTTS_DIALOG:
1867
+ break;
1868
+ case SSML_TAGS.MSTTS_TTS_EMBEDDING:
1869
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
1870
+ break;
1871
+ case SSML_TAGS.MSTTS_EMBEDDING:
1872
+ addAttribute(attributes, SSML_ATTRS.ID, element.id);
1873
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
1874
+ break;
1875
+ case SSML_TAGS.MSTTS_VOICE_CONVERSION:
1876
+ addAttribute(attributes, SSML_ATTRS.URL, element.url);
1877
+ addAttribute(attributes, SSML_ATTRS.PROFILE, element.profile);
1878
+ addAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID, element.speakerProfileId);
1879
+ break;
1827
1880
  case SSML_TAGS.PARAGRAPH:
1828
1881
  case SSML_TAGS.SENTENCE:
1829
1882
  case SSML_TAGS.WORD:
@@ -2388,6 +2441,54 @@ function convertElement(node) {
2388
2441
  if (value !== void 0) element.value = value;
2389
2442
  return finishElement(element, node, attributes);
2390
2443
  }
2444
+ case SSML_TAGS.MSTTS_DIALOG: {
2445
+ const element = { type: SSML_TAGS.MSTTS_DIALOG };
2446
+ return finishElement(element, node, attributes);
2447
+ }
2448
+ case SSML_TAGS.MSTTS_TURN: {
2449
+ const element = { type: SSML_TAGS.MSTTS_TURN };
2450
+ const voice = readAttribute(attributes, SSML_ATTRS.VOICE);
2451
+ const speaker = readAttribute(attributes, SSML_ATTRS.SPEAKER);
2452
+ if (voice !== void 0) element.voice = voice;
2453
+ if (speaker !== void 0) element.speaker = speaker;
2454
+ return finishElement(element, node, attributes);
2455
+ }
2456
+ case SSML_TAGS.MSTTS_BACKGROUND_AUDIO: {
2457
+ const element = { type: SSML_TAGS.MSTTS_BACKGROUND_AUDIO };
2458
+ const src = readAttribute(attributes, SSML_ATTRS.SRC);
2459
+ const volume = readAttribute(attributes, SSML_ATTRS.VOLUME);
2460
+ const fadeIn = readAttribute(attributes, SSML_ATTRS.FADE_IN);
2461
+ const fadeOut = readAttribute(attributes, SSML_ATTRS.FADE_OUT);
2462
+ if (src !== void 0) element.src = src;
2463
+ if (volume !== void 0) element.volume = volume;
2464
+ if (fadeIn !== void 0) element.fadeIn = fadeIn;
2465
+ if (fadeOut !== void 0) element.fadeOut = fadeOut;
2466
+ return finishElement(element, node, attributes);
2467
+ }
2468
+ case SSML_TAGS.MSTTS_TTS_EMBEDDING: {
2469
+ const element = { type: SSML_TAGS.MSTTS_TTS_EMBEDDING };
2470
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
2471
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
2472
+ return finishElement(element, node, attributes);
2473
+ }
2474
+ case SSML_TAGS.MSTTS_EMBEDDING: {
2475
+ const element = { type: SSML_TAGS.MSTTS_EMBEDDING };
2476
+ const id = readAttribute(attributes, SSML_ATTRS.ID);
2477
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
2478
+ if (id !== void 0) element.id = id;
2479
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
2480
+ return finishElement(element, node, attributes);
2481
+ }
2482
+ case SSML_TAGS.MSTTS_VOICE_CONVERSION: {
2483
+ const element = { type: SSML_TAGS.MSTTS_VOICE_CONVERSION };
2484
+ const url = readAttribute(attributes, SSML_ATTRS.URL);
2485
+ const profile = readAttribute(attributes, SSML_ATTRS.PROFILE);
2486
+ const speakerProfileId = readAttribute(attributes, SSML_ATTRS.SPEAKER_PROFILE_ID);
2487
+ if (url !== void 0) element.url = url;
2488
+ if (profile !== void 0) element.profile = profile;
2489
+ if (speakerProfileId !== void 0) element.speakerProfileId = speakerProfileId;
2490
+ return finishElement(element, node, attributes);
2491
+ }
2391
2492
  default: {
2392
2493
  const element = {
2393
2494
  name: node.name,
@@ -2508,6 +2609,683 @@ function validateSsml(xmlString) {
2508
2609
  };
2509
2610
  }
2510
2611
  }
2612
+ var AZURE_VOICE_DEFINITIONS = [
2613
+ { name: "de-DE-ConradNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
2614
+ { name: "de-DE-KatjaNeural", locale: "de-DE", styles: ["cheerful", "sad"] },
2615
+ { name: "en-US-AndrewNeural", locale: "en-US", styles: ["empathetic", "relieved"] },
2616
+ {
2617
+ name: "en-US-GuyNeural",
2618
+ locale: "en-US",
2619
+ styles: [
2620
+ "angry",
2621
+ "cheerful",
2622
+ "excited",
2623
+ "friendly",
2624
+ "hopeful",
2625
+ "newscast",
2626
+ "sad",
2627
+ "shouting",
2628
+ "terrified",
2629
+ "unfriendly",
2630
+ "whispering"
2631
+ ]
2632
+ },
2633
+ {
2634
+ name: "en-US-JennyMultilingualNeural",
2635
+ locale: "en-US",
2636
+ styles: [
2637
+ "cheerful",
2638
+ "empathetic",
2639
+ "excited",
2640
+ "friendly",
2641
+ "hopeful",
2642
+ "sad",
2643
+ "shouting",
2644
+ "terrified",
2645
+ "unfriendly",
2646
+ "whispering"
2647
+ ]
2648
+ },
2649
+ {
2650
+ name: "en-US-JennyNeural",
2651
+ locale: "en-US",
2652
+ styles: [
2653
+ "assistant",
2654
+ "chat",
2655
+ "customerservice",
2656
+ "newscast",
2657
+ "cheerful",
2658
+ "empathetic",
2659
+ "excited",
2660
+ "friendly",
2661
+ "hopeful",
2662
+ "sad",
2663
+ "shouting",
2664
+ "terrified",
2665
+ "unfriendly",
2666
+ "whispering"
2667
+ ]
2668
+ },
2669
+ { name: "es-ES-ElviraNeural", locale: "es-ES" },
2670
+ { name: "fil-PH-AngeloNeural", locale: "fil-PH" },
2671
+ { name: "fr-FR-DeniseNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
2672
+ { name: "fr-FR-HenriNeural", locale: "fr-FR", styles: ["cheerful", "sad"] },
2673
+ { name: "id-ID-GadisNeural", locale: "id-ID" },
2674
+ { name: "it-IT-ElsaNeural", locale: "it-IT", styles: ["cheerful", "sad"] },
2675
+ { name: "ja-JP-KeitaNeural", locale: "ja-JP", styles: ["chat"] },
2676
+ { name: "ja-JP-MayuNeural", locale: "ja-JP", styles: ["calm", "cheerful", "sad"] },
2677
+ { name: "ja-JP-NanamiNeural", locale: "ja-JP", styles: ["chat", "customerservice", "cheerful", "whispering", "sad"] },
2678
+ { name: "ko-KR-SunHiNeural", locale: "ko-KR", styles: ["cheerful", "sad"] },
2679
+ { name: "ms-MY-YasminNeural", locale: "ms-MY" },
2680
+ { name: "pt-BR-FranciscaNeural", locale: "pt-BR", styles: ["calm"] },
2681
+ {
2682
+ name: "ru-RU-SvetlanaNeural",
2683
+ locale: "ru-RU",
2684
+ styles: ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
2685
+ },
2686
+ { name: "th-TH-PremwadeeNeural", locale: "th-TH" },
2687
+ { name: "vi-VN-HoaiMyNeural", locale: "vi-VN" },
2688
+ {
2689
+ name: "zh-CN-XiaoxiaoNeural",
2690
+ locale: "zh-CN",
2691
+ styles: [
2692
+ "assistant",
2693
+ "chat",
2694
+ "customerservice",
2695
+ "newscast",
2696
+ "cheerful",
2697
+ "empathetic",
2698
+ "excited",
2699
+ "friendly",
2700
+ "hopeful",
2701
+ "sad",
2702
+ "terrified",
2703
+ "whispering",
2704
+ "poetry-reading",
2705
+ "sports_commentary",
2706
+ "sports_commentary_excited",
2707
+ "story"
2708
+ ]
2709
+ },
2710
+ {
2711
+ name: "zh-CN-YunxiNeural",
2712
+ locale: "zh-CN",
2713
+ styles: [
2714
+ "narration-relaxed",
2715
+ "embarrassed",
2716
+ "fearful",
2717
+ "sad",
2718
+ "disgruntled",
2719
+ "serious",
2720
+ "angry",
2721
+ "depressed",
2722
+ "chat",
2723
+ "cheerful",
2724
+ "assistant"
2725
+ ]
2726
+ },
2727
+ { name: "zh-TW-HsiaoChenNeural", locale: "zh-TW" }
2728
+ ];
2729
+ var ALLOWED_BREAK_STRENGTHS = /* @__PURE__ */ new Set(["none", "x-weak", "weak", "medium", "strong", "x-strong"]);
2730
+ var ALLOWED_SAY_AS = /* @__PURE__ */ new Set([
2731
+ "characters",
2732
+ "spell-out",
2733
+ "cardinal",
2734
+ "ordinal",
2735
+ "number",
2736
+ "date",
2737
+ "time",
2738
+ "telephone",
2739
+ "fraction",
2740
+ "address",
2741
+ "name",
2742
+ "currency",
2743
+ "number_digit"
2744
+ ]);
2745
+ var ALLOWED_ROLES = /* @__PURE__ */ new Set([
2746
+ "Girl",
2747
+ "Boy",
2748
+ "YoungAdultFemale",
2749
+ "YoungAdultMale",
2750
+ "OlderAdultFemale",
2751
+ "OlderAdultMale",
2752
+ "SeniorFemale",
2753
+ "SeniorMale"
2754
+ ]);
2755
+ var ALLOWED_EMPHASIS_LEVELS = /* @__PURE__ */ new Set(["strong", "moderate", "reduced", "none"]);
2756
+ var ALLOWED_SILENCE_TYPES = /* @__PURE__ */ new Set([
2757
+ "Leading",
2758
+ "Tailing",
2759
+ "Sentenceboundary",
2760
+ "Comma",
2761
+ "Semicolon",
2762
+ "Enumerationcomma"
2763
+ ]);
2764
+ var ALLOWED_VISEME_TYPES = /* @__PURE__ */ new Set(["redlips_front", "FacialExpression"]);
2765
+ function decodeAttribute(value) {
2766
+ return value.replace(
2767
+ /&(?:amp|apos|gt|lt|quot);/gi,
2768
+ (entity) => ({ "&amp;": "&", "&apos;": "'", "&gt;": ">", "&lt;": "<", "&quot;": '"' })[entity.toLowerCase()] ?? entity
2769
+ );
2770
+ }
2771
+ function findTagEnd2(source, start) {
2772
+ let quote = "";
2773
+ for (let index = start; index < source.length; index += 1) {
2774
+ const character = source[index];
2775
+ if (quote) {
2776
+ if (character === quote) quote = "";
2777
+ } else if (character === '"' || character === "'") quote = character;
2778
+ else if (character === ">") return index;
2779
+ }
2780
+ return source.length - 1;
2781
+ }
2782
+ function tokenizeElements(source) {
2783
+ const tokens = [];
2784
+ const openElements = [];
2785
+ let index = 0;
2786
+ while (index < source.length) {
2787
+ const start = source.indexOf("<", index);
2788
+ if (start === -1) break;
2789
+ if (source.startsWith("<!--", start)) {
2790
+ const end2 = source.indexOf("-->", start + 4);
2791
+ index = end2 === -1 ? source.length : end2 + 3;
2792
+ continue;
2793
+ }
2794
+ if (source.startsWith("<![CDATA[", start)) {
2795
+ const end2 = source.indexOf("]]>", start + 9);
2796
+ index = end2 === -1 ? source.length : end2 + 3;
2797
+ continue;
2798
+ }
2799
+ if (source.startsWith("<?", start)) {
2800
+ const end2 = source.indexOf("?>", start + 2);
2801
+ index = end2 === -1 ? source.length : end2 + 2;
2802
+ continue;
2803
+ }
2804
+ const end = findTagEnd2(source, start + 1);
2805
+ const raw = source.slice(start, end + 1);
2806
+ if (raw.startsWith("</")) {
2807
+ openElements.pop();
2808
+ index = end + 1;
2809
+ continue;
2810
+ }
2811
+ const nameMatch = /^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/.exec(raw);
2812
+ if (!nameMatch?.[1]) {
2813
+ index = end + 1;
2814
+ continue;
2815
+ }
2816
+ const attributes = /* @__PURE__ */ new Map();
2817
+ const attributeSource = raw.slice(nameMatch[0].length, raw.length - 1).replace(/\/\s*$/, "");
2818
+ const attributePattern = /([A-Za-z_][A-Za-z0-9_.:-]*)\s*=\s*(["'])([\s\S]*?)\2/g;
2819
+ for (const match of attributeSource.matchAll(attributePattern)) {
2820
+ attributes.set(match[1].toLowerCase(), decodeAttribute(match[3]));
2821
+ }
2822
+ const selfClosing = /\/\s*>$/.test(raw);
2823
+ const parent = openElements[openElements.length - 1];
2824
+ const childElementIndex = parent?.childElementCount;
2825
+ if (parent) parent.childElementCount += 1;
2826
+ const parentVoiceName = [...openElements].reverse().find((element) => element.voiceName)?.voiceName;
2827
+ const tokenName = nameMatch[1];
2828
+ const tokenVoiceName = tokenName.toLowerCase() === "voice" ? attributes.get("name") : tokenName.toLowerCase() === "mstts:turn" ? attributes.get("voice") ?? parentVoiceName : parentVoiceName;
2829
+ tokens.push({
2830
+ attributes,
2831
+ childElementIndex,
2832
+ end,
2833
+ name: tokenName,
2834
+ parentName: parent?.name,
2835
+ parentVoiceName,
2836
+ selfClosing,
2837
+ start
2838
+ });
2839
+ if (!selfClosing) {
2840
+ openElements.push({
2841
+ childElementCount: 0,
2842
+ name: tokenName,
2843
+ voiceName: tokenVoiceName
2844
+ });
2845
+ }
2846
+ index = end + 1;
2847
+ }
2848
+ return tokens;
2849
+ }
2850
+ function location(source, offset) {
2851
+ const before = source.slice(0, Math.max(0, offset));
2852
+ const line = before.split("\n").length;
2853
+ return { line, column: before.length - (before.lastIndexOf("\n") + 1) + 1 };
2854
+ }
2855
+ function addDiagnostic(diagnostics, source, offset, message, severity = "error", code2) {
2856
+ diagnostics.push({
2857
+ ...location(source, offset),
2858
+ message,
2859
+ severity,
2860
+ source: "ssml-static-validator",
2861
+ ...code2 ? { code: code2 } : {}
2862
+ });
2863
+ }
2864
+ function isSupportedProsodyRate(value) {
2865
+ const trimmed = value.trim();
2866
+ if (/^(x-slow|slow|medium|fast|x-fast|[+-]?\d+(?:\.\d+)?%)$/.test(trimmed)) return true;
2867
+ const multiplier = /^(\d+(?:\.\d+)?)(x)?$/i.exec(trimmed);
2868
+ if (!multiplier) return false;
2869
+ const numericValue = Number(multiplier[1]);
2870
+ return numericValue >= 0.5 && numericValue <= 2;
2871
+ }
2872
+ function isValidAzureAudioDuration(value) {
2873
+ const trimmed = value.trim();
2874
+ const numeric = /^(\d+(?:\.\d+)?)(ms|s)$/.exec(trimmed);
2875
+ if (numeric) return Number(numeric[1]) > 0;
2876
+ const clock = /^(\d{2,}):([0-5]\d):([0-5]\d)(?:\.(\d{1,3}))?$/.exec(trimmed);
2877
+ if (!clock) return false;
2878
+ return Number(clock[1]) > 0 || Number(clock[2]) > 0 || Number(clock[3]) > 0 || Number(clock[4] ?? 0) > 0;
2879
+ }
2880
+ function isValidAzureBackgroundAudioDuration(value) {
2881
+ const match = /^(\d+(?:\.\d+)?)(ms|s)?$/i.exec(value.trim());
2882
+ if (!match) return false;
2883
+ const milliseconds = Number(match[1]) * (match[2]?.toLowerCase() === "s" ? 1e3 : 1);
2884
+ return Number.isFinite(milliseconds) && milliseconds >= 0 && milliseconds <= 1e4;
2885
+ }
2886
+ function attr(token, name) {
2887
+ return token.attributes.get(name.toLowerCase());
2888
+ }
2889
+ var DEFAULT_LANGUAGE_ALIASES = {
2890
+ "zh-CN": ["zh-Hans"],
2891
+ "zh-TW": ["zh-Hant"]
2892
+ };
2893
+ function canonicalLanguageTag(language) {
2894
+ const trimmed = language.trim();
2895
+ if (!trimmed) return "";
2896
+ try {
2897
+ return new Intl.Locale(trimmed).toString().toLowerCase();
2898
+ } catch {
2899
+ return trimmed.toLowerCase();
2900
+ }
2901
+ }
2902
+ function createLanguageNormalizer(options) {
2903
+ const aliases = /* @__PURE__ */ new Map();
2904
+ const addAliasGroup = (canonical, values) => {
2905
+ const normalizedCanonical = canonicalLanguageTag(canonical);
2906
+ if (!normalizedCanonical) return;
2907
+ aliases.set(normalizedCanonical, normalizedCanonical);
2908
+ for (const value of values) {
2909
+ const normalizedValue = canonicalLanguageTag(value);
2910
+ if (normalizedValue) aliases.set(normalizedValue, normalizedCanonical);
2911
+ }
2912
+ };
2913
+ for (const [canonical, values] of Object.entries(DEFAULT_LANGUAGE_ALIASES)) addAliasGroup(canonical, values);
2914
+ for (const [canonical, valueOrValues] of Object.entries(options.languageAliases ?? {}))
2915
+ addAliasGroup(canonical, typeof valueOrValues === "string" ? [valueOrValues] : valueOrValues);
2916
+ return (language) => {
2917
+ const customValue = options.normalizeLanguage ? options.normalizeLanguage(language) : language;
2918
+ const normalized = canonicalLanguageTag(customValue);
2919
+ return aliases.get(normalized) ?? normalized;
2920
+ };
2921
+ }
2922
+ function voiceLocalePrefix(voiceName) {
2923
+ const match = /^(?<language>[A-Za-z]{2,3})-(?<region>[A-Za-z]{2}|\d{3})(?:-|$)/.exec(voiceName.trim());
2924
+ if (!match?.groups) return void 0;
2925
+ const tag = `${match.groups.language}-${match.groups.region}`;
2926
+ return {
2927
+ language: match.groups.language.toLowerCase(),
2928
+ region: match.groups.region.toLowerCase(),
2929
+ tag
2930
+ };
2931
+ }
2932
+ function definitionFromStyleMap(voiceName, styles) {
2933
+ return {
2934
+ name: voiceName,
2935
+ locale: voiceLocalePrefix(voiceName)?.tag ?? "",
2936
+ styles
2937
+ };
2938
+ }
2939
+ function normalizeVoiceCatalog(options) {
2940
+ const definitions = /* @__PURE__ */ new Map();
2941
+ for (const definition of AZURE_VOICE_DEFINITIONS) definitions.set(definition.name.toLowerCase(), definition);
2942
+ for (const definition of options.voiceCatalog ?? []) definitions.set(definition.name.toLowerCase(), definition);
2943
+ for (const definition of options.voiceDefinitions ?? []) definitions.set(definition.name.toLowerCase(), definition);
2944
+ for (const definition of options.customVoiceDefinitions ?? [])
2945
+ definitions.set(definition.name.toLowerCase(), definition);
2946
+ for (const [voiceName, styles] of Object.entries(options.customVoiceStyleMap ?? {})) {
2947
+ const key = voiceName.toLowerCase();
2948
+ const current = definitions.get(key);
2949
+ definitions.set(key, {
2950
+ ...current ?? definitionFromStyleMap(voiceName, styles),
2951
+ name: current?.name ?? voiceName,
2952
+ styles: styles.map((style) => style.toLowerCase())
2953
+ });
2954
+ }
2955
+ return definitions;
2956
+ }
2957
+ function diagnosticSeverity(policy) {
2958
+ if (policy === "ignore") return void 0;
2959
+ return policy === "error" ? "error" : "warning";
2960
+ }
2961
+ function languagePart(language) {
2962
+ try {
2963
+ return new Intl.Locale(language).language.toLowerCase();
2964
+ } catch {
2965
+ return language.split("-")[0]?.toLowerCase() ?? "";
2966
+ }
2967
+ }
2968
+ function definitionMatchesLanguage(definition, voiceName, language, normalizeLanguage) {
2969
+ const candidateLanguages = definition ? [definition.locale, ...definition.secondaryLocales ?? []].filter(Boolean) : [voiceLocalePrefix(voiceName)?.tag ?? ""];
2970
+ if (candidateLanguages.length === 0 || !language.trim()) return void 0;
2971
+ const normalizedLanguage = normalizeLanguage(language);
2972
+ const normalizedCandidates = candidateLanguages.map(normalizeLanguage);
2973
+ if (normalizedCandidates.includes(normalizedLanguage)) return true;
2974
+ if (!normalizedLanguage || !normalizedCandidates.some(Boolean)) return void 0;
2975
+ return normalizedLanguage === languagePart(normalizedLanguage) ? normalizedCandidates.some((candidate) => languagePart(candidate) === normalizedLanguage) : false;
2976
+ }
2977
+ function canonicalTagName(name) {
2978
+ const normalized = name.toLowerCase();
2979
+ if (normalized === "express-as" || normalized === "expressas") return "mstts:express-as";
2980
+ if (normalized === "sayas") return "say-as";
2981
+ return normalized;
2982
+ }
2983
+ function validateVoiceFeatureMatrix(token, source, diagnostics, voiceName, definition) {
2984
+ if (!voiceName || !definition || token.name.toLowerCase() === "voice" || token.name.toLowerCase() === "mstts:turn")
2985
+ return;
2986
+ const tagName = canonicalTagName(token.name);
2987
+ const unsupportedTags = new Set((definition.unsupportedTags ?? []).map(canonicalTagName));
2988
+ const supportedTags = definition.supportedTags?.map(canonicalTagName);
2989
+ if (unsupportedTags.has(tagName) || supportedTags !== void 0 && !supportedTags.includes(tagName)) {
2990
+ addDiagnostic(
2991
+ diagnostics,
2992
+ source,
2993
+ token.start,
2994
+ `Tag <${token.name}> is not supported by voice "${voiceName}" according to the configured feature matrix.`,
2995
+ "error",
2996
+ "azure-unsupported-tag-for-voice"
2997
+ );
2998
+ }
2999
+ }
3000
+ function validateAudioSource(token, source, diagnostics, options, elementName2) {
3001
+ const src = attr(token, "src");
3002
+ if (!src) {
3003
+ addDiagnostic(diagnostics, source, token.start, `<${elementName2}> requires a "src" attribute.`);
3004
+ return;
3005
+ }
3006
+ let parsed;
3007
+ try {
3008
+ parsed = new URL(src);
3009
+ } catch {
3010
+ addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must be an absolute HTTP(S) URL.`);
3011
+ return;
3012
+ }
3013
+ if (parsed.protocol !== "https:" && !(options.allowHttpAudio && parsed.protocol === "http:"))
3014
+ addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> must use HTTPS.`);
3015
+ const isAllowedOrigin = options.allowedAudioOrigins?.some((allowedOrigin) => {
3016
+ try {
3017
+ return new URL(allowedOrigin).origin === parsed.origin;
3018
+ } catch {
3019
+ return allowedOrigin === parsed.origin;
3020
+ }
3021
+ }) ?? false;
3022
+ if (options.allowedAudioOrigins && !isAllowedOrigin)
3023
+ addDiagnostic(diagnostics, source, token.start, `<${elementName2} src> origin "${parsed.origin}" is not allowed.`);
3024
+ else if (!isAllowedOrigin && !options.allowExternalAudio)
3025
+ addDiagnostic(
3026
+ diagnostics,
3027
+ source,
3028
+ token.start,
3029
+ `<${elementName2} src> external origin "${parsed.origin}" is blocked by default; set allowExternalAudio to true or provide allowedAudioOrigins.`
3030
+ );
3031
+ }
3032
+ function validateElement(token, source, diagnostics, voiceName, options, voiceCatalog) {
3033
+ const name = token.name.toLowerCase();
3034
+ if (name === "voice" && !attr(token, "name")?.trim())
3035
+ addDiagnostic(diagnostics, source, token.start, '<voice> requires a non-empty "name" attribute.');
3036
+ if (name === "break") {
3037
+ const time = attr(token, "time");
3038
+ const strength = attr(token, "strength");
3039
+ if (!time && !strength)
3040
+ addDiagnostic(diagnostics, source, token.start, '<break> requires either "time" or "strength".');
3041
+ if (time && strength)
3042
+ addDiagnostic(diagnostics, source, token.start, '<break> must not specify both "time" and "strength".');
3043
+ if (time && !/^\d+(?:\.\d+)?(?:ms|s)$/.test(time.trim()))
3044
+ addDiagnostic(diagnostics, source, token.start, '<break time> must use a numeric value followed by "ms" or "s".');
3045
+ if (strength && !ALLOWED_BREAK_STRENGTHS.has(strength))
3046
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <break strength> value "${strength}".`);
3047
+ }
3048
+ if (name === "prosody") {
3049
+ const rate = attr(token, "rate");
3050
+ const pitch = attr(token, "pitch");
3051
+ const volume = attr(token, "volume");
3052
+ if (rate && !isSupportedProsodyRate(rate))
3053
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody rate> value "${rate}".`);
3054
+ if (pitch && !/^(x-low|low|medium|high|x-high|[+-]?\d+(?:\.\d+)?(?:st|Hz|%)?)$/.test(pitch.trim()))
3055
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody pitch> value "${pitch}".`);
3056
+ if (volume && !/^(silent|x-soft|soft|medium|loud|x-loud|[+-]?\d+(?:\.\d+)?(?:dB|%)?)$/.test(volume.trim()))
3057
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <prosody volume> value "${volume}".`);
3058
+ }
3059
+ if (name === "mstts:express-as" || name === "express-as" || name === "expressas") {
3060
+ const style = attr(token, "style");
3061
+ if (!style?.trim())
3062
+ addDiagnostic(diagnostics, source, token.start, '<mstts:express-as> requires a non-empty "style" attribute.');
3063
+ const degree = attr(token, "styledegree") ?? attr(token, "style-degree");
3064
+ if (degree && (!/^\d+(?:\.\d+)?$/.test(degree) || Number(degree) < 0.01 || Number(degree) > 2))
3065
+ addDiagnostic(
3066
+ diagnostics,
3067
+ source,
3068
+ token.start,
3069
+ "<mstts:express-as styledegree> must be a number between 0.01 and 2."
3070
+ );
3071
+ const role = attr(token, "role");
3072
+ if (role && !ALLOWED_ROLES.has(role))
3073
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:express-as role> value "${role}".`);
3074
+ const definition = voiceName ? voiceCatalog.get(voiceName.toLowerCase()) : void 0;
3075
+ const supportedStyles = definition?.styles;
3076
+ const severity = diagnosticSeverity(options.unsupportedStylePolicy ?? options.unknownVoicePolicy ?? "warn");
3077
+ if (style && definition && !supportedStyles?.some((candidate) => candidate.toLowerCase() === style.toLowerCase()) && severity)
3078
+ addDiagnostic(
3079
+ diagnostics,
3080
+ source,
3081
+ token.start,
3082
+ `Unknown style "${style}" is not supported by voice "${voiceName}" according to the configured voice style map.`,
3083
+ severity,
3084
+ "azure-unsupported-style"
3085
+ );
3086
+ if (style && voiceName && !definition && severity)
3087
+ addDiagnostic(
3088
+ diagnostics,
3089
+ source,
3090
+ token.start,
3091
+ `Unknown style "${style}" cannot be verified because voice "${voiceName}" is not registered in the voice style map.`,
3092
+ severity
3093
+ );
3094
+ }
3095
+ if (name === "say-as" || name === "sayas") {
3096
+ const interpretAs = attr(token, "interpret-as");
3097
+ if (!interpretAs || !ALLOWED_SAY_AS.has(interpretAs))
3098
+ addDiagnostic(diagnostics, source, token.start, `<say-as> requires a supported "interpret-as" value.`);
3099
+ }
3100
+ if (name === "phoneme" && (!attr(token, "alphabet") || !attr(token, "ph")))
3101
+ addDiagnostic(diagnostics, source, token.start, '<phoneme> requires both "alphabet" and "ph" attributes.');
3102
+ if (name === "emphasis" && attr(token, "level") && !ALLOWED_EMPHASIS_LEVELS.has(attr(token, "level") ?? ""))
3103
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <emphasis level> value "${attr(token, "level")}".`);
3104
+ if (name === "sub" && !attr(token, "alias")?.trim())
3105
+ addDiagnostic(diagnostics, source, token.start, '<sub> requires a non-empty "alias" attribute.');
3106
+ if (name === "lang" && !attr(token, "xml:lang")?.trim() && !attr(token, "lang")?.trim())
3107
+ addDiagnostic(diagnostics, source, token.start, '<lang> requires an "xml:lang" attribute.');
3108
+ if (name === "mark" && !attr(token, "name")?.trim())
3109
+ addDiagnostic(diagnostics, source, token.start, '<mark> requires a non-empty "name" attribute.');
3110
+ if (name === "bookmark" && !attr(token, "mark")?.trim())
3111
+ addDiagnostic(diagnostics, source, token.start, '<bookmark> requires a non-empty "mark" attribute.');
3112
+ if (name === "lexicon") {
3113
+ const uri = attr(token, "uri");
3114
+ if (!uri) addDiagnostic(diagnostics, source, token.start, '<lexicon> requires a "uri" attribute.');
3115
+ else {
3116
+ try {
3117
+ const parsed = new URL(uri);
3118
+ if (parsed.protocol !== "https:")
3119
+ addDiagnostic(diagnostics, source, token.start, "<lexicon uri> must use HTTPS.");
3120
+ } catch {
3121
+ addDiagnostic(diagnostics, source, token.start, "<lexicon uri> must be an absolute HTTPS URL.");
3122
+ }
3123
+ }
3124
+ }
3125
+ if (name === "mstts:silence") {
3126
+ const type = attr(token, "type");
3127
+ const value = attr(token, "value");
3128
+ if (!type || !ALLOWED_SILENCE_TYPES.has(type))
3129
+ addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a supported "type" attribute.');
3130
+ if (!value || !/^\d+(?:\.\d+)?(?:ms|s)$/.test(value.trim()))
3131
+ addDiagnostic(diagnostics, source, token.start, '<mstts:silence> requires a time-valued "value" attribute.');
3132
+ }
3133
+ if (name === "mstts:audioduration") {
3134
+ const value = attr(token, "value");
3135
+ if (!value || !isValidAzureAudioDuration(value))
3136
+ addDiagnostic(
3137
+ diagnostics,
3138
+ source,
3139
+ token.start,
3140
+ '<mstts:audioduration> requires a positive duration such as "10s", "5000ms", or "00:00:10".'
3141
+ );
3142
+ if (!token.selfClosing)
3143
+ addDiagnostic(diagnostics, source, token.start, "<mstts:audioduration> must be self-closing.");
3144
+ }
3145
+ if (name === "mstts:viseme") {
3146
+ const type = attr(token, "type");
3147
+ if (!type || !ALLOWED_VISEME_TYPES.has(type))
3148
+ addDiagnostic(diagnostics, source, token.start, '<mstts:viseme> requires a supported "type" attribute.');
3149
+ }
3150
+ if (name === "audio") {
3151
+ validateAudioSource(token, source, diagnostics, options, "audio");
3152
+ }
3153
+ if (name === "mstts:turn") {
3154
+ if (!attr(token, "voice")?.trim() && !attr(token, "speaker")?.trim())
3155
+ addDiagnostic(
3156
+ diagnostics,
3157
+ source,
3158
+ token.start,
3159
+ '<mstts:turn> requires a non-empty "voice" or "speaker" attribute.'
3160
+ );
3161
+ if (token.parentName?.toLowerCase() !== "mstts:dialog")
3162
+ addDiagnostic(diagnostics, source, token.start, "<mstts:turn> is only allowed directly inside <mstts:dialog>.");
3163
+ }
3164
+ if (name === "mstts:backgroundaudio") {
3165
+ validateAudioSource(token, source, diagnostics, options, "mstts:backgroundaudio");
3166
+ const volume = attr(token, "volume");
3167
+ if (volume !== void 0 && (!/^\d+(?:\.\d+)?$/.test(volume.trim()) || Number(volume) > 100))
3168
+ addDiagnostic(diagnostics, source, token.start, `Unsupported <mstts:backgroundaudio volume> value "${volume}".`);
3169
+ for (const [attribute, value] of [
3170
+ ["fadein", attr(token, "fadein")],
3171
+ ["fadeout", attr(token, "fadeout")]
3172
+ ]) {
3173
+ if (value !== void 0 && !isValidAzureBackgroundAudioDuration(value))
3174
+ addDiagnostic(
3175
+ diagnostics,
3176
+ source,
3177
+ token.start,
3178
+ `<mstts:backgroundaudio ${attribute}> must be between 0 and 10000 milliseconds, for example "500ms" or "10s".`
3179
+ );
3180
+ }
3181
+ if (token.parentName?.toLowerCase() !== "speak" || token.childElementIndex !== 0)
3182
+ addDiagnostic(
3183
+ diagnostics,
3184
+ source,
3185
+ token.start,
3186
+ "<mstts:backgroundaudio> must be the first element directly under <speak>."
3187
+ );
3188
+ if (!token.selfClosing)
3189
+ addDiagnostic(diagnostics, source, token.start, "<mstts:backgroundaudio> must be self-closing.");
3190
+ }
3191
+ }
3192
+ function validateAzureSsml(ssml, options = {}) {
3193
+ const diagnostics = [];
3194
+ if (typeof ssml !== "string") {
3195
+ return [
3196
+ {
3197
+ line: 1,
3198
+ column: 1,
3199
+ message: "SSML input must be a string",
3200
+ severity: "error",
3201
+ source: "ssml-static-validator"
3202
+ }
3203
+ ];
3204
+ }
3205
+ const maxLength = options.maxLength ?? 1e4;
3206
+ if (ssml.length > maxLength)
3207
+ addDiagnostic(diagnostics, ssml, maxLength, `SSML exceeds the maximum length of ${maxLength} characters.`);
3208
+ try {
3209
+ parseSsml(ssml);
3210
+ } catch (error) {
3211
+ const message = error instanceof Error ? error.message.replace(/ at position \d+$/, "") : String(error);
3212
+ const match = / at position (\d+)$/.exec(error instanceof Error ? error.message : "");
3213
+ addDiagnostic(diagnostics, ssml, match ? Number(match[1]) : 0, message);
3214
+ return diagnostics;
3215
+ }
3216
+ const tokens = tokenizeElements(ssml);
3217
+ const speak = tokens.find((token) => token.name.toLowerCase() === "speak");
3218
+ const voices = tokens.filter((token) => token.name.toLowerCase() === "voice");
3219
+ const backgroundAudioTokens = tokens.filter((token) => token.name.toLowerCase() === "mstts:backgroundaudio");
3220
+ for (const [index, token] of backgroundAudioTokens.entries()) {
3221
+ if (index > 0)
3222
+ addDiagnostic(
3223
+ diagnostics,
3224
+ ssml,
3225
+ token.start,
3226
+ "An SSML document can contain at most one <mstts:backgroundaudio> element."
3227
+ );
3228
+ }
3229
+ if (!speak || voices.length === 0)
3230
+ addDiagnostic(
3231
+ diagnostics,
3232
+ ssml,
3233
+ speak?.start ?? 0,
3234
+ "Azure SSML requires at least one <voice> element under <speak>."
3235
+ );
3236
+ const voiceName = voices[0] ? attr(voices[0], "name") : void 0;
3237
+ const voiceCatalog = normalizeVoiceCatalog(options);
3238
+ const normalizeLanguage = createLanguageNormalizer(options);
3239
+ const policySeverity = diagnosticSeverity(options.unknownVoicePolicy ?? "warn");
3240
+ const voicesToValidate = options.validateNestedVoices === false ? voices.slice(0, 1) : voices;
3241
+ for (const token of voicesToValidate) {
3242
+ const name = attr(token, "name")?.trim();
3243
+ const language = attr(token, "xml:lang")?.trim() || (speak ? attr(speak, "xml:lang")?.trim() : void 0);
3244
+ const definition = name ? voiceCatalog.get(name.toLowerCase()) : void 0;
3245
+ if (name && !definition && policySeverity)
3246
+ addDiagnostic(
3247
+ diagnostics,
3248
+ ssml,
3249
+ token.start,
3250
+ `Unknown voice "${name}" is not registered in the voice catalog.`,
3251
+ policySeverity,
3252
+ "azure-unknown-voice"
3253
+ );
3254
+ if (name && language && definitionMatchesLanguage(definition, name, language, normalizeLanguage) === false)
3255
+ addDiagnostic(
3256
+ diagnostics,
3257
+ ssml,
3258
+ token.start,
3259
+ `Voice "${name}" does not match language "${language}"; the voice name prefix indicates a different language or region.`,
3260
+ "warning",
3261
+ "azure-locale-mismatch"
3262
+ );
3263
+ }
3264
+ for (const token of tokens) {
3265
+ const tokenName = token.name.toLowerCase();
3266
+ const tokenVoiceName = tokenName === "voice" ? attr(token, "name")?.trim() : tokenName === "mstts:turn" ? attr(token, "voice")?.trim() || token.parentVoiceName : options.validateNestedVoices === false ? voiceName : token.parentVoiceName;
3267
+ validateElement(token, ssml, diagnostics, tokenVoiceName, options, voiceCatalog);
3268
+ const definition = tokenVoiceName ? voiceCatalog.get(tokenVoiceName.toLowerCase()) : void 0;
3269
+ validateVoiceFeatureMatrix(token, ssml, diagnostics, tokenVoiceName, definition);
3270
+ if (tokenName === "voice" && options.model && definition?.models && !definition.models.some((model) => model.toLowerCase() === options.model?.toLowerCase())) {
3271
+ addDiagnostic(
3272
+ diagnostics,
3273
+ ssml,
3274
+ token.start,
3275
+ `Voice "${tokenVoiceName}" does not support model "${options.model}" according to the configured feature matrix.`,
3276
+ "error",
3277
+ "azure-unsupported-model-for-voice"
3278
+ );
3279
+ }
3280
+ }
3281
+ return diagnostics;
3282
+ }
3283
+ var AZURE_VOICE_CATALOG_METADATA = {
3284
+ apiVersion: "2025-10-01",
3285
+ generatedAt: "2026-08-28T00:00:00.000Z",
3286
+ regions: [],
3287
+ voiceCount: AZURE_VOICE_DEFINITIONS.length
3288
+ };
2511
3289
 
2512
3290
  // packages/ssml-editor-react/src/clearSsmlDocument.ts
2513
3291
  function getDocumentChildren(document2) {
@@ -3266,7 +4044,7 @@ function createSsmlInsertionEdit(source, startOffset, endOffset, template, eol =
3266
4044
  }
3267
4045
 
3268
4046
  // packages/ssml-editor-react/src/ssmlContext.ts
3269
- function findTagEnd2(source, start, limit) {
4047
+ function findTagEnd3(source, start, limit) {
3270
4048
  let quote;
3271
4049
  for (let index = start + 1; index < limit; index += 1) {
3272
4050
  const character = source[index];
@@ -3336,7 +4114,7 @@ function findActiveSsmlTags(source, offset) {
3336
4114
  index = end;
3337
4115
  continue;
3338
4116
  }
3339
- const tagEnd = findTagEnd2(source, tagStart, source.length);
4117
+ const tagEnd = findTagEnd3(source, tagStart, source.length);
3340
4118
  const tag = source.slice(tagStart, tagEnd === -1 ? source.length : tagEnd + 1);
3341
4119
  const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
3342
4120
  const openingMatch = tag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
@@ -3379,7 +4157,7 @@ function findSsmlVoiceContext(source, offset) {
3379
4157
  index = end === -1 || end + 2 > limit ? limit : end + 2;
3380
4158
  continue;
3381
4159
  }
3382
- const tagEnd = findTagEnd2(source, tagStart, limit);
4160
+ const tagEnd = findTagEnd3(source, tagStart, limit);
3383
4161
  if (tagEnd === -1) {
3384
4162
  break;
3385
4163
  }
@@ -4139,6 +4917,24 @@ var SSML_COMPLETION_SNIPPETS = [
4139
4917
  label: "mstts:audioduration",
4140
4918
  insertText: '<mstts:audioduration value="10s" />'
4141
4919
  },
4920
+ {
4921
+ label: "mstts:dialog",
4922
+ insertText: `<mstts:dialog>
4923
+ <mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>
4924
+ </mstts:dialog>`
4925
+ },
4926
+ {
4927
+ label: "mstts:turn",
4928
+ insertText: `<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>`
4929
+ },
4930
+ {
4931
+ label: "mstts:backgroundaudio",
4932
+ insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:-3dB}" />`
4933
+ },
4934
+ {
4935
+ label: "mstts:ttsembedding",
4936
+ insertText: `<mstts:ttsembedding>\${1:text}</mstts:ttsembedding>`
4937
+ },
4142
4938
  {
4143
4939
  label: "sub",
4144
4940
  insertText: `<sub alias="\${1:\u8AAD\u307F}">\${2:\u6F22\u5B57}</sub>`
@@ -4317,7 +5113,7 @@ function registerSsmlCodeActions(monaco) {
4317
5113
 
4318
5114
  // packages/ssml-editor-react/src/ssmlCodeLens.ts
4319
5115
  var CODE_LENS_COMMAND = "ssml-editor.codeLens";
4320
- function findTagEnd3(source, start) {
5116
+ function findTagEnd4(source, start) {
4321
5117
  let quote;
4322
5118
  for (let index = start + 1; index < source.length; index += 1) {
4323
5119
  const character = source[index];
@@ -4357,7 +5153,7 @@ function getElementEnd(source, tagName, tagEnd) {
4357
5153
  index = index === -1 ? source.length : index + 3;
4358
5154
  continue;
4359
5155
  }
4360
- const nextEnd = findTagEnd3(source, nextStart);
5156
+ const nextEnd = findTagEnd4(source, nextStart);
4361
5157
  if (nextEnd === -1) {
4362
5158
  break;
4363
5159
  }
@@ -4409,7 +5205,7 @@ function registerSsmlCodeLens(monaco, editor, onOpenPopover) {
4409
5205
  index = index === -1 ? source.length : index + 3;
4410
5206
  continue;
4411
5207
  }
4412
- const tagEnd = findTagEnd3(source, tagStart);
5208
+ const tagEnd = findTagEnd4(source, tagStart);
4413
5209
  if (tagEnd === -1) {
4414
5210
  break;
4415
5211
  }
@@ -4806,6 +5602,63 @@ var SSML_TAG_DEFINITIONS = [
4806
5602
  example: "10s"
4807
5603
  }
4808
5604
  ]
5605
+ },
5606
+ {
5607
+ name: "mstts:dialog",
5608
+ description: "Groups multiple Azure dialog turns that can use different voices.",
5609
+ parameters: []
5610
+ },
5611
+ {
5612
+ name: "mstts:turn",
5613
+ description: "Adds one dialog turn using the required Azure voice name.",
5614
+ parameters: [
5615
+ {
5616
+ name: "voice",
5617
+ description: "The Azure voice used for this turn, such as `en-US-JennyNeural`.",
5618
+ example: "en-US-JennyNeural"
5619
+ }
5620
+ ]
5621
+ },
5622
+ {
5623
+ name: "mstts:backgroundaudio",
5624
+ description: "Plays background audio while speech is synthesized.",
5625
+ parameters: [
5626
+ {
5627
+ name: "src",
5628
+ description: "An absolute HTTP(S) URL for the background audio file.",
5629
+ example: "https://example.com/music.mp3"
5630
+ },
5631
+ {
5632
+ name: "volume",
5633
+ description: "The background audio volume, for example `-3dB` or `medium`.",
5634
+ example: "-3dB"
5635
+ },
5636
+ {
5637
+ name: "fadein",
5638
+ description: "The fade-in duration, for example `1s`.",
5639
+ example: "1s"
5640
+ },
5641
+ {
5642
+ name: "fadeout",
5643
+ description: "The fade-out duration, for example `500ms`.",
5644
+ example: "500ms"
5645
+ }
5646
+ ]
5647
+ },
5648
+ {
5649
+ name: "mstts:ttsembedding",
5650
+ description: "Embeds custom voice or speaker profile metadata for Azure Speech.",
5651
+ parameters: []
5652
+ },
5653
+ {
5654
+ name: "mstts:embedding",
5655
+ description: "Specifies embedding metadata for custom voice scenarios.",
5656
+ parameters: []
5657
+ },
5658
+ {
5659
+ name: "mstts:voiceconversion",
5660
+ description: "Specifies voice conversion metadata for custom voice scenarios.",
5661
+ parameters: []
4809
5662
  }
4810
5663
  ];
4811
5664
  var definitionsByName = /* @__PURE__ */ new Map();
@@ -4864,7 +5717,7 @@ function toRange(source, token) {
4864
5717
  function containsOffset(token, offset) {
4865
5718
  return offset >= token.start && offset < token.end;
4866
5719
  }
4867
- function findTagEnd4(source, start) {
5720
+ function findTagEnd5(source, start) {
4868
5721
  let quote;
4869
5722
  for (let index = start; index < source.length; index += 1) {
4870
5723
  const character = source[index];
@@ -4995,7 +5848,7 @@ function findTagAtOffset(source, offset) {
4995
5848
  searchStart = tokenEnd2;
4996
5849
  continue;
4997
5850
  }
4998
- const tagEnd = findTagEnd4(source, start + 1);
5851
+ const tagEnd = findTagEnd5(source, start + 1);
4999
5852
  const contentEnd = tagEnd ?? source.length;
5000
5853
  const tokenEnd = tagEnd === void 0 ? source.length : tagEnd + 1;
5001
5854
  if (offset < tokenEnd) {
@@ -5410,6 +6263,140 @@ function useSsmlMonaco({
5410
6263
  return { editorRef, onMount };
5411
6264
  }
5412
6265
 
6266
+ // packages/ssml-editor-react/src/components/VisualSsmlEditor.tsx
6267
+ var import_react3 = require("react");
6268
+ function isElement(node) {
6269
+ return typeof node !== "string" && node.type !== "text";
6270
+ }
6271
+ function elementLabel(element) {
6272
+ return element.type === "custom" || element.type === "element" ? element.name : element.type;
6273
+ }
6274
+ function collectTextLeaves(nodes, path = [], ancestors = []) {
6275
+ return nodes.flatMap((node, index) => {
6276
+ const currentPath = [...path, index];
6277
+ if (typeof node === "string") return [{ ancestors, path: currentPath, value: node }];
6278
+ if (node.type === "text") return [{ ancestors, path: currentPath, value: node.value }];
6279
+ return collectTextLeaves(node.children ?? [], currentPath, [...ancestors, elementLabel(node)]);
6280
+ });
6281
+ }
6282
+ function updateNodesAtPath(nodes, path, update) {
6283
+ if (path.length === 0) return nodes;
6284
+ const [index, ...rest] = path;
6285
+ return nodes.flatMap((node, nodeIndex) => {
6286
+ if (nodeIndex !== index) return [node];
6287
+ if (rest.length === 0) {
6288
+ const next = update(node);
6289
+ return Array.isArray(next) ? next : [next];
6290
+ }
6291
+ if (!isElement(node)) return [node];
6292
+ return [{ ...node, children: updateNodesAtPath(node.children ?? [], rest, update) }];
6293
+ });
6294
+ }
6295
+ function updateTextAtPath(document2, path, value) {
6296
+ return { ...document2, children: updateNodesAtPath(document2.children ?? [], path, () => value) };
6297
+ }
6298
+ function wrapTextAtPath(document2, path, start, end, tag, attributes) {
6299
+ return {
6300
+ ...document2,
6301
+ children: updateNodesAtPath(document2.children ?? [], path, (node) => {
6302
+ const value = typeof node === "string" ? node : node.type === "text" ? node.value : "";
6303
+ if (!value || start === end) return node;
6304
+ if (tag === "break") {
6305
+ return [
6306
+ value.slice(0, start),
6307
+ { type: tag, attributes, children: [] },
6308
+ value.slice(start)
6309
+ ].filter((part) => typeof part === "string" ? part.length > 0 : true);
6310
+ }
6311
+ const selected = value.slice(start, end);
6312
+ const wrapped = { type: tag, attributes, children: [selected] };
6313
+ return [value.slice(0, start), wrapped, value.slice(end)].filter(
6314
+ (part) => typeof part === "string" ? part.length > 0 : true
6315
+ );
6316
+ })
6317
+ };
6318
+ }
6319
+ function TreeNode({
6320
+ node,
6321
+ path,
6322
+ selectedPath,
6323
+ onSelect
6324
+ }) {
6325
+ if (!isElement(node)) return null;
6326
+ const name = elementLabel(node);
6327
+ const isSelected = selectedPath?.join(".") === path.join(".");
6328
+ return /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement("button", { type: "button", "aria-current": isSelected ? "true" : void 0, onClick: () => onSelect(path) }, `<${name}>`), (node.children ?? []).length > 0 && /* @__PURE__ */ React.createElement("ul", null, node.children?.map((child, index) => /* @__PURE__ */ React.createElement(
6329
+ TreeNode,
6330
+ {
6331
+ key: `${path.join(".")}/${isElement(child) ? elementLabel(child) : JSON.stringify(child)}`,
6332
+ node: child,
6333
+ path: [...path, index],
6334
+ selectedPath,
6335
+ onSelect
6336
+ }
6337
+ ))));
6338
+ }
6339
+ function VisualSsmlEditor({
6340
+ document: document2,
6341
+ readOnly = false,
6342
+ onChange,
6343
+ onPreviewSelection
6344
+ }) {
6345
+ const [selectedPath, setSelectedPath] = (0, import_react3.useState)(null);
6346
+ const [selection, setSelection] = (0, import_react3.useState)({ start: 0, end: 0 });
6347
+ const textLeaves = (0, import_react3.useMemo)(() => collectTextLeaves(document2.children ?? []), [document2]);
6348
+ const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
6349
+ const diagnostics = (0, import_react3.useMemo)(() => validateAzureSsml(buildSsml(document2)), [document2]);
6350
+ const commit = (nextDocument) => onChange?.(nextDocument);
6351
+ const applyWrapper = (tag, attributes) => {
6352
+ if (readOnly || !selectedLeaf) return;
6353
+ const start = selection.start === selection.end ? 0 : selection.start;
6354
+ const end = selection.start === selection.end ? selectedLeaf.value.length : selection.end;
6355
+ commit(wrapTextAtPath(document2, selectedLeaf.path, start, end, tag, attributes));
6356
+ };
6357
+ return /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual", "data-ssml-editor-visual": "" }, /* @__PURE__ */ React.createElement("fieldset", { className: "ssml-editor-visual-breadcrumb" }, /* @__PURE__ */ React.createElement("legend", null, "SSML structure breadcrumb"), /* @__PURE__ */ React.createElement("span", null, "<speak>"), selectedLeaf?.ancestors.map((ancestor, index) => /* @__PURE__ */ React.createElement("span", { key: selectedLeaf?.ancestors.slice(0, index + 1).join("/") }, " / <", ancestor, ">")), selectedPath && /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setSelectedPath(null) }, "Clear parent")), diagnostics.length > 0 && /* @__PURE__ */ React.createElement("div", { role: "alert", "aria-invalid": "true", className: "ssml-editor-visual-errors" }, diagnostics.map((diagnostic) => /* @__PURE__ */ React.createElement("div", { key: `${diagnostic.code}-${diagnostic.message}` }, diagnostic.message))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-layout" }, /* @__PURE__ */ React.createElement("nav", { "aria-label": "SSML structure tree", className: "ssml-editor-visual-tree" }, /* @__PURE__ */ React.createElement("strong", null, "Structure"), /* @__PURE__ */ React.createElement("ul", null, document2.children?.map((node, index) => /* @__PURE__ */ React.createElement(
6358
+ TreeNode,
6359
+ {
6360
+ key: isElement(node) ? elementLabel(node) : JSON.stringify(node),
6361
+ node,
6362
+ path: [index],
6363
+ selectedPath,
6364
+ onSelect: setSelectedPath
6365
+ }
6366
+ )))), /* @__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(
6367
+ "textarea",
6368
+ {
6369
+ value: selectedLeaf.value,
6370
+ readOnly,
6371
+ onSelect: (event) => setSelection({ start: event.currentTarget.selectionStart, end: event.currentTarget.selectionEnd }),
6372
+ onChange: (event) => commit(updateTextAtPath(document2, selectedLeaf.path, event.target.value))
6373
+ }
6374
+ )), /* @__PURE__ */ React.createElement("fieldset", { className: "ssml-editor-visual-actions" }, /* @__PURE__ */ React.createElement("legend", null, "Apply SSML formatting"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("prosody", { rate: "slow" }) }, "Rate"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("prosody", { pitch: "high" }) }, "Pitch"), /* @__PURE__ */ React.createElement(
6375
+ "button",
6376
+ {
6377
+ type: "button",
6378
+ disabled: readOnly,
6379
+ onClick: () => applyWrapper("mstts:express-as", { style: "cheerful" })
6380
+ },
6381
+ "Emotion"
6382
+ ), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("break", { time: "500ms" }) }, "Pause"), /* @__PURE__ */ React.createElement(
6383
+ "button",
6384
+ {
6385
+ type: "button",
6386
+ disabled: readOnly,
6387
+ onClick: () => applyWrapper("phoneme", { alphabet: "ipa", ph: selectedLeaf.value })
6388
+ },
6389
+ "Pronunciation"
6390
+ ), onPreviewSelection && /* @__PURE__ */ React.createElement(
6391
+ "button",
6392
+ {
6393
+ type: "button",
6394
+ onClick: () => onPreviewSelection(buildSsml({ ...document2, children: [selectedLeaf.value] }))
6395
+ },
6396
+ "Preview selection"
6397
+ ))) : /* @__PURE__ */ React.createElement("p", null, "Select an element or text node to edit it."))));
6398
+ }
6399
+
5413
6400
  // packages/ssml-editor-react/src/SsmlEditor.tsx
5414
6401
  var UNGROUPED_TOOLBAR_GROUP = "__ssml-editor-ungrouped__";
5415
6402
  var TIMING_POPOVER_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence", "mstts:audioduration"]);
@@ -5876,6 +6863,60 @@ var STYLE_CSS = `
5876
6863
  > .ssml-editor-help-settings-summary::before {
5877
6864
  content: "\u25BE";
5878
6865
  }
6866
+ [data-ssml-editor] .ssml-editor-visual-layout {
6867
+ display: grid;
6868
+ grid-template-columns: minmax(12rem, 0.35fr) minmax(16rem, 1fr);
6869
+ gap: 1rem;
6870
+ }
6871
+ [data-ssml-editor] .ssml-editor-visual-tree,
6872
+ [data-ssml-editor] .ssml-editor-visual-form {
6873
+ display: grid;
6874
+ gap: 0.5rem;
6875
+ align-content: start;
6876
+ }
6877
+ [data-ssml-editor] .ssml-editor-visual-tree ul {
6878
+ margin: 0;
6879
+ padding-left: 1.25rem;
6880
+ }
6881
+ [data-ssml-editor] .ssml-editor-visual-tree button,
6882
+ [data-ssml-editor] .ssml-editor-visual-breadcrumb button,
6883
+ [data-ssml-editor] .ssml-editor-visual-actions button {
6884
+ padding: 0.35rem 0.5rem;
6885
+ border: 1px solid var(--ssml-editor-control-border);
6886
+ border-radius: 0.25rem;
6887
+ color: var(--ssml-editor-color);
6888
+ background: var(--ssml-editor-control-bg);
6889
+ cursor: pointer;
6890
+ }
6891
+ [data-ssml-editor] .ssml-editor-visual-tree button[aria-current] {
6892
+ border-color: var(--ssml-editor-active-border);
6893
+ background: var(--ssml-editor-active-bg);
6894
+ }
6895
+ [data-ssml-editor] .ssml-editor-visual-breadcrumb,
6896
+ [data-ssml-editor] .ssml-editor-visual-actions {
6897
+ display: flex;
6898
+ flex-wrap: wrap;
6899
+ gap: 0.4rem;
6900
+ align-items: center;
6901
+ }
6902
+ [data-ssml-editor] .ssml-editor-visual-form textarea {
6903
+ box-sizing: border-box;
6904
+ width: 100%;
6905
+ min-height: 6rem;
6906
+ padding: 0.5rem;
6907
+ color: var(--ssml-editor-color);
6908
+ background: var(--ssml-editor-control-bg);
6909
+ border: 1px solid var(--ssml-editor-control-border);
6910
+ border-radius: 0.25rem;
6911
+ font: inherit;
6912
+ }
6913
+ [data-ssml-editor] .ssml-editor-visual-errors {
6914
+ padding: 0.5rem;
6915
+ color: var(--ssml-editor-error);
6916
+ background: var(--ssml-editor-error-bg);
6917
+ border: 1px solid var(--ssml-editor-error);
6918
+ border-radius: 0.25rem;
6919
+ }
5879
6920
  `.trim();
5880
6921
  function injectEditorTheme() {
5881
6922
  if (typeof document !== "undefined" && !document.getElementById(STYLE_ID)) {
@@ -5907,7 +6948,7 @@ function ToolbarDecorationsSwitch({
5907
6948
  /* @__PURE__ */ React.createElement("span", { className: "ssml-editor-switch-thumb", style: editorStyles.toolbarSwitchThumb, "aria-hidden": "true" })
5908
6949
  ));
5909
6950
  }
5910
- var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
6951
+ var SsmlEditor = (0, import_react4.forwardRef)(function SsmlEditor2({
5911
6952
  document: document2,
5912
6953
  onChange,
5913
6954
  onSsmlChange,
@@ -5945,8 +6986,11 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5945
6986
  toolbarStyle,
5946
6987
  displayClassName,
5947
6988
  displayStyle,
5948
- loadingFallback
6989
+ loadingFallback,
6990
+ editMode: editModeProp = "code"
5949
6991
  }, ref) {
6992
+ const [editMode, setEditMode] = (0, import_react4.useState)(editModeProp);
6993
+ (0, import_react4.useEffect)(() => setEditMode(editModeProp), [editModeProp]);
5950
6994
  const resolvedEditorOptions = {
5951
6995
  ...settings,
5952
6996
  ...editorOptions,
@@ -6024,6 +7068,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
6024
7068
  );
6025
7069
  const {
6026
7070
  editorRef,
7071
+ draftDocument,
6027
7072
  text,
6028
7073
  selectionOverlay,
6029
7074
  activeTags,
@@ -6035,6 +7080,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
6035
7080
  syntaxError,
6036
7081
  setSyntaxError,
6037
7082
  helpPanelId,
7083
+ commit,
6038
7084
  handleTextChange,
6039
7085
  handleInsert,
6040
7086
  handleInsertBreak,
@@ -6082,7 +7128,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
6082
7128
  enableCodeLens,
6083
7129
  onOpenPopover: handleCodeLensAction
6084
7130
  });
6085
- (0, import_react3.useImperativeHandle)(
7131
+ (0, import_react4.useImperativeHandle)(
6086
7132
  ref,
6087
7133
  () => ({
6088
7134
  getSelectedSsml: getSelectedSsml2,
@@ -6265,7 +7311,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
6265
7311
  previousRenderedGroupId = groupId;
6266
7312
  hasRenderedToolbarItem = true;
6267
7313
  renderedItems.push(
6268
- /* @__PURE__ */ React.createElement(import_react3.Fragment, { key: id }, showSeparator && /* @__PURE__ */ React.createElement("span", { style: editorStyles.toolbarSeparator, "aria-hidden": "true" }), render())
7314
+ /* @__PURE__ */ React.createElement(import_react4.Fragment, { key: id }, showSeparator && /* @__PURE__ */ React.createElement("span", { style: editorStyles.toolbarSeparator, "aria-hidden": "true" }), render())
6269
7315
  );
6270
7316
  }
6271
7317
  return renderedItems;
@@ -6294,11 +7340,40 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
6294
7340
  "aria-label": copy.toolbarAriaLabel,
6295
7341
  "data-ssml-editor-toolbar-actions": ""
6296
7342
  },
6297
- renderToolbarItems()
7343
+ renderToolbarItems(),
7344
+ /* @__PURE__ */ React.createElement("span", { style: editorStyles.toolbarSeparator, "aria-hidden": "true" }),
7345
+ /* @__PURE__ */ React.createElement(
7346
+ "button",
7347
+ {
7348
+ type: "button",
7349
+ style: editMode === "visual" ? { ...toolbarButtonStyle, ...editorStyles.toolbarButtonActive } : toolbarButtonStyle,
7350
+ "aria-pressed": editMode === "visual",
7351
+ onClick: () => setEditMode("visual")
7352
+ },
7353
+ "Visual"
7354
+ ),
7355
+ /* @__PURE__ */ React.createElement(
7356
+ "button",
7357
+ {
7358
+ type: "button",
7359
+ style: editMode === "code" ? { ...toolbarButtonStyle, ...editorStyles.toolbarButtonActive } : toolbarButtonStyle,
7360
+ "aria-pressed": editMode === "code",
7361
+ onClick: () => setEditMode("code")
7362
+ },
7363
+ "Code"
7364
+ )
6298
7365
  )
6299
7366
  ),
6300
- /* @__PURE__ */ React.createElement("div", { className: displayClassName, style: { ...editorStyles.display, ...displayStyle }, "data-ssml-editor-display": "" }, isHelpOpen && isSsmlEditorButtonVisible(buttonVisibility, "help") && /* @__PURE__ */ React.createElement("section", { id: helpPanelId, style: editorStyles.helpPanel, "aria-label": copy.helpHeading }, /* @__PURE__ */ React.createElement("h3", { style: editorStyles.helpHeading }, copy.helpHeading), /* @__PURE__ */ React.createElement("p", { style: editorStyles.helpDescription }, copy.helpDescription), helpInsertions.length > 0 && /* @__PURE__ */ React.createElement("ul", { style: editorStyles.helpList }, helpInsertions.map((insertion) => /* @__PURE__ */ React.createElement("li", { key: insertion.id, style: editorStyles.helpItem }, /* @__PURE__ */ React.createElement("details", { className: "ssml-editor-help-settings-accordion", style: editorStyles.helpSettingsAccordion }, /* @__PURE__ */ React.createElement("summary", { className: "ssml-editor-help-settings-summary", style: editorStyles.helpSettingsSummary }, /* @__PURE__ */ React.createElement("span", { style: editorStyles.helpIcon, "aria-hidden": "true" }, insertion.icon), /* @__PURE__ */ React.createElement("span", { style: editorStyles.helpSettingsSummaryContent }, /* @__PURE__ */ React.createElement("strong", null, insertion.labels[language]), " ", insertion.tagName && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("code", null, `<${insertion.tagName}${insertion.selfClosing ? "/>" : ">"}`), " \u2014", " "), insertion.descriptions[language])), /* @__PURE__ */ React.createElement("p", { style: editorStyles.helpSettingsDescription }, /* @__PURE__ */ React.createElement("strong", null, copy.parameters, ":"), " ", insertion.parameterDescription[language]), /* @__PURE__ */ React.createElement("ul", { style: editorStyles.helpSettingsList }, insertion.options.map((option) => /* @__PURE__ */ React.createElement("li", { key: option.value }, /* @__PURE__ */ React.createElement("strong", null, option.labels[language]), option.descriptions && /* @__PURE__ */ React.createElement(React.Fragment, null, " \u2014 ", option.descriptions[language]))))))))), /* @__PURE__ */ React.createElement("div", { style: { ...editorStyles.editor, minHeight: editorMinHeight } }, /* @__PURE__ */ React.createElement(
6301
- import_react4.default,
7367
+ /* @__PURE__ */ React.createElement("div", { className: displayClassName, style: { ...editorStyles.display, ...displayStyle }, "data-ssml-editor-display": "" }, isHelpOpen && isSsmlEditorButtonVisible(buttonVisibility, "help") && /* @__PURE__ */ React.createElement("section", { id: helpPanelId, style: editorStyles.helpPanel, "aria-label": copy.helpHeading }, /* @__PURE__ */ React.createElement("h3", { style: editorStyles.helpHeading }, copy.helpHeading), /* @__PURE__ */ React.createElement("p", { style: editorStyles.helpDescription }, copy.helpDescription), helpInsertions.length > 0 && /* @__PURE__ */ React.createElement("ul", { style: editorStyles.helpList }, helpInsertions.map((insertion) => /* @__PURE__ */ React.createElement("li", { key: insertion.id, style: editorStyles.helpItem }, /* @__PURE__ */ React.createElement("details", { className: "ssml-editor-help-settings-accordion", style: editorStyles.helpSettingsAccordion }, /* @__PURE__ */ React.createElement("summary", { className: "ssml-editor-help-settings-summary", style: editorStyles.helpSettingsSummary }, /* @__PURE__ */ React.createElement("span", { style: editorStyles.helpIcon, "aria-hidden": "true" }, insertion.icon), /* @__PURE__ */ React.createElement("span", { style: editorStyles.helpSettingsSummaryContent }, /* @__PURE__ */ React.createElement("strong", null, insertion.labels[language]), " ", insertion.tagName && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("code", null, `<${insertion.tagName}${insertion.selfClosing ? "/>" : ">"}`), " \u2014", " "), insertion.descriptions[language])), /* @__PURE__ */ React.createElement("p", { style: editorStyles.helpSettingsDescription }, /* @__PURE__ */ React.createElement("strong", null, copy.parameters, ":"), " ", insertion.parameterDescription[language]), /* @__PURE__ */ React.createElement("ul", { style: editorStyles.helpSettingsList }, insertion.options.map((option) => /* @__PURE__ */ React.createElement("li", { key: option.value }, /* @__PURE__ */ React.createElement("strong", null, option.labels[language]), option.descriptions && /* @__PURE__ */ React.createElement(React.Fragment, null, " \u2014 ", option.descriptions[language]))))))))), editMode === "visual" ? /* @__PURE__ */ React.createElement("div", { style: editorStyles.visual }, /* @__PURE__ */ React.createElement(
7368
+ VisualSsmlEditor,
7369
+ {
7370
+ document: draftDocument,
7371
+ readOnly: isReadOnly,
7372
+ onChange: commit,
7373
+ onPreviewSelection
7374
+ }
7375
+ )) : /* @__PURE__ */ React.createElement("div", { style: { ...editorStyles.editor, minHeight: editorMinHeight } }, /* @__PURE__ */ React.createElement(
7376
+ import_react5.default,
6302
7377
  {
6303
7378
  height: editorHeight,
6304
7379
  language: "xml",
@@ -6359,6 +7434,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
6359
7434
  SSML_HOVER_COPY,
6360
7435
  SSML_INSERTIONS,
6361
7436
  SsmlEditor,
7437
+ VisualSsmlEditor,
6362
7438
  createSsmlEditorInsertionDefinition,
6363
7439
  registerSsmlCodeLens,
6364
7440
  updateTagAttribute