@ssml-builder-js/ssml-editor-react 2.8.0 → 2.9.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/index.js CHANGED
@@ -36,6 +36,7 @@ __export(index_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(index_exports, {
43
44
  module.exports = __toCommonJS(index_exports);
44
45
 
45
46
  // 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
  // src/buttonVisibility.ts
50
51
  function isSsmlEditorButtonVisible(buttonVisibility, button) {
@@ -54,6 +55,8 @@ function isSsmlEditorButtonVisible(buttonVisibility, button) {
54
55
  // src/constants/ssmlPresets.ts
55
56
  var ssmlPresets_exports = {};
56
57
  __export(ssmlPresets_exports, {
58
+ AUDIO_DURATION_DESCRIPTIONS: () => AUDIO_DURATION_DESCRIPTIONS,
59
+ AUDIO_DURATION_PRESETS: () => AUDIO_DURATION_PRESETS,
57
60
  BREAK_STRENGTH_PRESETS: () => BREAK_STRENGTH_PRESETS,
58
61
  BREAK_TIME_DESCRIPTIONS: () => BREAK_TIME_DESCRIPTIONS,
59
62
  BREAK_TIME_PRESETS: () => BREAK_TIME_PRESETS,
@@ -86,6 +89,103 @@ __export(ssmlPresets_exports, {
86
89
  getExpressAsStyleCategory: () => getExpressAsStyleCategory,
87
90
  resolveExpressAsStyles: () => resolveExpressAsStyles
88
91
  });
92
+
93
+ // src/constants/azureVoiceStyleMap.generated.ts
94
+ var AZURE_VOICE_STYLE_MAP = {
95
+ "de-DE-ConradNeural": ["cheerful", "sad"],
96
+ "de-DE-KatjaNeural": ["cheerful", "sad"],
97
+ "en-US-AndrewNeural": ["empathetic", "relieved"],
98
+ "en-US-GuyNeural": [
99
+ "angry",
100
+ "cheerful",
101
+ "excited",
102
+ "friendly",
103
+ "hopeful",
104
+ "newscast",
105
+ "sad",
106
+ "shouting",
107
+ "terrified",
108
+ "unfriendly",
109
+ "whispering"
110
+ ],
111
+ "en-US-JennyMultilingualNeural": [
112
+ "cheerful",
113
+ "empathetic",
114
+ "excited",
115
+ "friendly",
116
+ "hopeful",
117
+ "sad",
118
+ "shouting",
119
+ "terrified",
120
+ "unfriendly",
121
+ "whispering"
122
+ ],
123
+ "en-US-JennyNeural": [
124
+ "assistant",
125
+ "chat",
126
+ "customerservice",
127
+ "newscast",
128
+ "cheerful",
129
+ "empathetic",
130
+ "excited",
131
+ "friendly",
132
+ "hopeful",
133
+ "sad",
134
+ "shouting",
135
+ "terrified",
136
+ "unfriendly",
137
+ "whispering"
138
+ ],
139
+ "es-ES-ElviraNeural": [],
140
+ "fil-PH-AngeloNeural": [],
141
+ "fr-FR-DeniseNeural": ["cheerful", "sad"],
142
+ "fr-FR-HenriNeural": ["cheerful", "sad"],
143
+ "id-ID-GadisNeural": [],
144
+ "it-IT-ElsaNeural": ["cheerful", "sad"],
145
+ "ja-JP-KeitaNeural": ["chat"],
146
+ "ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
147
+ "ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
148
+ "ko-KR-SunHiNeural": ["cheerful", "sad"],
149
+ "ms-MY-YasminNeural": [],
150
+ "pt-BR-FranciscaNeural": ["calm"],
151
+ "ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"],
152
+ "th-TH-PremwadeeNeural": [],
153
+ "vi-VN-HoaiMyNeural": [],
154
+ "zh-CN-XiaoxiaoNeural": [
155
+ "assistant",
156
+ "chat",
157
+ "customerservice",
158
+ "newscast",
159
+ "cheerful",
160
+ "empathetic",
161
+ "excited",
162
+ "friendly",
163
+ "hopeful",
164
+ "sad",
165
+ "terrified",
166
+ "whispering",
167
+ "poetry-reading",
168
+ "sports_commentary",
169
+ "sports_commentary_excited",
170
+ "story"
171
+ ],
172
+ "zh-CN-YunxiNeural": [
173
+ "narration-relaxed",
174
+ "embarrassed",
175
+ "fearful",
176
+ "sad",
177
+ "disgruntled",
178
+ "serious",
179
+ "angry",
180
+ "depressed",
181
+ "chat",
182
+ "cheerful",
183
+ "assistant"
184
+ ],
185
+ "zh-TW-HsiaoChenNeural": []
186
+ };
187
+
188
+ // src/constants/ssmlPresets.ts
89
189
  var SSML_PRESETS = [
90
190
  {
91
191
  id: "basic",
@@ -185,7 +285,7 @@ function getExpressAsStyleCategory(style) {
185
285
  }
186
286
  return "other";
187
287
  }
188
- var VOICE_STYLE_MAP = {
288
+ var LEGACY_VOICE_STYLE_MAP = {
189
289
  "ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
190
290
  "ja-JP-KeitaNeural": ["chat"],
191
291
  "ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
@@ -271,6 +371,7 @@ var VOICE_STYLE_MAP = {
271
371
  "de-DE-ConradNeural": ["cheerful", "sad"],
272
372
  "ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
273
373
  };
374
+ var VOICE_STYLE_MAP = Object.keys(AZURE_VOICE_STYLE_MAP).length > 0 ? AZURE_VOICE_STYLE_MAP : LEGACY_VOICE_STYLE_MAP;
274
375
  var VOICE_STYLE_MAP_BY_NORMALIZED_NAME = new Map(
275
376
  Object.entries(VOICE_STYLE_MAP).map(([voiceName, styles]) => [voiceName.toLowerCase(), styles])
276
377
  );
@@ -313,6 +414,7 @@ var SAY_AS_PRESETS = [
313
414
  ];
314
415
  var LANGUAGE_PRESETS = ["ja-JP", "en-US", "de-DE", "fr-FR"];
315
416
  var SILENCE_VALUE_PRESETS = ["300ms", "500ms", "1s"];
417
+ var AUDIO_DURATION_PRESETS = ["5s", "10s", "30s"];
316
418
  var SILENCE_TYPE_PRESETS = [
317
419
  "Leading",
318
420
  "Tailing",
@@ -321,6 +423,11 @@ var SILENCE_TYPE_PRESETS = [
321
423
  "Semicolon",
322
424
  "Enumerationcomma"
323
425
  ];
426
+ var AUDIO_DURATION_DESCRIPTIONS = {
427
+ "5s": { ja: "5\u79D2", en: "5 seconds" },
428
+ "10s": { ja: "10\u79D2", en: "10 seconds" },
429
+ "30s": { ja: "30\u79D2", en: "30 seconds" }
430
+ };
324
431
  var PHONEME_ALPHABET_PRESETS = ["ipa", "sapi", "ups", "x-sampa"];
325
432
  var VISEME_TYPE_PRESETS = ["redlips_front", "FacialExpression"];
326
433
  var SSML_ATTRIBUTE_PRESETS = {
@@ -364,6 +471,14 @@ var SSML_ATTRIBUTE_PRESETS = {
364
471
  type: SILENCE_TYPE_PRESETS,
365
472
  value: SILENCE_VALUE_PRESETS
366
473
  },
474
+ "mstts:audioduration": {
475
+ value: AUDIO_DURATION_PRESETS
476
+ },
477
+ "mstts:backgroundaudio": {
478
+ volume: PROSODY_VOLUME_PRESETS,
479
+ fadein: AUDIO_DURATION_PRESETS,
480
+ fadeout: AUDIO_DURATION_PRESETS
481
+ },
367
482
  silence: {
368
483
  type: SILENCE_TYPE_PRESETS,
369
484
  value: SILENCE_VALUE_PRESETS
@@ -910,6 +1025,13 @@ var SSML_HOVER_COPY = {
910
1025
  parameters: {
911
1026
  type: { title: "type", description: "\u30D3\u30BC\u30FC\u30E0\u30A4\u30D9\u30F3\u30C8\u306E\u5F62\u5F0F\u3002" }
912
1027
  }
1028
+ },
1029
+ "mstts:audioduration": {
1030
+ title: "\u97F3\u58F0\u9577",
1031
+ description: "\u5408\u6210\u97F3\u58F0\u306E\u76EE\u6A19\u6642\u9593\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
1032
+ parameters: {
1033
+ value: { title: "value", description: "\u76EE\u6A19\u6642\u9593\u3002\u4F8B: `10s`\u3001`5000ms`\u3001`00:00:10`\u3002" }
1034
+ }
913
1035
  }
914
1036
  }
915
1037
  },
@@ -1067,6 +1189,13 @@ var SSML_HOVER_COPY = {
1067
1189
  parameters: {
1068
1190
  type: { title: "type", description: "The viseme event format." }
1069
1191
  }
1192
+ },
1193
+ "mstts:audioduration": {
1194
+ title: "Audio duration",
1195
+ description: "Sets the target duration of synthesized audio.",
1196
+ parameters: {
1197
+ value: { title: "value", description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`." }
1198
+ }
1070
1199
  }
1071
1200
  }
1072
1201
  }
@@ -1166,6 +1295,14 @@ var editorStyles = {
1166
1295
  display: "grid",
1167
1296
  gap: "0.5rem"
1168
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
+ },
1169
1306
  toolbarIconOnly: {
1170
1307
  justifyContent: "center",
1171
1308
  minWidth: "2.25rem",
@@ -1604,6 +1741,7 @@ var INTRINSICALLY_EMPTY_ELEMENTS = /* @__PURE__ */ new Set([
1604
1741
  "lexicon",
1605
1742
  "mark",
1606
1743
  "mstts:silence",
1744
+ "mstts:audioduration",
1607
1745
  "mstts:viseme",
1608
1746
  "silence",
1609
1747
  "viseme"
@@ -2450,7 +2588,7 @@ function findSsmlVoiceContext(source, offset) {
2450
2588
  }
2451
2589
 
2452
2590
  // src/hooks/useSsmlEditorState.ts
2453
- var TIMING_INSERTION_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence"]);
2591
+ var TIMING_INSERTION_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence", "mstts:audioduration"]);
2454
2592
  var PROSODY_INSERTION_TAGS = /* @__PURE__ */ new Set(["prosody", "mstts:express-as", "voice", "emphasis"]);
2455
2593
  var TEXT_INSERTION_TAGS = /* @__PURE__ */ new Set(["sub", "say-as", "phoneme", "w", "lang"]);
2456
2594
  var EMPTY_SELECTION_OVERLAY = {
@@ -3172,6 +3310,28 @@ var SSML_COMPLETION_SNIPPETS = [
3172
3310
  label: "mstts:express-as",
3173
3311
  insertText: `<mstts:express-as style="cheerful">\${1:text}</mstts:express-as>`
3174
3312
  },
3313
+ {
3314
+ label: "mstts:audioduration",
3315
+ insertText: '<mstts:audioduration value="10s" />'
3316
+ },
3317
+ {
3318
+ label: "mstts:dialog",
3319
+ insertText: `<mstts:dialog>
3320
+ <mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>
3321
+ </mstts:dialog>`
3322
+ },
3323
+ {
3324
+ label: "mstts:turn",
3325
+ insertText: `<mstts:turn voice="\${1:en-US-JennyNeural}">\${2:text}</mstts:turn>`
3326
+ },
3327
+ {
3328
+ label: "mstts:backgroundaudio",
3329
+ insertText: `<mstts:backgroundaudio src="\${1:https://example.com/audio.mp3}" volume="\${2:-3dB}" />`
3330
+ },
3331
+ {
3332
+ label: "mstts:ttsembedding",
3333
+ insertText: `<mstts:ttsembedding>\${1:text}</mstts:ttsembedding>`
3334
+ },
3175
3335
  {
3176
3336
  label: "sub",
3177
3337
  insertText: `<sub alias="\${1:\u8AAD\u307F}">\${2:\u6F22\u5B57}</sub>`
@@ -3447,7 +3607,7 @@ function registerSsmlCodeLens(monaco, editor, onOpenPopover) {
3447
3607
  break;
3448
3608
  }
3449
3609
  const tag = source.slice(tagStart, tagEnd + 1);
3450
- const tagName = tag.match(/^<\s*(prosody|break)\b/i)?.[1]?.toLowerCase();
3610
+ const tagName = tag.match(/^<\s*(prosody|break|mstts:audioduration)\b/i)?.[1]?.toLowerCase();
3451
3611
  index = tagEnd + 1;
3452
3612
  if (!tagName) {
3453
3613
  continue;
@@ -3477,7 +3637,22 @@ function registerSsmlCodeLens(monaco, editor, onOpenPopover) {
3477
3637
  elementRange
3478
3638
  })
3479
3639
  );
3480
- } else if (/\/\s*>$/.test(tag)) {
3640
+ } else if (tagName === "mstts:audioduration" && /\/\s*>$/.test(tag)) {
3641
+ lenses.push(
3642
+ createLens(
3643
+ model,
3644
+ tagStart,
3645
+ tagEnd + 1,
3646
+ `\u26A1 Duration: ${getAttributeValue(tag, "value") ?? "default"} (Click to edit)`,
3647
+ { type: "attribute", insertionId: "mstts:audioduration", attributeName: "value", tagRange }
3648
+ ),
3649
+ createLens(model, tagStart, tagEnd + 1, "Delete", {
3650
+ type: "delete",
3651
+ tagRange,
3652
+ elementRange
3653
+ })
3654
+ );
3655
+ } else if (tagName === "break" && /\/\s*>$/.test(tag)) {
3481
3656
  lenses.push(
3482
3657
  createLens(
3483
3658
  model,
@@ -3813,6 +3988,74 @@ var SSML_TAG_DEFINITIONS = [
3813
3988
  values: VISEME_TYPE_PRESETS2
3814
3989
  }
3815
3990
  ]
3991
+ },
3992
+ {
3993
+ name: "mstts:audioduration",
3994
+ description: "Sets the target duration of synthesized audio.",
3995
+ parameters: [
3996
+ {
3997
+ name: "value",
3998
+ description: "The target duration, such as `10s`, `5000ms`, or `00:00:10`.",
3999
+ example: "10s"
4000
+ }
4001
+ ]
4002
+ },
4003
+ {
4004
+ name: "mstts:dialog",
4005
+ description: "Groups multiple Azure dialog turns that can use different voices.",
4006
+ parameters: []
4007
+ },
4008
+ {
4009
+ name: "mstts:turn",
4010
+ description: "Adds one dialog turn using the required Azure voice name.",
4011
+ parameters: [
4012
+ {
4013
+ name: "voice",
4014
+ description: "The Azure voice used for this turn, such as `en-US-JennyNeural`.",
4015
+ example: "en-US-JennyNeural"
4016
+ }
4017
+ ]
4018
+ },
4019
+ {
4020
+ name: "mstts:backgroundaudio",
4021
+ description: "Plays background audio while speech is synthesized.",
4022
+ parameters: [
4023
+ {
4024
+ name: "src",
4025
+ description: "An absolute HTTP(S) URL for the background audio file.",
4026
+ example: "https://example.com/music.mp3"
4027
+ },
4028
+ {
4029
+ name: "volume",
4030
+ description: "The background audio volume, for example `-3dB` or `medium`.",
4031
+ example: "-3dB"
4032
+ },
4033
+ {
4034
+ name: "fadein",
4035
+ description: "The fade-in duration, for example `1s`.",
4036
+ example: "1s"
4037
+ },
4038
+ {
4039
+ name: "fadeout",
4040
+ description: "The fade-out duration, for example `500ms`.",
4041
+ example: "500ms"
4042
+ }
4043
+ ]
4044
+ },
4045
+ {
4046
+ name: "mstts:ttsembedding",
4047
+ description: "Embeds custom voice or speaker profile metadata for Azure Speech.",
4048
+ parameters: []
4049
+ },
4050
+ {
4051
+ name: "mstts:embedding",
4052
+ description: "Specifies embedding metadata for custom voice scenarios.",
4053
+ parameters: []
4054
+ },
4055
+ {
4056
+ name: "mstts:voiceconversion",
4057
+ description: "Specifies voice conversion metadata for custom voice scenarios.",
4058
+ parameters: []
3816
4059
  }
3817
4060
  ];
3818
4061
  var definitionsByName = /* @__PURE__ */ new Map();
@@ -4417,10 +4660,182 @@ function useSsmlMonaco({
4417
4660
  return { editorRef, onMount };
4418
4661
  }
4419
4662
 
4420
- // src/SsmlEditor.tsx
4663
+ // src/components/VisualSsmlEditor.tsx
4664
+ var import_react3 = require("react");
4665
+ var import_ssml_core4 = require("@ssml-builder-js/ssml-core");
4421
4666
  var import_jsx_runtime6 = require("react/jsx-runtime");
4667
+ function isElement(node) {
4668
+ return typeof node !== "string" && node.type !== "text";
4669
+ }
4670
+ function elementLabel(element) {
4671
+ return element.type === "custom" || element.type === "element" ? element.name : element.type;
4672
+ }
4673
+ function collectTextLeaves(nodes, path = [], ancestors = []) {
4674
+ return nodes.flatMap((node, index) => {
4675
+ const currentPath = [...path, index];
4676
+ if (typeof node === "string") return [{ ancestors, path: currentPath, value: node }];
4677
+ if (node.type === "text") return [{ ancestors, path: currentPath, value: node.value }];
4678
+ return collectTextLeaves(node.children ?? [], currentPath, [...ancestors, elementLabel(node)]);
4679
+ });
4680
+ }
4681
+ function updateNodesAtPath(nodes, path, update) {
4682
+ if (path.length === 0) return nodes;
4683
+ const [index, ...rest] = path;
4684
+ return nodes.flatMap((node, nodeIndex) => {
4685
+ if (nodeIndex !== index) return [node];
4686
+ if (rest.length === 0) {
4687
+ const next = update(node);
4688
+ return Array.isArray(next) ? next : [next];
4689
+ }
4690
+ if (!isElement(node)) return [node];
4691
+ return [{ ...node, children: updateNodesAtPath(node.children ?? [], rest, update) }];
4692
+ });
4693
+ }
4694
+ function updateTextAtPath(document2, path, value) {
4695
+ return { ...document2, children: updateNodesAtPath(document2.children ?? [], path, () => value) };
4696
+ }
4697
+ function wrapTextAtPath(document2, path, start, end, tag, attributes) {
4698
+ return {
4699
+ ...document2,
4700
+ children: updateNodesAtPath(document2.children ?? [], path, (node) => {
4701
+ const value = typeof node === "string" ? node : node.type === "text" ? node.value : "";
4702
+ if (!value || start === end) return node;
4703
+ if (tag === "break") {
4704
+ return [
4705
+ value.slice(0, start),
4706
+ { type: tag, attributes, children: [] },
4707
+ value.slice(start)
4708
+ ].filter((part) => typeof part === "string" ? part.length > 0 : true);
4709
+ }
4710
+ const selected = value.slice(start, end);
4711
+ const wrapped = { type: tag, attributes, children: [selected] };
4712
+ return [value.slice(0, start), wrapped, value.slice(end)].filter(
4713
+ (part) => typeof part === "string" ? part.length > 0 : true
4714
+ );
4715
+ })
4716
+ };
4717
+ }
4718
+ function TreeNode({
4719
+ node,
4720
+ path,
4721
+ selectedPath,
4722
+ onSelect
4723
+ }) {
4724
+ if (!isElement(node)) return null;
4725
+ const name = elementLabel(node);
4726
+ const isSelected = selectedPath?.join(".") === path.join(".");
4727
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { children: [
4728
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", "aria-current": isSelected ? "true" : void 0, onClick: () => onSelect(path), children: `<${name}>` }),
4729
+ (node.children ?? []).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { children: node.children?.map((child, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4730
+ TreeNode,
4731
+ {
4732
+ node: child,
4733
+ path: [...path, index],
4734
+ selectedPath,
4735
+ onSelect
4736
+ },
4737
+ `${path.join(".")}/${isElement(child) ? elementLabel(child) : JSON.stringify(child)}`
4738
+ )) })
4739
+ ] });
4740
+ }
4741
+ function VisualSsmlEditor({
4742
+ document: document2,
4743
+ readOnly = false,
4744
+ onChange,
4745
+ onPreviewSelection
4746
+ }) {
4747
+ const [selectedPath, setSelectedPath] = (0, import_react3.useState)(null);
4748
+ const [selection, setSelection] = (0, import_react3.useState)({ start: 0, end: 0 });
4749
+ const textLeaves = (0, import_react3.useMemo)(() => collectTextLeaves(document2.children ?? []), [document2]);
4750
+ const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
4751
+ const diagnostics = (0, import_react3.useMemo)(() => (0, import_ssml_core4.validateAzureSsml)((0, import_ssml_core4.buildSsml)(document2)), [document2]);
4752
+ const commit = (nextDocument) => onChange?.(nextDocument);
4753
+ const applyWrapper = (tag, attributes) => {
4754
+ if (readOnly || !selectedLeaf) return;
4755
+ const start = selection.start === selection.end ? 0 : selection.start;
4756
+ const end = selection.start === selection.end ? selectedLeaf.value.length : selection.end;
4757
+ commit(wrapTextAtPath(document2, selectedLeaf.path, start, end, tag, attributes));
4758
+ };
4759
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ssml-editor-visual", "data-ssml-editor-visual": "", children: [
4760
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("fieldset", { className: "ssml-editor-visual-breadcrumb", children: [
4761
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("legend", { children: "SSML structure breadcrumb" }),
4762
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "<speak>" }),
4763
+ selectedLeaf?.ancestors.map((ancestor, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { children: [
4764
+ " / <",
4765
+ ancestor,
4766
+ ">"
4767
+ ] }, selectedLeaf?.ancestors.slice(0, index + 1).join("/"))),
4768
+ selectedPath && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", onClick: () => setSelectedPath(null), children: "Clear parent" })
4769
+ ] }),
4770
+ diagnostics.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { role: "alert", "aria-invalid": "true", className: "ssml-editor-visual-errors", children: diagnostics.map((diagnostic) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { children: diagnostic.message }, `${diagnostic.code}-${diagnostic.message}`)) }),
4771
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "ssml-editor-visual-layout", children: [
4772
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("nav", { "aria-label": "SSML structure tree", className: "ssml-editor-visual-tree", children: [
4773
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: "Structure" }),
4774
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { children: document2.children?.map((node, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4775
+ TreeNode,
4776
+ {
4777
+ node,
4778
+ path: [index],
4779
+ selectedPath,
4780
+ onSelect: setSelectedPath
4781
+ },
4782
+ isElement(node) ? elementLabel(node) : JSON.stringify(node)
4783
+ )) })
4784
+ ] }),
4785
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "ssml-editor-visual-form", children: selectedLeaf ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
4786
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { children: [
4787
+ "Text",
4788
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4789
+ "textarea",
4790
+ {
4791
+ value: selectedLeaf.value,
4792
+ readOnly,
4793
+ onSelect: (event) => setSelection({ start: event.currentTarget.selectionStart, end: event.currentTarget.selectionEnd }),
4794
+ onChange: (event) => commit(updateTextAtPath(document2, selectedLeaf.path, event.target.value))
4795
+ }
4796
+ )
4797
+ ] }),
4798
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("fieldset", { className: "ssml-editor-visual-actions", children: [
4799
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("legend", { children: "Apply SSML formatting" }),
4800
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("prosody", { rate: "slow" }), children: "Rate" }),
4801
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("prosody", { pitch: "high" }), children: "Pitch" }),
4802
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4803
+ "button",
4804
+ {
4805
+ type: "button",
4806
+ disabled: readOnly,
4807
+ onClick: () => applyWrapper("mstts:express-as", { style: "cheerful" }),
4808
+ children: "Emotion"
4809
+ }
4810
+ ),
4811
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("break", { time: "500ms" }), children: "Pause" }),
4812
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4813
+ "button",
4814
+ {
4815
+ type: "button",
4816
+ disabled: readOnly,
4817
+ onClick: () => applyWrapper("phoneme", { alphabet: "ipa", ph: selectedLeaf.value }),
4818
+ children: "Pronunciation"
4819
+ }
4820
+ ),
4821
+ onPreviewSelection && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
4822
+ "button",
4823
+ {
4824
+ type: "button",
4825
+ onClick: () => onPreviewSelection((0, import_ssml_core4.buildSsml)({ ...document2, children: [selectedLeaf.value] })),
4826
+ children: "Preview selection"
4827
+ }
4828
+ )
4829
+ ] })
4830
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { children: "Select an element or text node to edit it." }) })
4831
+ ] })
4832
+ ] });
4833
+ }
4834
+
4835
+ // src/SsmlEditor.tsx
4836
+ var import_jsx_runtime7 = require("react/jsx-runtime");
4422
4837
  var UNGROUPED_TOOLBAR_GROUP = "__ssml-editor-ungrouped__";
4423
- var TIMING_POPOVER_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence"]);
4838
+ var TIMING_POPOVER_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence", "mstts:audioduration"]);
4424
4839
  var PROSODY_POPOVER_TAGS = /* @__PURE__ */ new Set(["prosody", "mstts:express-as", "voice", "emphasis"]);
4425
4840
  var TEXT_POPOVER_TAGS = /* @__PURE__ */ new Set(["sub", "say-as", "phoneme", "w", "lang"]);
4426
4841
  function localizedText(value) {
@@ -4648,13 +5063,34 @@ var SSML_INSERTIONS = [
4648
5063
  suffix: "",
4649
5064
  mode: "insert"
4650
5065
  })
5066
+ },
5067
+ {
5068
+ id: "mstts:audioduration",
5069
+ icon: "\u25F7",
5070
+ tagName: "mstts:audioduration",
5071
+ selfClosing: true,
5072
+ labels: { ja: "\u97F3\u58F0\u9577", en: "Audio duration" },
5073
+ descriptions: {
5074
+ ja: "\u5408\u6210\u97F3\u58F0\u306E\u76EE\u6A19\u6642\u9593\u3092\u6307\u5B9A\u3057\u307E\u3059\u3002",
5075
+ en: "Sets the target duration of synthesized audio."
5076
+ },
5077
+ parameterDescription: {
5078
+ ja: "\u76EE\u6A19\u6642\u9593\u3092\u9078\u629E\u3057\u307E\u3059\u3002",
5079
+ en: "Selects the target duration."
5080
+ },
5081
+ options: createInsertionOptions(AUDIO_DURATION_PRESETS, AUDIO_DURATION_DESCRIPTIONS),
5082
+ createTemplate: (value) => ({
5083
+ prefix: `<mstts:audioduration value="${value}"/>`,
5084
+ suffix: "",
5085
+ mode: "insert"
5086
+ })
4651
5087
  }
4652
5088
  ];
4653
5089
  var DEFAULT_INSERTION_GROUPS = [
4654
5090
  {
4655
5091
  id: "pauses",
4656
5092
  labels: { ja: "\u9593\u30FB\u7121\u97F3", en: "Pauses" },
4657
- insertionIds: ["break", "mstts:silence"]
5093
+ insertionIds: ["break", "mstts:silence", "mstts:audioduration"]
4658
5094
  },
4659
5095
  {
4660
5096
  id: "prosody",
@@ -4863,6 +5299,60 @@ var STYLE_CSS = `
4863
5299
  > .ssml-editor-help-settings-summary::before {
4864
5300
  content: "\u25BE";
4865
5301
  }
5302
+ [data-ssml-editor] .ssml-editor-visual-layout {
5303
+ display: grid;
5304
+ grid-template-columns: minmax(12rem, 0.35fr) minmax(16rem, 1fr);
5305
+ gap: 1rem;
5306
+ }
5307
+ [data-ssml-editor] .ssml-editor-visual-tree,
5308
+ [data-ssml-editor] .ssml-editor-visual-form {
5309
+ display: grid;
5310
+ gap: 0.5rem;
5311
+ align-content: start;
5312
+ }
5313
+ [data-ssml-editor] .ssml-editor-visual-tree ul {
5314
+ margin: 0;
5315
+ padding-left: 1.25rem;
5316
+ }
5317
+ [data-ssml-editor] .ssml-editor-visual-tree button,
5318
+ [data-ssml-editor] .ssml-editor-visual-breadcrumb button,
5319
+ [data-ssml-editor] .ssml-editor-visual-actions button {
5320
+ padding: 0.35rem 0.5rem;
5321
+ border: 1px solid var(--ssml-editor-control-border);
5322
+ border-radius: 0.25rem;
5323
+ color: var(--ssml-editor-color);
5324
+ background: var(--ssml-editor-control-bg);
5325
+ cursor: pointer;
5326
+ }
5327
+ [data-ssml-editor] .ssml-editor-visual-tree button[aria-current] {
5328
+ border-color: var(--ssml-editor-active-border);
5329
+ background: var(--ssml-editor-active-bg);
5330
+ }
5331
+ [data-ssml-editor] .ssml-editor-visual-breadcrumb,
5332
+ [data-ssml-editor] .ssml-editor-visual-actions {
5333
+ display: flex;
5334
+ flex-wrap: wrap;
5335
+ gap: 0.4rem;
5336
+ align-items: center;
5337
+ }
5338
+ [data-ssml-editor] .ssml-editor-visual-form textarea {
5339
+ box-sizing: border-box;
5340
+ width: 100%;
5341
+ min-height: 6rem;
5342
+ padding: 0.5rem;
5343
+ color: var(--ssml-editor-color);
5344
+ background: var(--ssml-editor-control-bg);
5345
+ border: 1px solid var(--ssml-editor-control-border);
5346
+ border-radius: 0.25rem;
5347
+ font: inherit;
5348
+ }
5349
+ [data-ssml-editor] .ssml-editor-visual-errors {
5350
+ padding: 0.5rem;
5351
+ color: var(--ssml-editor-error);
5352
+ background: var(--ssml-editor-error-bg);
5353
+ border: 1px solid var(--ssml-editor-error);
5354
+ border-radius: 0.25rem;
5355
+ }
4866
5356
  `.trim();
4867
5357
  function injectEditorTheme() {
4868
5358
  if (typeof document !== "undefined" && !document.getElementById(STYLE_ID)) {
@@ -4879,10 +5369,10 @@ function ToolbarDecorationsSwitch({
4879
5369
  decorationsVisible,
4880
5370
  onVisibilityChange
4881
5371
  }) {
4882
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: editorStyles.toolbarSwitch, children: [
4883
- showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "\u2606" }),
4884
- showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: copy.decorations }),
4885
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5372
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: editorStyles.toolbarSwitch, children: [
5373
+ showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "\u2606" }),
5374
+ showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: copy.decorations }),
5375
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
4886
5376
  "button",
4887
5377
  {
4888
5378
  type: "button",
@@ -4893,12 +5383,12 @@ function ToolbarDecorationsSwitch({
4893
5383
  "aria-label": copy.decorations,
4894
5384
  title: decorationsVisible ? copy.decorationsHideTitle : copy.decorationsShowTitle,
4895
5385
  onClick: () => onVisibilityChange(!decorationsVisible),
4896
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "ssml-editor-switch-thumb", style: editorStyles.toolbarSwitchThumb, "aria-hidden": "true" })
5386
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "ssml-editor-switch-thumb", style: editorStyles.toolbarSwitchThumb, "aria-hidden": "true" })
4897
5387
  }
4898
5388
  )
4899
5389
  ] });
4900
5390
  }
4901
- var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5391
+ var SsmlEditor = (0, import_react4.forwardRef)(function SsmlEditor2({
4902
5392
  document: document2,
4903
5393
  onChange,
4904
5394
  onSsmlChange,
@@ -4936,8 +5426,11 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
4936
5426
  toolbarStyle,
4937
5427
  displayClassName,
4938
5428
  displayStyle,
4939
- loadingFallback
5429
+ loadingFallback,
5430
+ editMode: editModeProp = "code"
4940
5431
  }, ref) {
5432
+ const [editMode, setEditMode] = (0, import_react4.useState)(editModeProp);
5433
+ (0, import_react4.useEffect)(() => setEditMode(editModeProp), [editModeProp]);
4941
5434
  const resolvedEditorOptions = {
4942
5435
  ...settings,
4943
5436
  ...editorOptions,
@@ -5015,6 +5508,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5015
5508
  );
5016
5509
  const {
5017
5510
  editorRef,
5511
+ draftDocument,
5018
5512
  text,
5019
5513
  selectionOverlay,
5020
5514
  activeTags,
@@ -5026,6 +5520,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5026
5520
  syntaxError,
5027
5521
  setSyntaxError,
5028
5522
  helpPanelId,
5523
+ commit,
5029
5524
  handleTextChange,
5030
5525
  handleInsert,
5031
5526
  handleInsertBreak,
@@ -5073,7 +5568,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5073
5568
  enableCodeLens,
5074
5569
  onOpenPopover: handleCodeLensAction
5075
5570
  });
5076
- (0, import_react3.useImperativeHandle)(
5571
+ (0, import_react4.useImperativeHandle)(
5077
5572
  ref,
5078
5573
  () => ({
5079
5574
  getSelectedSsml: getSelectedSsml2,
@@ -5103,15 +5598,15 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5103
5598
  onApply: isTimingPopover ? handleInsertBreak : isProsodyPopover ? handleInsertProsody : isTextPopover ? handleInsertText : handleInsert
5104
5599
  };
5105
5600
  if (isTimingPopover) {
5106
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TimingPopovers, { ...props, insertions: [insertion] });
5601
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TimingPopovers, { ...props, insertions: [insertion] });
5107
5602
  }
5108
5603
  if (isProsodyPopover) {
5109
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ProsodyPopovers, { ...props, insertions: [insertion], voiceName: popoverVoiceName });
5604
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ProsodyPopovers, { ...props, insertions: [insertion], voiceName: popoverVoiceName });
5110
5605
  }
5111
5606
  if (isTextPopover) {
5112
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(TextPopovers, { ...props, insertions: [insertion] });
5607
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(TextPopovers, { ...props, insertions: [insertion] });
5113
5608
  }
5114
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5609
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5115
5610
  InsertionPopover,
5116
5611
  {
5117
5612
  ...props,
@@ -5124,7 +5619,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5124
5619
  const toolbarItemRenderers = /* @__PURE__ */ new Map([
5125
5620
  [
5126
5621
  "undo",
5127
- () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
5622
+ () => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5128
5623
  "button",
5129
5624
  {
5130
5625
  type: "button",
@@ -5138,8 +5633,8 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5138
5633
  }
5139
5634
  },
5140
5635
  children: [
5141
- showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "\u21A9" }),
5142
- showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: copy.undo })
5636
+ showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "\u21A9" }),
5637
+ showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: copy.undo })
5143
5638
  ]
5144
5639
  },
5145
5640
  "undo"
@@ -5147,7 +5642,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5147
5642
  ],
5148
5643
  [
5149
5644
  "redo",
5150
- () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
5645
+ () => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5151
5646
  "button",
5152
5647
  {
5153
5648
  type: "button",
@@ -5161,8 +5656,8 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5161
5656
  }
5162
5657
  },
5163
5658
  children: [
5164
- showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "\u21AA" }),
5165
- showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: copy.redo })
5659
+ showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "\u21AA" }),
5660
+ showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: copy.redo })
5166
5661
  ]
5167
5662
  },
5168
5663
  "redo"
@@ -5170,7 +5665,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5170
5665
  ],
5171
5666
  [
5172
5667
  "clearAll",
5173
- () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
5668
+ () => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5174
5669
  "button",
5175
5670
  {
5176
5671
  type: "button",
@@ -5184,8 +5679,8 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5184
5679
  }
5185
5680
  },
5186
5681
  children: [
5187
- showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "\xD7" }),
5188
- showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: copy.clearAll })
5682
+ showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "\xD7" }),
5683
+ showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: copy.clearAll })
5189
5684
  ]
5190
5685
  },
5191
5686
  "clearAll"
@@ -5193,7 +5688,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5193
5688
  ],
5194
5689
  [
5195
5690
  "format",
5196
- () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
5691
+ () => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5197
5692
  "button",
5198
5693
  {
5199
5694
  type: "button",
@@ -5207,8 +5702,8 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5207
5702
  }
5208
5703
  },
5209
5704
  children: [
5210
- showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "\u2261" }),
5211
- showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: copy.format })
5705
+ showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "\u2261" }),
5706
+ showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: copy.format })
5212
5707
  ]
5213
5708
  },
5214
5709
  "format"
@@ -5216,7 +5711,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5216
5711
  ],
5217
5712
  [
5218
5713
  "decorations",
5219
- () => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5714
+ () => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5220
5715
  ToolbarDecorationsSwitch,
5221
5716
  {
5222
5717
  copy,
@@ -5230,7 +5725,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5230
5725
  ],
5231
5726
  [
5232
5727
  "help",
5233
- () => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
5728
+ () => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5234
5729
  "button",
5235
5730
  {
5236
5731
  type: "button",
@@ -5241,8 +5736,8 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5241
5736
  "aria-controls": helpPanelId,
5242
5737
  onClick: () => setIsHelpOpen((open) => !open),
5243
5738
  children: [
5244
- showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "?" }),
5245
- showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: copy.help })
5739
+ showToolbarIcons && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true", children: "?" }),
5740
+ showToolbarText && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { children: copy.help })
5246
5741
  ]
5247
5742
  },
5248
5743
  "help"
@@ -5266,15 +5761,15 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5266
5761
  previousRenderedGroupId = groupId;
5267
5762
  hasRenderedToolbarItem = true;
5268
5763
  renderedItems.push(
5269
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react3.Fragment, { children: [
5270
- showSeparator && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: editorStyles.toolbarSeparator, "aria-hidden": "true" }),
5764
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react4.Fragment, { children: [
5765
+ showSeparator && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.toolbarSeparator, "aria-hidden": "true" }),
5271
5766
  render()
5272
5767
  ] }, id)
5273
5768
  );
5274
5769
  }
5275
5770
  return renderedItems;
5276
5771
  };
5277
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
5772
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5278
5773
  "section",
5279
5774
  {
5280
5775
  className,
@@ -5283,62 +5778,93 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5283
5778
  "data-ssml-editor": "",
5284
5779
  "data-theme": isDarkTheme ? "dark" : "light",
5285
5780
  children: [
5286
- showToolbar && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5781
+ showToolbar && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5287
5782
  "div",
5288
5783
  {
5289
5784
  className: toolbarClassName,
5290
5785
  style: { ...editorStyles.toolbarContainer, ...toolbarStyle },
5291
5786
  "data-ssml-editor-toolbar": "",
5292
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5787
+ children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5293
5788
  "div",
5294
5789
  {
5295
5790
  style: editorStyles.toolbarActions,
5296
5791
  role: "toolbar",
5297
5792
  "aria-label": copy.toolbarAriaLabel,
5298
5793
  "data-ssml-editor-toolbar-actions": "",
5299
- children: renderToolbarItems()
5794
+ children: [
5795
+ renderToolbarItems(),
5796
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.toolbarSeparator, "aria-hidden": "true" }),
5797
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5798
+ "button",
5799
+ {
5800
+ type: "button",
5801
+ style: editMode === "visual" ? { ...toolbarButtonStyle, ...editorStyles.toolbarButtonActive } : toolbarButtonStyle,
5802
+ "aria-pressed": editMode === "visual",
5803
+ onClick: () => setEditMode("visual"),
5804
+ children: "Visual"
5805
+ }
5806
+ ),
5807
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5808
+ "button",
5809
+ {
5810
+ type: "button",
5811
+ style: editMode === "code" ? { ...toolbarButtonStyle, ...editorStyles.toolbarButtonActive } : toolbarButtonStyle,
5812
+ "aria-pressed": editMode === "code",
5813
+ onClick: () => setEditMode("code"),
5814
+ children: "Code"
5815
+ }
5816
+ )
5817
+ ]
5300
5818
  }
5301
5819
  )
5302
5820
  }
5303
5821
  ),
5304
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: displayClassName, style: { ...editorStyles.display, ...displayStyle }, "data-ssml-editor-display": "", children: [
5305
- isHelpOpen && isSsmlEditorButtonVisible(buttonVisibility, "help") && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("section", { id: helpPanelId, style: editorStyles.helpPanel, "aria-label": copy.helpHeading, children: [
5306
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h3", { style: editorStyles.helpHeading, children: copy.helpHeading }),
5307
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { style: editorStyles.helpDescription, children: copy.helpDescription }),
5308
- helpInsertions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { style: editorStyles.helpList, children: helpInsertions.map((insertion) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("li", { style: editorStyles.helpItem, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("details", { className: "ssml-editor-help-settings-accordion", style: editorStyles.helpSettingsAccordion, children: [
5309
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("summary", { className: "ssml-editor-help-settings-summary", style: editorStyles.helpSettingsSummary, children: [
5310
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: editorStyles.helpIcon, "aria-hidden": "true", children: insertion.icon }),
5311
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: editorStyles.helpSettingsSummaryContent, children: [
5312
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: insertion.labels[language] }),
5822
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: displayClassName, style: { ...editorStyles.display, ...displayStyle }, "data-ssml-editor-display": "", children: [
5823
+ isHelpOpen && isSsmlEditorButtonVisible(buttonVisibility, "help") && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("section", { id: helpPanelId, style: editorStyles.helpPanel, "aria-label": copy.helpHeading, children: [
5824
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h3", { style: editorStyles.helpHeading, children: copy.helpHeading }),
5825
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: editorStyles.helpDescription, children: copy.helpDescription }),
5826
+ helpInsertions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { style: editorStyles.helpList, children: helpInsertions.map((insertion) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("li", { style: editorStyles.helpItem, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("details", { className: "ssml-editor-help-settings-accordion", style: editorStyles.helpSettingsAccordion, children: [
5827
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("summary", { className: "ssml-editor-help-settings-summary", style: editorStyles.helpSettingsSummary, children: [
5828
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.helpIcon, "aria-hidden": "true", children: insertion.icon }),
5829
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { style: editorStyles.helpSettingsSummaryContent, children: [
5830
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: insertion.labels[language] }),
5313
5831
  " ",
5314
- insertion.tagName && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5315
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("code", { children: `<${insertion.tagName}${insertion.selfClosing ? "/>" : ">"}` }),
5832
+ insertion.tagName && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
5833
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("code", { children: `<${insertion.tagName}${insertion.selfClosing ? "/>" : ">"}` }),
5316
5834
  " \u2014",
5317
5835
  " "
5318
5836
  ] }),
5319
5837
  insertion.descriptions[language]
5320
5838
  ] })
5321
5839
  ] }),
5322
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { style: editorStyles.helpSettingsDescription, children: [
5323
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("strong", { children: [
5840
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { style: editorStyles.helpSettingsDescription, children: [
5841
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("strong", { children: [
5324
5842
  copy.parameters,
5325
5843
  ":"
5326
5844
  ] }),
5327
5845
  " ",
5328
5846
  insertion.parameterDescription[language]
5329
5847
  ] }),
5330
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("ul", { style: editorStyles.helpSettingsList, children: insertion.options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("li", { children: [
5331
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("strong", { children: option.labels[language] }),
5332
- option.descriptions && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_jsx_runtime6.Fragment, { children: [
5848
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { style: editorStyles.helpSettingsList, children: insertion.options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { children: [
5849
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("strong", { children: option.labels[language] }),
5850
+ option.descriptions && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
5333
5851
  " \u2014 ",
5334
5852
  option.descriptions[language]
5335
5853
  ] })
5336
5854
  ] }, option.value)) })
5337
5855
  ] }) }, insertion.id)) })
5338
5856
  ] }),
5339
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { style: { ...editorStyles.editor, minHeight: editorMinHeight }, children: [
5340
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
5341
- import_react4.default,
5857
+ editMode === "visual" ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: editorStyles.visual, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5858
+ VisualSsmlEditor,
5859
+ {
5860
+ document: draftDocument,
5861
+ readOnly: isReadOnly,
5862
+ onChange: commit,
5863
+ onPreviewSelection
5864
+ }
5865
+ ) }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: { ...editorStyles.editor, minHeight: editorMinHeight }, children: [
5866
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
5867
+ import_react5.default,
5342
5868
  {
5343
5869
  height: editorHeight,
5344
5870
  language: "xml",
@@ -5362,7 +5888,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5362
5888
  onChange: (value) => handleTextChange(value ?? "")
5363
5889
  }
5364
5890
  ),
5365
- selectionOverlay.hasSelection && selectionOverlay.position && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
5891
+ selectionOverlay.hasSelection && selectionOverlay.position && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5366
5892
  "div",
5367
5893
  {
5368
5894
  role: "toolbar",
@@ -5376,12 +5902,12 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5376
5902
  },
5377
5903
  onMouseDown: (event) => event.preventDefault(),
5378
5904
  children: [
5379
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("span", { style: editorStyles.selectionCount, "aria-live": "polite", children: [
5905
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("span", { style: editorStyles.selectionCount, "aria-live": "polite", children: [
5380
5906
  selectionOverlay.characterCount,
5381
5907
  copy.selectionCountSuffix
5382
5908
  ] }),
5383
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: editorStyles.selectionActionsDivider, "aria-hidden": "true" }),
5384
- /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
5909
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.selectionActionsDivider, "aria-hidden": "true" }),
5910
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
5385
5911
  "button",
5386
5912
  {
5387
5913
  type: "button",
@@ -5390,7 +5916,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5390
5916
  disabled: !canPreviewSelection,
5391
5917
  onClick: previewSelection,
5392
5918
  children: [
5393
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { style: editorStyles.selectionActionIcon, "aria-hidden": "true", children: "\u25B6" }),
5919
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: editorStyles.selectionActionIcon, "aria-hidden": "true", children: "\u25B6" }),
5394
5920
  copy.previewSelection
5395
5921
  ]
5396
5922
  }
@@ -5399,7 +5925,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5399
5925
  }
5400
5926
  )
5401
5927
  ] }),
5402
- syntaxError && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { style: editorStyles.error, role: "alert", children: [
5928
+ syntaxError && /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { style: editorStyles.error, role: "alert", children: [
5403
5929
  copy.syntaxError,
5404
5930
  ": ",
5405
5931
  syntaxError.message
@@ -5416,6 +5942,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5416
5942
  SSML_HOVER_COPY,
5417
5943
  SSML_INSERTIONS,
5418
5944
  SsmlEditor,
5945
+ VisualSsmlEditor,
5419
5946
  createSsmlEditorInsertionDefinition,
5420
5947
  registerSsmlCodeLens,
5421
5948
  updateTagAttribute