ssml-builder-js 2.1.0 → 2.3.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,7 +36,9 @@ __export(react_exports, {
36
36
  SSML_HOVER_COPY: () => SSML_HOVER_COPY,
37
37
  SSML_INSERTIONS: () => SSML_INSERTIONS,
38
38
  SsmlEditor: () => SsmlEditor,
39
- createSsmlEditorInsertionDefinition: () => createSsmlEditorInsertionDefinition
39
+ createSsmlEditorInsertionDefinition: () => createSsmlEditorInsertionDefinition,
40
+ registerSsmlCodeLens: () => registerSsmlCodeLens,
41
+ updateTagAttribute: () => updateTagAttribute
40
42
  });
41
43
  module.exports = __toCommonJS(react_exports);
42
44
 
@@ -58,7 +60,7 @@ __export(ssmlPresets_exports, {
58
60
  EMPHASIS_LEVEL_DESCRIPTIONS: () => EMPHASIS_LEVEL_DESCRIPTIONS,
59
61
  EMPHASIS_LEVEL_PRESETS: () => EMPHASIS_LEVEL_PRESETS,
60
62
  EXPRESS_AS_ROLE_PRESETS: () => EXPRESS_AS_ROLE_PRESETS,
61
- EXPRESS_AS_STYLES_BY_VOICE: () => EXPRESS_AS_STYLES_BY_VOICE,
63
+ EXPRESS_AS_STYLE_CATEGORIES: () => EXPRESS_AS_STYLE_CATEGORIES,
62
64
  EXPRESS_AS_STYLE_DESCRIPTIONS: () => EXPRESS_AS_STYLE_DESCRIPTIONS,
63
65
  EXPRESS_AS_STYLE_PRESETS: () => EXPRESS_AS_STYLE_PRESETS,
64
66
  LANGUAGE_DESCRIPTIONS: () => LANGUAGE_DESCRIPTIONS,
@@ -80,6 +82,8 @@ __export(ssmlPresets_exports, {
80
82
  SSML_PRESETS: () => SSML_PRESETS,
81
83
  SSML_PRESET_EXAMPLES: () => SSML_PRESET_EXAMPLES,
82
84
  VISEME_TYPE_PRESETS: () => VISEME_TYPE_PRESETS,
85
+ VOICE_STYLE_MAP: () => VOICE_STYLE_MAP,
86
+ getExpressAsStyleCategory: () => getExpressAsStyleCategory,
83
87
  resolveExpressAsStyles: () => resolveExpressAsStyles
84
88
  });
85
89
  var SSML_PRESETS = [
@@ -124,21 +128,90 @@ var EXPRESS_AS_STYLE_PRESETS = [
124
128
  "shouting",
125
129
  "terrified",
126
130
  "unfriendly",
127
- "whispering"
131
+ "whispering",
132
+ "empathetic",
133
+ "relieved",
134
+ "fearful",
135
+ "depressed",
136
+ "disgruntled",
137
+ "embarrassed",
138
+ "narration-relaxed",
139
+ "poetry-reading",
140
+ "sports_commentary",
141
+ "sports_commentary_excited",
142
+ "story"
128
143
  ];
129
- var EXPRESS_AS_STYLES_BY_VOICE = {
130
- "ja-JP-NanamiNeural": ["chat", "cheerful", "customerservice"],
131
- "ja-JP-KeitaNeural": [],
132
- "en-US-JennyNeural": [
144
+ var EXPRESS_AS_STYLE_CATEGORIES = {
145
+ emotions: [
146
+ "cheerful",
147
+ "sad",
133
148
  "angry",
149
+ "calm",
150
+ "fearful",
151
+ "depressed",
152
+ "disgruntled",
153
+ "embarrassed",
154
+ "empathetic",
155
+ "envious",
156
+ "excited",
157
+ "friendly",
158
+ "gentle",
159
+ "hopeful",
160
+ "relieved",
161
+ "serious",
162
+ "shouting",
163
+ "terrified",
164
+ "unfriendly",
165
+ "whispering"
166
+ ],
167
+ scenarios: ["chat", "customerservice", "assistant", "livecommercial", "poetry-reading", "story"],
168
+ media: [
169
+ "newscast",
170
+ "newscast-casual",
171
+ "newscast-formal",
172
+ "narration-professional",
173
+ "narration-relaxed",
174
+ "documentary-narration",
175
+ "advertisement_upbeat",
176
+ "sports_commentary",
177
+ "sports_commentary_excited"
178
+ ]
179
+ };
180
+ function getExpressAsStyleCategory(style) {
181
+ for (const [category, styles] of Object.entries(EXPRESS_AS_STYLE_CATEGORIES)) {
182
+ if (styles.includes(style)) {
183
+ return category;
184
+ }
185
+ }
186
+ return "other";
187
+ }
188
+ var VOICE_STYLE_MAP = {
189
+ "ja-JP-MayuNeural": ["calm", "cheerful", "sad"],
190
+ "ja-JP-KeitaNeural": ["chat"],
191
+ "ja-JP-NanamiNeural": ["chat", "customerservice", "cheerful", "whispering", "sad"],
192
+ "en-US-JennyMultilingualNeural": [
193
+ "cheerful",
194
+ "empathetic",
195
+ "excited",
196
+ "friendly",
197
+ "hopeful",
198
+ "sad",
199
+ "shouting",
200
+ "terrified",
201
+ "unfriendly",
202
+ "whispering"
203
+ ],
204
+ "en-US-AndrewNeural": ["empathetic", "relieved"],
205
+ "en-US-JennyNeural": [
134
206
  "assistant",
135
207
  "chat",
136
- "cheerful",
137
208
  "customerservice",
209
+ "newscast",
210
+ "cheerful",
211
+ "empathetic",
138
212
  "excited",
139
213
  "friendly",
140
214
  "hopeful",
141
- "newscast",
142
215
  "sad",
143
216
  "shouting",
144
217
  "terrified",
@@ -157,18 +230,58 @@ var EXPRESS_AS_STYLES_BY_VOICE = {
157
230
  "terrified",
158
231
  "unfriendly",
159
232
  "whispering"
160
- ]
233
+ ],
234
+ "ko-KR-SunHiNeural": ["cheerful", "sad"],
235
+ "zh-CN-XiaoxiaoNeural": [
236
+ "assistant",
237
+ "chat",
238
+ "customerservice",
239
+ "newscast",
240
+ "cheerful",
241
+ "empathetic",
242
+ "excited",
243
+ "friendly",
244
+ "hopeful",
245
+ "sad",
246
+ "terrified",
247
+ "whispering",
248
+ "poetry-reading",
249
+ "sports_commentary",
250
+ "sports_commentary_excited",
251
+ "story"
252
+ ],
253
+ "zh-CN-YunxiNeural": [
254
+ "narration-relaxed",
255
+ "embarrassed",
256
+ "fearful",
257
+ "sad",
258
+ "disgruntled",
259
+ "serious",
260
+ "angry",
261
+ "depressed",
262
+ "chat",
263
+ "cheerful",
264
+ "assistant"
265
+ ],
266
+ "fr-FR-DeniseNeural": ["cheerful", "sad"],
267
+ "fr-FR-HenriNeural": ["cheerful", "sad"],
268
+ "pt-BR-FranciscaNeural": ["calm"],
269
+ "it-IT-ElsaNeural": ["cheerful", "sad"],
270
+ "de-DE-KatjaNeural": ["cheerful", "sad"],
271
+ "de-DE-ConradNeural": ["cheerful", "sad"],
272
+ "ru-RU-SvetlanaNeural": ["cheerful", "sad", "angry", "disgruntled", "embarrassed", "fearful"]
161
273
  };
162
- var EXPRESS_AS_STYLES_BY_NORMALIZED_VOICE = new Map(
163
- Object.entries(EXPRESS_AS_STYLES_BY_VOICE).map(([voiceName, styles]) => [voiceName.toLowerCase(), styles])
274
+ var VOICE_STYLE_MAP_BY_NORMALIZED_NAME = new Map(
275
+ Object.entries(VOICE_STYLE_MAP).map(([voiceName, styles]) => [voiceName.toLowerCase(), styles])
164
276
  );
165
277
  function resolveExpressAsStyles(voiceName, candidates = EXPRESS_AS_STYLE_PRESETS) {
166
- if (voiceName === void 0) {
278
+ const normalizedVoiceName = voiceName?.trim().toLowerCase();
279
+ if (!normalizedVoiceName) {
167
280
  return candidates;
168
281
  }
169
- const supportedStyles = EXPRESS_AS_STYLES_BY_NORMALIZED_VOICE.get(voiceName.trim().toLowerCase());
282
+ const supportedStyles = VOICE_STYLE_MAP_BY_NORMALIZED_NAME.get(normalizedVoiceName);
170
283
  if (supportedStyles === void 0) {
171
- return candidates;
284
+ return [];
172
285
  }
173
286
  const supportedStyleSet = new Set(supportedStyles);
174
287
  return candidates.filter((style) => supportedStyleSet.has(style));
@@ -418,6 +531,30 @@ var EXPRESS_AS_STYLE_DESCRIPTIONS = {
418
531
  ja: "\u8208\u596E\u3057\u305F\u30B9\u30BF\u30A4\u30EB",
419
532
  en: "Uses an excited style."
420
533
  },
534
+ empathetic: {
535
+ ja: "\u5171\u611F\u3092\u793A\u3059\u30B9\u30BF\u30A4\u30EB",
536
+ en: "Uses an empathetic style."
537
+ },
538
+ relieved: {
539
+ ja: "\u5B89\u5FC3\u3057\u305F\u30B9\u30BF\u30A4\u30EB",
540
+ en: "Uses a relieved style."
541
+ },
542
+ fearful: {
543
+ ja: "\u6050\u308C\u3092\u611F\u3058\u3055\u305B\u308B\u30B9\u30BF\u30A4\u30EB",
544
+ en: "Uses a fearful style."
545
+ },
546
+ depressed: {
547
+ ja: "\u843D\u3061\u8FBC\u3093\u3060\u30B9\u30BF\u30A4\u30EB",
548
+ en: "Uses a depressed style."
549
+ },
550
+ disgruntled: {
551
+ ja: "\u4E0D\u6E80\u3092\u611F\u3058\u3055\u305B\u308B\u30B9\u30BF\u30A4\u30EB",
552
+ en: "Uses a disgruntled style."
553
+ },
554
+ embarrassed: {
555
+ ja: "\u6065\u305A\u304B\u3057\u305D\u3046\u306A\u30B9\u30BF\u30A4\u30EB",
556
+ en: "Uses an embarrassed style."
557
+ },
421
558
  serious: {
422
559
  ja: "\u771F\u5263\u306A\u30B9\u30BF\u30A4\u30EB",
423
560
  en: "Uses a serious style."
@@ -457,6 +594,26 @@ var EXPRESS_AS_STYLE_DESCRIPTIONS = {
457
594
  whispering: {
458
595
  ja: "\u3055\u3055\u3084\u304F\u3088\u3046\u306A\u30B9\u30BF\u30A4\u30EB",
459
596
  en: "Uses a whispering style."
597
+ },
598
+ "narration-relaxed": {
599
+ ja: "\u30EA\u30E9\u30C3\u30AF\u30B9\u3057\u305F\u30CA\u30EC\u30FC\u30B7\u30E7\u30F3\u5411\u3051\u306E\u30B9\u30BF\u30A4\u30EB",
600
+ en: "Uses a relaxed narration style."
601
+ },
602
+ "poetry-reading": {
603
+ ja: "\u8A69\u306E\u6717\u8AAD\u5411\u3051\u306E\u30B9\u30BF\u30A4\u30EB",
604
+ en: "Uses a poetry-reading style."
605
+ },
606
+ sports_commentary: {
607
+ ja: "\u30B9\u30DD\u30FC\u30C4\u5B9F\u6CC1\u5411\u3051\u306E\u30B9\u30BF\u30A4\u30EB",
608
+ en: "Uses a sports commentary style."
609
+ },
610
+ sports_commentary_excited: {
611
+ ja: "\u8208\u596E\u3057\u305F\u30B9\u30DD\u30FC\u30C4\u5B9F\u6CC1\u5411\u3051\u306E\u30B9\u30BF\u30A4\u30EB",
612
+ en: "Uses an excited sports commentary style."
613
+ },
614
+ story: {
615
+ ja: "\u7269\u8A9E\u306E\u6717\u8AAD\u5411\u3051\u306E\u30B9\u30BF\u30A4\u30EB",
616
+ en: "Uses a storytelling style."
460
617
  }
461
618
  };
462
619
  var SAY_AS_DESCRIPTIONS = {
@@ -568,7 +725,12 @@ var EDITOR_COPY = {
568
725
  selectionCountSuffix: "\u6587\u5B57",
569
726
  previewSelection: "\u9078\u629E\u90E8\u5206\u3092\u8A66\u8074",
570
727
  previewSelectionTitle: "\u9078\u629E\u90E8\u5206\u306ESSML\u3092\u8A66\u8074",
571
- noAvailableOptions: "\u5229\u7528\u53EF\u80FD\u306A\u9078\u629E\u80A2\u304C\u3042\u308A\u307E\u305B\u3093\u3002"
728
+ noAvailableOptions: "\u5229\u7528\u53EF\u80FD\u306A\u9078\u629E\u80A2\u304C\u3042\u308A\u307E\u305B\u3093\u3002",
729
+ styleNotSupported: "\u3053\u306E\u97F3\u58F0\u306F\u30B9\u30BF\u30A4\u30EB\u6307\u5B9A\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093",
730
+ categoryEmotions: "\u611F\u60C5\u30FB\u30C8\u30FC\u30F3",
731
+ categoryScenarios: "\u4F1A\u8A71\u30FB\u30B7\u30CA\u30EA\u30AA",
732
+ categoryMedia: "\u30E1\u30C7\u30A3\u30A2\u30FB\u30CA\u30EC\u30FC\u30B7\u30E7\u30F3",
733
+ categoryOther: "\u305D\u306E\u4ED6"
572
734
  },
573
735
  en: {
574
736
  editorAriaLabel: "SSML editor",
@@ -594,7 +756,12 @@ var EDITOR_COPY = {
594
756
  selectionCountSuffix: " characters",
595
757
  previewSelection: "Preview selection",
596
758
  previewSelectionTitle: "Preview the selected SSML",
597
- noAvailableOptions: "No options are available."
759
+ noAvailableOptions: "No options are available.",
760
+ styleNotSupported: "This voice does not support style selection.",
761
+ categoryEmotions: "Emotions / Tone",
762
+ categoryScenarios: "Conversations / Scenarios",
763
+ categoryMedia: "Media / Broadcast",
764
+ categoryOther: "Other"
598
765
  }
599
766
  };
600
767
  var INLINE_BADGE_COPY = {
@@ -991,6 +1158,10 @@ var editorStyles = {
991
1158
  font: "inherit",
992
1159
  cursor: "pointer"
993
1160
  },
1161
+ toolbarButtonActive: {
1162
+ border: "1px solid var(--ssml-editor-active-border)",
1163
+ backgroundColor: "var(--ssml-editor-active-bg)"
1164
+ },
994
1165
  display: {
995
1166
  display: "grid",
996
1167
  gap: "0.5rem"
@@ -1036,12 +1207,35 @@ var editorStyles = {
1036
1207
  whiteSpace: "nowrap",
1037
1208
  cursor: "pointer"
1038
1209
  },
1210
+ toolbarOptionGroup: {
1211
+ display: "grid",
1212
+ gap: "0.125rem",
1213
+ margin: 0,
1214
+ padding: 0,
1215
+ border: 0
1216
+ },
1217
+ toolbarOptionGroupLabel: {
1218
+ padding: "0.375rem 0.5rem 0.125rem",
1219
+ color: "var(--ssml-editor-color)",
1220
+ fontSize: "0.875rem",
1221
+ fontWeight: 600,
1222
+ whiteSpace: "nowrap"
1223
+ },
1039
1224
  toolbarEmpty: {
1040
1225
  margin: 0,
1041
1226
  padding: "0.375rem 0.5rem",
1042
1227
  color: "var(--ssml-editor-color)",
1043
1228
  whiteSpace: "nowrap"
1044
1229
  },
1230
+ toolbarEmptySelect: {
1231
+ padding: "0.375rem 0.5rem",
1232
+ border: "1px solid var(--ssml-editor-control-border)",
1233
+ borderRadius: "0.125rem",
1234
+ color: "var(--ssml-editor-color)",
1235
+ backgroundColor: "var(--ssml-editor-control-bg)",
1236
+ font: "inherit",
1237
+ textAlign: "left"
1238
+ },
1045
1239
  helpPanel: {
1046
1240
  display: "grid",
1047
1241
  gap: "0.5rem",
@@ -1193,8 +1387,29 @@ function InsertionPopover({
1193
1387
  menuRef,
1194
1388
  onToggle,
1195
1389
  onClose,
1196
- onApply
1390
+ onApply,
1391
+ optionGroups
1197
1392
  }) {
1393
+ const hasOptions = optionGroups ? optionGroups.some((group) => group.options.length > 0) : insertion.options.length > 0;
1394
+ const renderOption = (option) => /* @__PURE__ */ React.createElement(
1395
+ "button",
1396
+ {
1397
+ key: option.value,
1398
+ type: "button",
1399
+ role: "menuitem",
1400
+ style: editorStyles.toolbarOption,
1401
+ title: option.descriptions?.[language] ?? insertion.descriptions[language],
1402
+ disabled: isReadOnly,
1403
+ onMouseDown: (event) => event.preventDefault(),
1404
+ onClick: () => {
1405
+ if (!isReadOnly) {
1406
+ onApply(insertion, option);
1407
+ }
1408
+ onClose();
1409
+ }
1410
+ },
1411
+ option.labels[language]
1412
+ );
1198
1413
  const menu = isOpen && menuPosition && typeof document !== "undefined" ? /* @__PURE__ */ React.createElement(
1199
1414
  "div",
1200
1415
  {
@@ -1206,25 +1421,7 @@ function InsertionPopover({
1206
1421
  role: "menu",
1207
1422
  "aria-label": insertion.labels[language]
1208
1423
  },
1209
- insertion.options.length === 0 ? /* @__PURE__ */ React.createElement("p", { role: "status", style: editorStyles.toolbarEmpty }, emptyOptionsMessage) : insertion.options.map((option) => /* @__PURE__ */ React.createElement(
1210
- "button",
1211
- {
1212
- key: option.value,
1213
- type: "button",
1214
- role: "menuitem",
1215
- style: editorStyles.toolbarOption,
1216
- title: option.descriptions?.[language] ?? insertion.descriptions[language],
1217
- disabled: isReadOnly,
1218
- onMouseDown: (event) => event.preventDefault(),
1219
- onClick: () => {
1220
- if (!isReadOnly) {
1221
- onApply(insertion, option);
1222
- }
1223
- onClose();
1224
- }
1225
- },
1226
- option.labels[language]
1227
- ))
1424
+ !hasOptions ? /* @__PURE__ */ React.createElement("div", { role: "presentation" }, /* @__PURE__ */ React.createElement("select", { disabled: true, value: "", style: editorStyles.toolbarEmptySelect }, /* @__PURE__ */ React.createElement("option", { value: "", disabled: true }, emptyOptionsMessage))) : optionGroups ? optionGroups.map((group) => /* @__PURE__ */ React.createElement("fieldset", { key: group.label, style: editorStyles.toolbarOptionGroup }, /* @__PURE__ */ React.createElement("legend", { style: editorStyles.toolbarOptionGroupLabel }, group.label), group.options.map(renderOption))) : insertion.options.map(renderOption)
1228
1425
  ) : null;
1229
1426
  return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { style: editorStyles.toolbarDropdown }, /* @__PURE__ */ React.createElement(
1230
1427
  "button",
@@ -1249,6 +1446,8 @@ function InsertionPopovers({
1249
1446
  insertions,
1250
1447
  openPopoverId,
1251
1448
  onToggle,
1449
+ emptyOptionsMessages,
1450
+ optionGroups,
1252
1451
  ...props
1253
1452
  }) {
1254
1453
  return /* @__PURE__ */ React.createElement(React.Fragment, null, insertions.map((insertion) => /* @__PURE__ */ React.createElement(
@@ -1257,6 +1456,8 @@ function InsertionPopovers({
1257
1456
  key: insertion.id,
1258
1457
  ...props,
1259
1458
  insertion,
1459
+ emptyOptionsMessage: emptyOptionsMessages?.[insertion.id] ?? props.emptyOptionsMessage,
1460
+ optionGroups: optionGroups?.[insertion.id],
1260
1461
  isOpen: openPopoverId === insertion.id,
1261
1462
  onToggle: (trigger) => onToggle(insertion.id, trigger)
1262
1463
  }
@@ -1264,23 +1465,57 @@ function InsertionPopovers({
1264
1465
  }
1265
1466
 
1266
1467
  // packages/ssml-editor-react/src/components/popovers/ProsodyPopovers.tsx
1267
- function ProsodyPopovers({ insertions, voiceName, ...props }) {
1468
+ var CATEGORY_LABEL_KEYS = {
1469
+ emotions: "categoryEmotions",
1470
+ scenarios: "categoryScenarios",
1471
+ media: "categoryMedia",
1472
+ other: "categoryOther"
1473
+ };
1474
+ var CATEGORY_ORDER = ["emotions", "scenarios", "media", "other"];
1475
+ function isExpressAsInsertion(insertion) {
1476
+ return insertion.id === "emotion" && insertion.tagName?.toLowerCase() === "mstts:express-as";
1477
+ }
1478
+ function createOptionGroups(options, language) {
1479
+ const t = (key) => EDITOR_COPY[language][key];
1480
+ return CATEGORY_ORDER.map((category) => ({
1481
+ label: t(CATEGORY_LABEL_KEYS[category]),
1482
+ options: options.filter((option) => getExpressAsStyleCategory(option.value) === category)
1483
+ })).filter((group) => group.options.length > 0);
1484
+ }
1485
+ function ProsodyPopovers({ insertions, voiceName, language, ...props }) {
1486
+ const emptyOptionsMessages = {};
1268
1487
  const filteredInsertions = insertions.map((insertion) => {
1269
- if (insertion.id !== "emotion" || insertion.tagName?.toLowerCase() !== "mstts:express-as") {
1488
+ if (!isExpressAsInsertion(insertion)) {
1270
1489
  return insertion;
1271
1490
  }
1272
- const styles = new Set(
1491
+ const availableStyles = new Set(
1273
1492
  resolveExpressAsStyles(
1274
1493
  voiceName,
1275
1494
  insertion.options.map((option) => option.value)
1276
1495
  )
1277
1496
  );
1497
+ if (availableStyles.size === 0) {
1498
+ emptyOptionsMessages[insertion.id] = EDITOR_COPY[language].styleNotSupported;
1499
+ }
1500
+ const availableOptions = insertion.options.filter((option) => availableStyles.has(option.value));
1278
1501
  return {
1279
1502
  ...insertion,
1280
- options: insertion.options.filter((option) => styles.has(option.value))
1503
+ options: availableOptions
1281
1504
  };
1282
1505
  });
1283
- return /* @__PURE__ */ React.createElement(InsertionPopovers, { ...props, insertions: filteredInsertions });
1506
+ const optionGroups = Object.fromEntries(
1507
+ filteredInsertions.filter(isExpressAsInsertion).map((insertion) => [insertion.id, createOptionGroups(insertion.options, language)])
1508
+ );
1509
+ return /* @__PURE__ */ React.createElement(
1510
+ InsertionPopovers,
1511
+ {
1512
+ ...props,
1513
+ emptyOptionsMessages,
1514
+ language,
1515
+ insertions: filteredInsertions,
1516
+ optionGroups
1517
+ }
1518
+ );
1284
1519
  }
1285
1520
 
1286
1521
  // packages/ssml-editor-react/src/components/popovers/TextPopovers.tsx
@@ -1355,6 +1590,7 @@ var SSML_ATTRS = {
1355
1590
  STYLE: "style",
1356
1591
  STYLE_DEGREE: "styledegree",
1357
1592
  STYLE_DEGREE_CAMEL: "styleDegree",
1593
+ STYLE_DEGREE_HYPHEN: "style-degree",
1358
1594
  ROLE: "role",
1359
1595
  INTERPRET_AS: "interpret-as",
1360
1596
  FORMAT: "format",
@@ -1898,7 +2134,12 @@ function convertElement(node) {
1898
2134
  case SSML_TAGS.MSTTS_EXPRESS_AS: {
1899
2135
  const element = { type: node.name };
1900
2136
  const style = readAttribute(attributes, SSML_ATTRS.STYLE);
1901
- const styleDegree = readAttribute(attributes, SSML_ATTRS.STYLE_DEGREE, SSML_ATTRS.STYLE_DEGREE_CAMEL);
2137
+ const styleDegree = readAttribute(
2138
+ attributes,
2139
+ SSML_ATTRS.STYLE_DEGREE,
2140
+ SSML_ATTRS.STYLE_DEGREE_CAMEL,
2141
+ SSML_ATTRS.STYLE_DEGREE_HYPHEN
2142
+ );
1902
2143
  const role = readAttribute(attributes, SSML_ATTRS.ROLE);
1903
2144
  if (style !== void 0) element.style = style;
1904
2145
  if (styleDegree !== void 0) element.styleDegree = styleDegree;
@@ -2648,148 +2889,7 @@ function formatXmlFragment(xml) {
2648
2889
  return preserveTrailingLineBreak(unwrapFormattedFragment(formatted) ?? xml, trailingLineBreaks);
2649
2890
  }
2650
2891
 
2651
- // packages/ssml-editor-react/src/ssmlInsertion.ts
2652
- function isLineStart(value, offset) {
2653
- return offset === 0 || value[offset - 1] === "\n" || value[offset - 1] === "\r";
2654
- }
2655
- function isLineEnd(value, offset) {
2656
- return offset === value.length || value[offset] === "\n" || value[offset] === "\r";
2657
- }
2658
- function getLineBreakAt(value, offset) {
2659
- if (value.startsWith("\r\n", offset)) {
2660
- return "\r\n";
2661
- }
2662
- if (value[offset] === "\n" || value[offset] === "\r") {
2663
- return value[offset];
2664
- }
2665
- return "";
2666
- }
2667
- function startsWithLineBreak(value) {
2668
- return value.startsWith("\n") || value.startsWith("\r");
2669
- }
2670
- function endsWithLineBreak(value) {
2671
- return value.endsWith("\n") || value.endsWith("\r");
2672
- }
2673
- function createSsmlInsertionEdit(source, startOffset, endOffset, template, eol = "\n", selectedText = startOffset === endOffset ? "" : source.slice(startOffset, endOffset)) {
2674
- if (template.mode === SSML_INSERTION_MODES.wrap) {
2675
- const trailingLineBreak2 = getLineBreakAt(source, endOffset) === "" ? eol : "";
2676
- return {
2677
- replacement: `${template.prefix}${selectedText}${template.suffix}${trailingLineBreak2}`,
2678
- selectionOffset: template.prefix.length
2679
- };
2680
- }
2681
- const followingLineBreak = getLineBreakAt(source, endOffset);
2682
- const leadingLineBreak = !isLineStart(source, startOffset) && !startsWithLineBreak(template.prefix) ? eol : "";
2683
- const needsTrailingLineBreak = selectedText.length > 0 ? !selectedText.startsWith("\n") && !selectedText.startsWith("\r") : !isLineEnd(source, endOffset) || followingLineBreak === "";
2684
- const trailingLineBreak = needsTrailingLineBreak && !endsWithLineBreak(template.prefix) ? eol : "";
2685
- const insertionPrefix = `${leadingLineBreak}${template.prefix}`;
2686
- return {
2687
- replacement: `${insertionPrefix}${trailingLineBreak}${selectedText}`,
2688
- selectionOffset: insertionPrefix.length + trailingLineBreak.length + (selectedText.length === 0 && trailingLineBreak === "" ? followingLineBreak.length : 0)
2689
- };
2690
- }
2691
-
2692
- // packages/ssml-editor-react/src/ssmlContext.ts
2693
- function findTagEnd2(source, start, limit) {
2694
- let quote;
2695
- for (let index = start + 1; index < limit; index += 1) {
2696
- const character = source[index];
2697
- if (quote !== void 0) {
2698
- if (character === quote) {
2699
- quote = void 0;
2700
- }
2701
- continue;
2702
- }
2703
- if (character === '"' || character === "'") {
2704
- quote = character;
2705
- continue;
2706
- }
2707
- if (character === ">") {
2708
- return index;
2709
- }
2710
- }
2711
- return -1;
2712
- }
2713
- function getVoiceName(tag) {
2714
- return tag.match(/\bname\s*=\s*(["'])([\s\S]*?)\1/i)?.[2];
2715
- }
2716
- function closeElement(stack, name) {
2717
- for (let index = stack.length - 1; index >= 0; index -= 1) {
2718
- if (stack[index]?.name === name) {
2719
- stack.splice(index);
2720
- return;
2721
- }
2722
- }
2723
- }
2724
- function findSsmlVoiceContext(source, offset) {
2725
- const limit = Math.max(0, Math.min(offset, source.length));
2726
- const stack = [];
2727
- let index = 0;
2728
- while (index < limit) {
2729
- const tagStart = source.indexOf("<", index);
2730
- if (tagStart === -1 || tagStart >= limit) {
2731
- break;
2732
- }
2733
- if (source.startsWith("<!--", tagStart)) {
2734
- const end = source.indexOf("-->", tagStart + 4);
2735
- index = end === -1 || end + 3 > limit ? limit : end + 3;
2736
- continue;
2737
- }
2738
- if (source.startsWith("<![CDATA[", tagStart)) {
2739
- const end = source.indexOf("]]>", tagStart + 9);
2740
- index = end === -1 || end + 3 > limit ? limit : end + 3;
2741
- continue;
2742
- }
2743
- if (source.startsWith("<?", tagStart)) {
2744
- const end = source.indexOf("?>", tagStart + 2);
2745
- index = end === -1 || end + 2 > limit ? limit : end + 2;
2746
- continue;
2747
- }
2748
- const tagEnd = findTagEnd2(source, tagStart, limit);
2749
- if (tagEnd === -1) {
2750
- break;
2751
- }
2752
- const tag = source.slice(tagStart, tagEnd + 1);
2753
- if (tag.startsWith("<!")) {
2754
- index = tagEnd + 1;
2755
- continue;
2756
- }
2757
- const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
2758
- if (closingMatch?.[1]) {
2759
- closeElement(stack, closingMatch[1].toLowerCase());
2760
- index = tagEnd + 1;
2761
- continue;
2762
- }
2763
- const openingMatch = tag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
2764
- if (openingMatch?.[1] && !/\/\s*>$/.test(tag)) {
2765
- const name = openingMatch[1].toLowerCase();
2766
- stack.push({
2767
- name,
2768
- ...name === "voice" ? { voiceName: getVoiceName(tag) } : {}
2769
- });
2770
- }
2771
- index = tagEnd + 1;
2772
- }
2773
- for (let stackIndex = stack.length - 1; stackIndex >= 0; stackIndex -= 1) {
2774
- const element = stack[stackIndex];
2775
- if (element?.name === "voice") {
2776
- return element.voiceName === void 0 ? {} : { voiceName: element.voiceName };
2777
- }
2778
- }
2779
- return void 0;
2780
- }
2781
-
2782
- // packages/ssml-editor-react/src/hooks/useSsmlEditorState.ts
2783
- var TIMING_INSERTION_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence"]);
2784
- var PROSODY_INSERTION_TAGS = /* @__PURE__ */ new Set(["prosody", "mstts:express-as", "voice", "emphasis"]);
2785
- var TEXT_INSERTION_TAGS = /* @__PURE__ */ new Set(["sub", "say-as", "phoneme", "w", "lang"]);
2786
- var EMPTY_SELECTION_OVERLAY = {
2787
- selectedText: "",
2788
- characterCount: 0,
2789
- hasSelection: false,
2790
- position: null,
2791
- placement: "above"
2792
- };
2892
+ // packages/ssml-editor-react/src/editableSsml.ts
2793
2893
  function isSsmlElement(node) {
2794
2894
  return typeof node !== "string" && node.type !== "text";
2795
2895
  }
@@ -2884,48 +2984,6 @@ function preserveEmptyPairElements(nodes, startTags, startTagIndex) {
2884
2984
  function getDocumentChildren2(document2) {
2885
2985
  return document2.children ?? (document2.content === void 0 ? [] : [document2.content]);
2886
2986
  }
2887
- function getPartialContext(document2) {
2888
- const children = getDocumentChildren2(document2);
2889
- const voice = findFirstElement(children, isVoice);
2890
- const prosody = findFirstElement(children, isProsody);
2891
- const context = {
2892
- version: document2.version,
2893
- lang: document2.lang
2894
- };
2895
- if (voice) {
2896
- context.voice = {
2897
- name: voice.name,
2898
- effect: voice.effect,
2899
- attributes: voice.attributes
2900
- };
2901
- }
2902
- if (prosody) {
2903
- context.prosody = {
2904
- rate: prosody.rate,
2905
- pitch: prosody.pitch,
2906
- volume: prosody.volume,
2907
- contour: prosody.contour,
2908
- range: prosody.range,
2909
- attributes: prosody.attributes
2910
- };
2911
- }
2912
- return context;
2913
- }
2914
- function findFirstElement(nodes, predicate) {
2915
- for (const node of nodes) {
2916
- if (!isSsmlElement(node)) {
2917
- continue;
2918
- }
2919
- if (predicate(node)) {
2920
- return node;
2921
- }
2922
- const child = findFirstElement(node.children ?? [], predicate);
2923
- if (child) {
2924
- return child;
2925
- }
2926
- }
2927
- return void 0;
2928
- }
2929
2987
  function findFirstElementPath(nodes, predicate, ancestors = []) {
2930
2988
  for (const node of nodes) {
2931
2989
  if (!isSsmlElement(node)) {
@@ -3006,13 +3064,10 @@ function getEditableRegion(document2) {
3006
3064
  ...voice ? { voiceName: voice.name } : {}
3007
3065
  };
3008
3066
  }
3009
- function getEditableChildren(document2) {
3010
- return getEditableRegion(document2).children;
3011
- }
3012
3067
  function getEditableText(document2) {
3013
- return serializeEditableText(getEditableChildren(document2), document2.lang);
3068
+ return serializeEditableText(getEditableRegion(document2).children, document2.lang);
3014
3069
  }
3015
- function updateText(document2, value) {
3070
+ function updateEditableText(document2, value) {
3016
3071
  const nextChildren = parseEditableText(value, document2.lang);
3017
3072
  const editableChildren = nextChildren.length > 0 ? nextChildren : [value];
3018
3073
  const children = getDocumentChildren2(document2);
@@ -3032,6 +3087,261 @@ function updateText(document2, value) {
3032
3087
  }
3033
3088
  return withChildren(document2, editableChildren);
3034
3089
  }
3090
+
3091
+ // packages/ssml-editor-react/src/ssmlInsertion.ts
3092
+ function isLineStart(value, offset) {
3093
+ return offset === 0 || value[offset - 1] === "\n" || value[offset - 1] === "\r";
3094
+ }
3095
+ function isLineEnd(value, offset) {
3096
+ return offset === value.length || value[offset] === "\n" || value[offset] === "\r";
3097
+ }
3098
+ function getLineBreakAt(value, offset) {
3099
+ if (value.startsWith("\r\n", offset)) {
3100
+ return "\r\n";
3101
+ }
3102
+ if (value[offset] === "\n" || value[offset] === "\r") {
3103
+ return value[offset];
3104
+ }
3105
+ return "";
3106
+ }
3107
+ function startsWithLineBreak(value) {
3108
+ return value.startsWith("\n") || value.startsWith("\r");
3109
+ }
3110
+ function endsWithLineBreak(value) {
3111
+ return value.endsWith("\n") || value.endsWith("\r");
3112
+ }
3113
+ function createSsmlInsertionEdit(source, startOffset, endOffset, template, eol = "\n", selectedText = startOffset === endOffset ? "" : source.slice(startOffset, endOffset)) {
3114
+ if (template.mode === SSML_INSERTION_MODES.wrap) {
3115
+ const trailingLineBreak2 = getLineBreakAt(source, endOffset) === "" ? eol : "";
3116
+ return {
3117
+ replacement: `${template.prefix}${selectedText}${template.suffix}${trailingLineBreak2}`,
3118
+ selectionOffset: template.prefix.length
3119
+ };
3120
+ }
3121
+ const followingLineBreak = getLineBreakAt(source, endOffset);
3122
+ const leadingLineBreak = !isLineStart(source, startOffset) && !startsWithLineBreak(template.prefix) ? eol : "";
3123
+ const needsTrailingLineBreak = selectedText.length > 0 ? !selectedText.startsWith("\n") && !selectedText.startsWith("\r") : !isLineEnd(source, endOffset) || followingLineBreak === "";
3124
+ const trailingLineBreak = needsTrailingLineBreak && !endsWithLineBreak(template.prefix) ? eol : "";
3125
+ const insertionPrefix = `${leadingLineBreak}${template.prefix}`;
3126
+ return {
3127
+ replacement: `${insertionPrefix}${trailingLineBreak}${selectedText}`,
3128
+ selectionOffset: insertionPrefix.length + trailingLineBreak.length + (selectedText.length === 0 && trailingLineBreak === "" ? followingLineBreak.length : 0)
3129
+ };
3130
+ }
3131
+
3132
+ // packages/ssml-editor-react/src/ssmlContext.ts
3133
+ function findTagEnd2(source, start, limit) {
3134
+ let quote;
3135
+ for (let index = start + 1; index < limit; index += 1) {
3136
+ const character = source[index];
3137
+ if (quote !== void 0) {
3138
+ if (character === quote) {
3139
+ quote = void 0;
3140
+ }
3141
+ continue;
3142
+ }
3143
+ if (character === '"' || character === "'") {
3144
+ quote = character;
3145
+ continue;
3146
+ }
3147
+ if (character === ">") {
3148
+ return index;
3149
+ }
3150
+ }
3151
+ return -1;
3152
+ }
3153
+ function getVoiceName(tag) {
3154
+ return tag.match(/\bname\s*=\s*(["'])([\s\S]*?)\1/i)?.[2];
3155
+ }
3156
+ function escapeXmlAttribute(value) {
3157
+ return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "&apos;");
3158
+ }
3159
+ function updateTagAttribute(text, tagRange, attributeName, newValue) {
3160
+ const start = Math.max(0, Math.min(tagRange.start, text.length));
3161
+ const end = Math.max(start, Math.min(tagRange.end, text.length));
3162
+ const tag = text.slice(start, end);
3163
+ const escapedAttributeName = attributeName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3164
+ const attributePattern = new RegExp(`(\\b${escapedAttributeName}\\s*=\\s*)(["'])([\\s\\S]*?)\\2`, "i");
3165
+ const escapedValue = escapeXmlAttribute(newValue);
3166
+ const match = tag.match(attributePattern);
3167
+ if (match?.index !== void 0) {
3168
+ const valueStart = match.index + match[0].indexOf(match[2]) + 1;
3169
+ const valueEnd = valueStart + (match[3]?.length ?? 0);
3170
+ return `${text.slice(0, start)}${tag.slice(0, valueStart)}${escapedValue}${tag.slice(valueEnd)}${text.slice(end)}`;
3171
+ }
3172
+ const insertionIndex = tag.endsWith("/>") ? tag.length - 2 : tag.length - 1;
3173
+ const attribute = ` ${attributeName}="${escapedValue}"`;
3174
+ return `${text.slice(0, start)}${tag.slice(0, insertionIndex)}${attribute}${tag.slice(insertionIndex)}${text.slice(end)}`;
3175
+ }
3176
+ function closeElement(stack, name) {
3177
+ for (let index = stack.length - 1; index >= 0; index -= 1) {
3178
+ if (stack[index]?.name === name) {
3179
+ stack.splice(index);
3180
+ return;
3181
+ }
3182
+ }
3183
+ }
3184
+ function findActiveSsmlTags(source, offset) {
3185
+ const limit = Math.max(0, Math.min(offset, source.length));
3186
+ const stack = [];
3187
+ let index = 0;
3188
+ while (index < source.length) {
3189
+ const tagStart = source.indexOf("<", index);
3190
+ if (tagStart === -1 || tagStart > limit) {
3191
+ break;
3192
+ }
3193
+ const nonContentEnd = source.startsWith("<!--", tagStart) ? source.indexOf("-->", tagStart + 4) : source.startsWith("<![CDATA[", tagStart) ? source.indexOf("]]>", tagStart + 9) : source.startsWith("<?", tagStart) ? source.indexOf("?>", tagStart + 2) : void 0;
3194
+ if (nonContentEnd !== void 0) {
3195
+ const delimiterLength = source.startsWith("<?", tagStart) ? 2 : 3;
3196
+ const end = nonContentEnd === -1 ? source.length : nonContentEnd + delimiterLength;
3197
+ if (limit < end) {
3198
+ break;
3199
+ }
3200
+ index = end;
3201
+ continue;
3202
+ }
3203
+ const tagEnd = findTagEnd2(source, tagStart, source.length);
3204
+ const tag = source.slice(tagStart, tagEnd === -1 ? source.length : tagEnd + 1);
3205
+ const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
3206
+ const openingMatch = tag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
3207
+ if (tagEnd === -1 || limit <= tagEnd) {
3208
+ if (openingMatch?.[1]) {
3209
+ stack.push({ name: openingMatch[1].toLowerCase() });
3210
+ }
3211
+ break;
3212
+ }
3213
+ if (closingMatch?.[1]) {
3214
+ closeElement(stack, closingMatch[1].toLowerCase());
3215
+ } else if (openingMatch?.[1] && !/\/\s*>$/.test(tag)) {
3216
+ stack.push({ name: openingMatch[1].toLowerCase() });
3217
+ }
3218
+ index = tagEnd + 1;
3219
+ }
3220
+ return new Set(stack.map(({ name }) => name));
3221
+ }
3222
+ function findSsmlVoiceContext(source, offset) {
3223
+ const limit = Math.max(0, Math.min(offset, source.length));
3224
+ const stack = [];
3225
+ let index = 0;
3226
+ while (index < limit) {
3227
+ const tagStart = source.indexOf("<", index);
3228
+ if (tagStart === -1 || tagStart >= limit) {
3229
+ break;
3230
+ }
3231
+ if (source.startsWith("<!--", tagStart)) {
3232
+ const end = source.indexOf("-->", tagStart + 4);
3233
+ index = end === -1 || end + 3 > limit ? limit : end + 3;
3234
+ continue;
3235
+ }
3236
+ if (source.startsWith("<![CDATA[", tagStart)) {
3237
+ const end = source.indexOf("]]>", tagStart + 9);
3238
+ index = end === -1 || end + 3 > limit ? limit : end + 3;
3239
+ continue;
3240
+ }
3241
+ if (source.startsWith("<?", tagStart)) {
3242
+ const end = source.indexOf("?>", tagStart + 2);
3243
+ index = end === -1 || end + 2 > limit ? limit : end + 2;
3244
+ continue;
3245
+ }
3246
+ const tagEnd = findTagEnd2(source, tagStart, limit);
3247
+ if (tagEnd === -1) {
3248
+ break;
3249
+ }
3250
+ const tag = source.slice(tagStart, tagEnd + 1);
3251
+ if (tag.startsWith("<!")) {
3252
+ index = tagEnd + 1;
3253
+ continue;
3254
+ }
3255
+ const closingMatch = tag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
3256
+ if (closingMatch?.[1]) {
3257
+ closeElement(stack, closingMatch[1].toLowerCase());
3258
+ index = tagEnd + 1;
3259
+ continue;
3260
+ }
3261
+ const openingMatch = tag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
3262
+ if (openingMatch?.[1] && !/\/\s*>$/.test(tag)) {
3263
+ const name = openingMatch[1].toLowerCase();
3264
+ stack.push({
3265
+ name,
3266
+ ...name === "voice" ? { voiceName: getVoiceName(tag) } : {}
3267
+ });
3268
+ }
3269
+ index = tagEnd + 1;
3270
+ }
3271
+ for (let stackIndex = stack.length - 1; stackIndex >= 0; stackIndex -= 1) {
3272
+ const element = stack[stackIndex];
3273
+ if (element?.name === "voice") {
3274
+ return element.voiceName === void 0 ? {} : { voiceName: element.voiceName };
3275
+ }
3276
+ }
3277
+ return void 0;
3278
+ }
3279
+
3280
+ // packages/ssml-editor-react/src/hooks/useSsmlEditorState.ts
3281
+ var TIMING_INSERTION_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence"]);
3282
+ var PROSODY_INSERTION_TAGS = /* @__PURE__ */ new Set(["prosody", "mstts:express-as", "voice", "emphasis"]);
3283
+ var TEXT_INSERTION_TAGS = /* @__PURE__ */ new Set(["sub", "say-as", "phoneme", "w", "lang"]);
3284
+ var EMPTY_SELECTION_OVERLAY = {
3285
+ selectedText: "",
3286
+ characterCount: 0,
3287
+ hasSelection: false,
3288
+ position: null,
3289
+ placement: "above"
3290
+ };
3291
+ function isSsmlElement2(node) {
3292
+ return typeof node !== "string" && node.type !== "text";
3293
+ }
3294
+ function isVoice2(element) {
3295
+ return element.type === "voice";
3296
+ }
3297
+ function isProsody2(element) {
3298
+ return element.type === "prosody";
3299
+ }
3300
+ function getDocumentChildren3(document2) {
3301
+ return document2.children ?? (document2.content === void 0 ? [] : [document2.content]);
3302
+ }
3303
+ function getPartialContext(document2) {
3304
+ const children = getDocumentChildren3(document2);
3305
+ const voice = findFirstElement(children, isVoice2);
3306
+ const prosody = findFirstElement(children, isProsody2);
3307
+ const context = {
3308
+ version: document2.version,
3309
+ lang: document2.lang
3310
+ };
3311
+ if (voice) {
3312
+ context.voice = {
3313
+ name: voice.name,
3314
+ effect: voice.effect,
3315
+ attributes: voice.attributes
3316
+ };
3317
+ }
3318
+ if (prosody) {
3319
+ context.prosody = {
3320
+ rate: prosody.rate,
3321
+ pitch: prosody.pitch,
3322
+ volume: prosody.volume,
3323
+ contour: prosody.contour,
3324
+ range: prosody.range,
3325
+ attributes: prosody.attributes
3326
+ };
3327
+ }
3328
+ return context;
3329
+ }
3330
+ function findFirstElement(nodes, predicate) {
3331
+ for (const node of nodes) {
3332
+ if (!isSsmlElement2(node)) {
3333
+ continue;
3334
+ }
3335
+ if (predicate(node)) {
3336
+ return node;
3337
+ }
3338
+ const child = findFirstElement(node.children ?? [], predicate);
3339
+ if (child) {
3340
+ return child;
3341
+ }
3342
+ }
3343
+ return void 0;
3344
+ }
3035
3345
  function getSelectionInfo(editor) {
3036
3346
  const model = editor.getModel();
3037
3347
  const selection = editor.getSelection();
@@ -3167,9 +3477,11 @@ function useSsmlEditorState({
3167
3477
  const [decorationsVisible, setDecorationsVisible] = (0, import_react.useState)(showDecorations);
3168
3478
  const [isHelpOpen, setIsHelpOpen] = (0, import_react.useState)(false);
3169
3479
  const [syntaxError, setSyntaxError] = (0, import_react.useState)(null);
3480
+ const [activeTags, setActiveTags] = (0, import_react.useState)(() => /* @__PURE__ */ new Set());
3170
3481
  const [openPopoverId, setOpenPopoverId] = (0, import_react.useState)(null);
3171
3482
  const [popoverVoiceName, setPopoverVoiceName] = (0, import_react.useState)(void 0);
3172
3483
  const [popoverPosition, setPopoverPosition] = (0, import_react.useState)(null);
3484
+ const pendingCodeLensAttributeRef = (0, import_react.useRef)(null);
3173
3485
  const helpPanelId = (0, import_react.useId)();
3174
3486
  const draftDocumentRef = (0, import_react.useRef)(document2);
3175
3487
  const onSelectionChangeRef = (0, import_react.useRef)(onSelectionChange);
@@ -3195,6 +3507,7 @@ function useSsmlEditorState({
3195
3507
  setDecorationsVisible(showDecorations);
3196
3508
  }, [showDecorations]);
3197
3509
  const closePopover = (0, import_react.useCallback)((restoreFocus = false) => {
3510
+ pendingCodeLensAttributeRef.current = null;
3198
3511
  setOpenPopoverId(null);
3199
3512
  setPopoverVoiceName(void 0);
3200
3513
  setPopoverPosition(null);
@@ -3203,6 +3516,7 @@ function useSsmlEditorState({
3203
3516
  }
3204
3517
  }, []);
3205
3518
  const togglePopover = (0, import_react.useCallback)((id, trigger) => {
3519
+ pendingCodeLensAttributeRef.current = null;
3206
3520
  setOpenPopoverId((currentId) => {
3207
3521
  if (currentId === id) {
3208
3522
  setPopoverVoiceName(void 0);
@@ -3226,13 +3540,41 @@ function useSsmlEditorState({
3226
3540
  }
3227
3541
  const updatePopoverPosition = () => {
3228
3542
  const trigger = activePopoverTriggerRef.current;
3229
- if (!trigger) {
3543
+ if (trigger) {
3544
+ const triggerBounds = trigger.getBoundingClientRect();
3545
+ setPopoverPosition({
3546
+ top: triggerBounds.bottom + 4,
3547
+ left: triggerBounds.left
3548
+ });
3549
+ return;
3550
+ }
3551
+ const pending = pendingCodeLensAttributeRef.current;
3552
+ const editor = editorRef.current;
3553
+ const model = editor?.getModel();
3554
+ if (!pending || !editor || !model) {
3555
+ return;
3556
+ }
3557
+ const position = model.getPositionAt(pending.tagRange.start);
3558
+ const visiblePosition = editor.getScrolledVisiblePosition(position);
3559
+ const editorElement = editor.getDomNode();
3560
+ if (!visiblePosition || !editorElement) {
3230
3561
  return;
3231
3562
  }
3232
- const triggerBounds = trigger.getBoundingClientRect();
3563
+ const editorBounds = editorElement.getBoundingClientRect();
3564
+ const menu = activePopoverMenuRef.current;
3565
+ const margin = 8;
3233
3566
  setPopoverPosition({
3234
- top: triggerBounds.bottom + 4,
3235
- left: triggerBounds.left
3567
+ top: Math.max(
3568
+ margin,
3569
+ Math.min(
3570
+ editorBounds.top + visiblePosition.top + visiblePosition.height + 4,
3571
+ window.innerHeight - (menu?.offsetHeight ?? 0) - margin
3572
+ )
3573
+ ),
3574
+ left: Math.max(
3575
+ margin,
3576
+ Math.min(editorBounds.left + visiblePosition.left, window.innerWidth - (menu?.offsetWidth ?? 0) - margin)
3577
+ )
3236
3578
  });
3237
3579
  };
3238
3580
  const handlePointerDown = (event) => {
@@ -3269,6 +3611,14 @@ function useSsmlEditorState({
3269
3611
  });
3270
3612
  }
3271
3613
  }, []);
3614
+ const updateActiveTags = (0, import_react.useCallback)((nextTags) => {
3615
+ setActiveTags((currentTags) => {
3616
+ if (currentTags.size === nextTags.size && [...currentTags].every((tag) => nextTags.has(tag))) {
3617
+ return currentTags;
3618
+ }
3619
+ return new Set(nextTags);
3620
+ });
3621
+ }, []);
3272
3622
  const commit = (0, import_react.useCallback)(
3273
3623
  (nextDocument) => {
3274
3624
  draftDocumentRef.current = nextDocument;
@@ -3280,7 +3630,7 @@ function useSsmlEditorState({
3280
3630
  );
3281
3631
  const handleTextChange = (0, import_react.useCallback)(
3282
3632
  (value) => {
3283
- commit(updateText(draftDocumentRef.current, value));
3633
+ commit(updateEditableText(draftDocumentRef.current, value));
3284
3634
  if (editorRef.current) {
3285
3635
  refreshSelectionOverlay(editorRef.current, false);
3286
3636
  }
@@ -3289,11 +3639,48 @@ function useSsmlEditorState({
3289
3639
  );
3290
3640
  const handleInsert = (0, import_react.useCallback)(
3291
3641
  (insertion, option) => {
3292
- if (editorRef.current) {
3293
- applySsmlInsertion(editorRef.current, insertion, option);
3642
+ const editor = editorRef.current;
3643
+ const pending = pendingCodeLensAttributeRef.current;
3644
+ if (!editor) {
3645
+ return;
3294
3646
  }
3647
+ if (pending?.insertionId === insertion.id) {
3648
+ const model = editor.getModel();
3649
+ if (model && model.getVersionId() === pending.modelVersionId) {
3650
+ const source = model.getValue();
3651
+ const tagText = source.slice(pending.tagRange.start, pending.tagRange.end);
3652
+ const updatedTag = updateTagAttribute(
3653
+ tagText,
3654
+ { start: 0, end: tagText.length },
3655
+ pending.attributeName,
3656
+ option.value
3657
+ );
3658
+ const start = model.getPositionAt(pending.tagRange.start);
3659
+ const end = model.getPositionAt(pending.tagRange.end);
3660
+ editor.pushUndoStop();
3661
+ editor.executeEdits("ssml-code-lens", [
3662
+ {
3663
+ range: {
3664
+ startLineNumber: start.lineNumber,
3665
+ startColumn: start.column,
3666
+ endLineNumber: end.lineNumber,
3667
+ endColumn: end.column
3668
+ },
3669
+ text: updatedTag
3670
+ }
3671
+ ]);
3672
+ editor.pushUndoStop();
3673
+ editor.focus();
3674
+ }
3675
+ pendingCodeLensAttributeRef.current = null;
3676
+ if (!model || model.getVersionId() !== pending.modelVersionId) {
3677
+ closePopover();
3678
+ }
3679
+ return;
3680
+ }
3681
+ applySsmlInsertion(editor, insertion, option);
3295
3682
  },
3296
- []
3683
+ [closePopover]
3297
3684
  );
3298
3685
  const handleInsertBreak = (0, import_react.useCallback)(
3299
3686
  (insertion, option) => {
@@ -3324,7 +3711,7 @@ function useSsmlEditorState({
3324
3711
  }, [commit]);
3325
3712
  const handleFormat = (0, import_react.useCallback)(() => {
3326
3713
  const value = editorRef.current?.getValue() ?? getEditableText(draftDocumentRef.current);
3327
- commit(updateText(draftDocumentRef.current, formatXmlFragment(value)));
3714
+ commit(updateEditableText(draftDocumentRef.current, formatXmlFragment(value)));
3328
3715
  }, [commit]);
3329
3716
  const handleUndo = (0, import_react.useCallback)(() => {
3330
3717
  editorRef.current?.trigger("toolbar", "undo", null);
@@ -3354,16 +3741,65 @@ function useSsmlEditorState({
3354
3741
  );
3355
3742
  const getFullSsml = (0, import_react.useCallback)(
3356
3743
  () => buildSsml(
3357
- editorRef.current ? updateText(draftDocumentRef.current, editorRef.current.getValue()) : draftDocumentRef.current
3744
+ editorRef.current ? updateEditableText(draftDocumentRef.current, editorRef.current.getValue()) : draftDocumentRef.current
3358
3745
  ),
3359
3746
  []
3360
3747
  );
3361
3748
  const getOuterVoiceName = (0, import_react.useCallback)(() => getEditableRegion(draftDocumentRef.current).voiceName, []);
3749
+ const handleCodeLensAction = (0, import_react.useCallback)((action) => {
3750
+ const editor = editorRef.current;
3751
+ const model = editor?.getModel();
3752
+ if (!editor || !model) {
3753
+ return;
3754
+ }
3755
+ if (action.type === "attribute") {
3756
+ pendingCodeLensAttributeRef.current = {
3757
+ insertionId: action.insertionId,
3758
+ attributeName: action.attributeName,
3759
+ tagRange: action.tagRange,
3760
+ modelVersionId: model.getVersionId()
3761
+ };
3762
+ activePopoverTriggerRef.current = null;
3763
+ setPopoverVoiceName(getEffectiveVoiceName(editor, draftDocumentRef.current));
3764
+ setOpenPopoverId(action.insertionId);
3765
+ setPopoverPosition(null);
3766
+ return;
3767
+ }
3768
+ const source = model.getValue();
3769
+ const toEditorRange = (range) => {
3770
+ const start = model.getPositionAt(range.start);
3771
+ const end = model.getPositionAt(range.end);
3772
+ return {
3773
+ startLineNumber: start.lineNumber,
3774
+ startColumn: start.column,
3775
+ endLineNumber: end.lineNumber,
3776
+ endColumn: end.column
3777
+ };
3778
+ };
3779
+ const edits = action.type === "delete" ? [{ range: toEditorRange(action.tagRange), text: "" }] : (() => {
3780
+ const elementText = source.slice(action.elementRange.start, action.elementRange.end);
3781
+ const closingOffset = elementText.search(/<\/\s*prosody\s*>/i);
3782
+ const closingStart = closingOffset === -1 ? action.elementRange.end : action.elementRange.start + closingOffset;
3783
+ const closingEnd = closingOffset === -1 ? action.elementRange.end : source.indexOf(">", closingStart) === -1 ? action.elementRange.end : source.indexOf(">", closingStart) + 1;
3784
+ return [
3785
+ { range: toEditorRange(action.tagRange), text: "" },
3786
+ { range: toEditorRange({ start: closingStart, end: closingEnd }), text: "" }
3787
+ ];
3788
+ })();
3789
+ editor.pushUndoStop();
3790
+ editor.executeEdits("ssml-code-lens", edits);
3791
+ editor.pushUndoStop();
3792
+ pendingCodeLensAttributeRef.current = null;
3793
+ setOpenPopoverId(null);
3794
+ setPopoverPosition(null);
3795
+ editor.focus();
3796
+ }, []);
3362
3797
  return {
3363
3798
  editorRef,
3364
3799
  draftDocument,
3365
3800
  text: getEditableText(draftDocument),
3366
3801
  selectionOverlay,
3802
+ activeTags,
3367
3803
  isDarkTheme: resolvedTheme === "dark" || resolvedTheme === "system" && isDark,
3368
3804
  decorationsVisible,
3369
3805
  setDecorationsVisible,
@@ -3385,10 +3821,12 @@ function useSsmlEditorState({
3385
3821
  previewSelection,
3386
3822
  canPreviewSelection: onPreviewSelection !== void 0,
3387
3823
  refreshSelectionOverlay,
3824
+ updateActiveTags,
3388
3825
  getSelectedSsml: getSelectedSsmlValue,
3389
3826
  getCurrentLineSsml: getCurrentLineSsmlValue,
3390
3827
  getFullSsml,
3391
3828
  getOuterVoiceName,
3829
+ handleCodeLensAction,
3392
3830
  openPopoverId,
3393
3831
  popoverVoiceName,
3394
3832
  popoverPosition,
@@ -3618,7 +4056,8 @@ function registerSsmlCompletionProvider(monaco, options = {}) {
3618
4056
  ...attributeValues?.map((value2) => ({
3619
4057
  label: value2,
3620
4058
  kind: monaco.languages.CompletionItemKind.Value,
3621
- insertText: value2
4059
+ insertText: value2,
4060
+ range
3622
4061
  })) ?? []
3623
4062
  ]
3624
4063
  };
@@ -3736,6 +4175,180 @@ function registerSsmlCodeActions(monaco) {
3736
4175
  });
3737
4176
  }
3738
4177
 
4178
+ // packages/ssml-editor-react/src/ssmlCodeLens.ts
4179
+ var CODE_LENS_COMMAND = "ssml-editor.codeLens";
4180
+ function findTagEnd3(source, start) {
4181
+ let quote;
4182
+ for (let index = start + 1; index < source.length; index += 1) {
4183
+ const character = source[index];
4184
+ if (quote !== void 0) {
4185
+ if (character === quote) {
4186
+ quote = void 0;
4187
+ }
4188
+ } else if (character === '"' || character === "'") {
4189
+ quote = character;
4190
+ } else if (character === ">") {
4191
+ return index;
4192
+ }
4193
+ }
4194
+ return -1;
4195
+ }
4196
+ function getAttributeValue(tag, attributeName) {
4197
+ const escapedName = attributeName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4198
+ return tag.match(new RegExp(`\\b${escapedName}\\s*=\\s*(["'])([\\s\\S]*?)\\1`, "i"))?.[2];
4199
+ }
4200
+ function getTagRange(start, end) {
4201
+ return { start, end };
4202
+ }
4203
+ function getElementEnd(source, tagName, tagEnd) {
4204
+ const openingTag = source.slice(0, tagEnd + 1);
4205
+ if (/\/\s*>$/.test(openingTag)) {
4206
+ return tagEnd + 1;
4207
+ }
4208
+ let depth = 1;
4209
+ let index = tagEnd + 1;
4210
+ while (index < source.length) {
4211
+ const nextStart = source.indexOf("<", index);
4212
+ if (nextStart === -1) {
4213
+ break;
4214
+ }
4215
+ if (source.startsWith("<!--", nextStart)) {
4216
+ index = source.indexOf("-->", nextStart + 4);
4217
+ index = index === -1 ? source.length : index + 3;
4218
+ continue;
4219
+ }
4220
+ const nextEnd = findTagEnd3(source, nextStart);
4221
+ if (nextEnd === -1) {
4222
+ break;
4223
+ }
4224
+ const nextTag = source.slice(nextStart, nextEnd + 1);
4225
+ const closingMatch = nextTag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
4226
+ const openingMatch = nextTag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
4227
+ if (closingMatch?.[1]?.toLowerCase() === tagName) {
4228
+ depth -= 1;
4229
+ if (depth === 0) {
4230
+ return nextEnd + 1;
4231
+ }
4232
+ } else if (openingMatch?.[1]?.toLowerCase() === tagName && !/\/\s*>$/.test(nextTag)) {
4233
+ depth += 1;
4234
+ }
4235
+ index = nextEnd + 1;
4236
+ }
4237
+ return tagEnd + 1;
4238
+ }
4239
+ function createLens(model, start, end, title, action) {
4240
+ const startPosition = model.getPositionAt(start);
4241
+ const endPosition = model.getPositionAt(end);
4242
+ return {
4243
+ range: {
4244
+ startLineNumber: startPosition.lineNumber,
4245
+ startColumn: startPosition.column,
4246
+ endLineNumber: endPosition.lineNumber,
4247
+ endColumn: endPosition.column
4248
+ },
4249
+ command: {
4250
+ id: CODE_LENS_COMMAND,
4251
+ title,
4252
+ arguments: [action]
4253
+ }
4254
+ };
4255
+ }
4256
+ function registerSsmlCodeLens(monaco, editor, onOpenPopover) {
4257
+ const provider = {
4258
+ provideCodeLenses(model) {
4259
+ const source = model.getValue();
4260
+ const lenses = [];
4261
+ let index = 0;
4262
+ while (index < source.length) {
4263
+ const tagStart = source.indexOf("<", index);
4264
+ if (tagStart === -1) {
4265
+ break;
4266
+ }
4267
+ if (source.startsWith("<!--", tagStart)) {
4268
+ index = source.indexOf("-->", tagStart + 4);
4269
+ index = index === -1 ? source.length : index + 3;
4270
+ continue;
4271
+ }
4272
+ const tagEnd = findTagEnd3(source, tagStart);
4273
+ if (tagEnd === -1) {
4274
+ break;
4275
+ }
4276
+ const tag = source.slice(tagStart, tagEnd + 1);
4277
+ const tagName = tag.match(/^<\s*(prosody|break)\b/i)?.[1]?.toLowerCase();
4278
+ index = tagEnd + 1;
4279
+ if (!tagName) {
4280
+ continue;
4281
+ }
4282
+ const tagRange = getTagRange(tagStart, tagEnd + 1);
4283
+ const elementEnd = getElementEnd(source, tagName, tagEnd);
4284
+ const elementRange = { start: tagStart, end: elementEnd };
4285
+ if (tagName === "prosody") {
4286
+ lenses.push(
4287
+ createLens(
4288
+ model,
4289
+ tagStart,
4290
+ tagEnd + 1,
4291
+ `\u26A1 Rate: ${getAttributeValue(tag, "rate") ?? "default"} (Click to edit)`,
4292
+ { type: "attribute", insertionId: "rate", attributeName: "rate", tagRange }
4293
+ ),
4294
+ createLens(
4295
+ model,
4296
+ tagStart,
4297
+ tagEnd + 1,
4298
+ `\u26A1 Pitch: ${getAttributeValue(tag, "pitch") ?? "default"} (Click to edit)`,
4299
+ { type: "attribute", insertionId: "pitch", attributeName: "pitch", tagRange }
4300
+ ),
4301
+ createLens(model, tagStart, tagEnd + 1, "Unwrap", {
4302
+ type: "unwrap",
4303
+ tagRange,
4304
+ elementRange
4305
+ })
4306
+ );
4307
+ } else if (/\/\s*>$/.test(tag)) {
4308
+ lenses.push(
4309
+ createLens(
4310
+ model,
4311
+ tagStart,
4312
+ tagEnd + 1,
4313
+ `\u26A1 Time: ${getAttributeValue(tag, "time") ?? "default"} (Click to edit)`,
4314
+ { type: "attribute", insertionId: "break", attributeName: "time", tagRange }
4315
+ ),
4316
+ createLens(model, tagStart, tagEnd + 1, "Delete", {
4317
+ type: "delete",
4318
+ tagRange,
4319
+ elementRange
4320
+ })
4321
+ );
4322
+ }
4323
+ }
4324
+ return { lenses, dispose: () => void 0 };
4325
+ }
4326
+ };
4327
+ const disposable = monaco.languages.registerCodeLensProvider("xml", provider);
4328
+ const commandHandler = (_accessor, ...args) => {
4329
+ const action = args[0];
4330
+ if (action && typeof action === "object" && "type" in action) {
4331
+ onOpenPopover(action);
4332
+ }
4333
+ };
4334
+ const commandDisposable = typeof monaco.editor?.registerCommand === "function" ? monaco.editor.registerCommand(CODE_LENS_COMMAND, commandHandler) : typeof monaco.registerCommand === "function" ? monaco.registerCommand(CODE_LENS_COMMAND, commandHandler) : editor.addAction({
4335
+ id: CODE_LENS_COMMAND,
4336
+ label: "SSML CodeLens",
4337
+ run: (_editor, ...args) => {
4338
+ const action = args[0];
4339
+ if (action && typeof action === "object" && "type" in action) {
4340
+ onOpenPopover(action);
4341
+ }
4342
+ }
4343
+ });
4344
+ return {
4345
+ dispose: () => {
4346
+ commandDisposable.dispose();
4347
+ disposable.dispose();
4348
+ }
4349
+ };
4350
+ }
4351
+
3739
4352
  // packages/ssml-editor-react/src/ssmlHover.ts
3740
4353
  var {
3741
4354
  BREAK_STRENGTH_PRESETS: BREAK_STRENGTH_PRESETS2,
@@ -4085,7 +4698,7 @@ function toRange(source, token) {
4085
4698
  function containsOffset(token, offset) {
4086
4699
  return offset >= token.start && offset < token.end;
4087
4700
  }
4088
- function findTagEnd3(source, start) {
4701
+ function findTagEnd4(source, start) {
4089
4702
  let quote;
4090
4703
  for (let index = start; index < source.length; index += 1) {
4091
4704
  const character = source[index];
@@ -4216,7 +4829,7 @@ function findTagAtOffset(source, offset) {
4216
4829
  searchStart = tokenEnd2;
4217
4830
  continue;
4218
4831
  }
4219
- const tagEnd = findTagEnd3(source, start + 1);
4832
+ const tagEnd = findTagEnd4(source, start + 1);
4220
4833
  const contentEnd = tagEnd ?? source.length;
4221
4834
  const tokenEnd = tagEnd === void 0 ? source.length : tagEnd + 1;
4222
4835
  if (offset < tokenEnd) {
@@ -4432,8 +5045,11 @@ function useSsmlMonaco({
4432
5045
  language,
4433
5046
  text,
4434
5047
  decorationsVisible,
5048
+ enableCodeLens,
4435
5049
  getOuterVoiceName,
5050
+ onOpenPopover,
4436
5051
  onSelectionOverlayChange,
5052
+ onActiveTagsChange,
4437
5053
  onSyntaxErrorChange
4438
5054
  }) {
4439
5055
  const internalEditorRef = (0, import_react2.useRef)(null);
@@ -4441,22 +5057,30 @@ function useSsmlMonaco({
4441
5057
  const monacoRef = (0, import_react2.useRef)(null);
4442
5058
  const completionProviderRef = (0, import_react2.useRef)(null);
4443
5059
  const codeActionProviderRef = (0, import_react2.useRef)(null);
5060
+ const codeLensProviderRef = (0, import_react2.useRef)(null);
4444
5061
  const releaseHoverProviderRef = (0, import_react2.useRef)(null);
4445
5062
  const selectionChangeRef = (0, import_react2.useRef)(null);
5063
+ const cursorPositionChangeRef = (0, import_react2.useRef)(null);
4446
5064
  const diagnosticsChangeRef = (0, import_react2.useRef)(null);
4447
5065
  const diagnosticsTimeoutRef = (0, import_react2.useRef)(null);
4448
5066
  const selectionLayoutDisposablesRef = (0, import_react2.useRef)([]);
4449
5067
  const inlineDecorationIdsRef = (0, import_react2.useRef)([]);
4450
5068
  const languageRef = (0, import_react2.useRef)(language);
4451
5069
  const decorationsVisibleRef = (0, import_react2.useRef)(decorationsVisible);
5070
+ const enableCodeLensRef = (0, import_react2.useRef)(enableCodeLens);
4452
5071
  const getOuterVoiceNameRef = (0, import_react2.useRef)(getOuterVoiceName);
4453
5072
  const onSelectionOverlayChangeRef = (0, import_react2.useRef)(onSelectionOverlayChange);
5073
+ const onActiveTagsChangeRef = (0, import_react2.useRef)(onActiveTagsChange);
4454
5074
  const onSyntaxErrorChangeRef = (0, import_react2.useRef)(onSyntaxErrorChange);
5075
+ const onOpenPopoverRef = (0, import_react2.useRef)(onOpenPopover);
4455
5076
  languageRef.current = language;
4456
5077
  decorationsVisibleRef.current = decorationsVisible;
5078
+ enableCodeLensRef.current = enableCodeLens;
4457
5079
  getOuterVoiceNameRef.current = getOuterVoiceName;
4458
5080
  onSelectionOverlayChangeRef.current = onSelectionOverlayChange;
5081
+ onActiveTagsChangeRef.current = onActiveTagsChange;
4459
5082
  onSyntaxErrorChangeRef.current = onSyntaxErrorChange;
5083
+ onOpenPopoverRef.current = onOpenPopover;
4460
5084
  const runSsmlDiagnostics = (0, import_react2.useCallback)((editor, monaco) => {
4461
5085
  const model = editor.getModel();
4462
5086
  if (!model) {
@@ -4485,6 +5109,15 @@ function useSsmlMonaco({
4485
5109
  diagnosticsChangeRef.current?.dispose();
4486
5110
  diagnosticsChangeRef.current = null;
4487
5111
  }, []);
5112
+ const syncActiveTags = (0, import_react2.useCallback)((editor) => {
5113
+ const model = editor.getModel();
5114
+ const position = editor.getPosition();
5115
+ if (!model || !position) {
5116
+ onActiveTagsChangeRef.current(/* @__PURE__ */ new Set());
5117
+ return;
5118
+ }
5119
+ onActiveTagsChangeRef.current(findActiveSsmlTags(model.getValue(), model.getOffsetAt(position)));
5120
+ }, []);
4488
5121
  const disposeMonacoResources = (0, import_react2.useCallback)(() => {
4489
5122
  clearSsmlDiagnosticsResources();
4490
5123
  const model = editorRef.current?.getModel();
@@ -4494,10 +5127,14 @@ function useSsmlMonaco({
4494
5127
  }
4495
5128
  selectionChangeRef.current?.dispose();
4496
5129
  selectionChangeRef.current = null;
5130
+ cursorPositionChangeRef.current?.dispose();
5131
+ cursorPositionChangeRef.current = null;
4497
5132
  completionProviderRef.current?.dispose();
4498
5133
  completionProviderRef.current = null;
4499
5134
  codeActionProviderRef.current?.dispose();
4500
5135
  codeActionProviderRef.current = null;
5136
+ codeLensProviderRef.current?.dispose();
5137
+ codeLensProviderRef.current = null;
4501
5138
  for (const disposable of selectionLayoutDisposablesRef.current) {
4502
5139
  disposable.dispose();
4503
5140
  }
@@ -4515,6 +5152,9 @@ function useSsmlMonaco({
4515
5152
  selectionChangeRef.current = editor.onDidChangeCursorSelection(() => {
4516
5153
  onSelectionOverlayChangeRef.current(editor, true);
4517
5154
  });
5155
+ cursorPositionChangeRef.current = editor.onDidChangeCursorPosition(() => {
5156
+ syncActiveTags(editor);
5157
+ });
4518
5158
  diagnosticsChangeRef.current = editor.onDidChangeModelContent(() => {
4519
5159
  const model2 = editor.getModel();
4520
5160
  if (model2) {
@@ -4526,6 +5166,7 @@ function useSsmlMonaco({
4526
5166
  inlineDecorationIdsRef.current
4527
5167
  );
4528
5168
  }
5169
+ syncActiveTags(editor);
4529
5170
  scheduleSsmlDiagnostics(editor, monaco);
4530
5171
  });
4531
5172
  selectionLayoutDisposablesRef.current = [
@@ -4538,6 +5179,13 @@ function useSsmlMonaco({
4538
5179
  model: editor.getModel()
4539
5180
  });
4540
5181
  codeActionProviderRef.current = registerSsmlCodeActions(monaco);
5182
+ if (enableCodeLensRef.current) {
5183
+ codeLensProviderRef.current = registerSsmlCodeLens(
5184
+ monaco,
5185
+ editor,
5186
+ (action) => onOpenPopoverRef.current(action)
5187
+ );
5188
+ }
4541
5189
  releaseHoverProviderRef.current = acquireSsmlHoverProvider(monaco, languageRef.current);
4542
5190
  runSsmlDiagnostics(editor, monaco);
4543
5191
  const model = editor.getModel();
@@ -4551,9 +5199,19 @@ function useSsmlMonaco({
4551
5199
  );
4552
5200
  }
4553
5201
  onSelectionOverlayChangeRef.current(editor, true);
5202
+ syncActiveTags(editor);
4554
5203
  },
4555
- [disposeMonacoResources, editorRef, runSsmlDiagnostics, scheduleSsmlDiagnostics]
5204
+ [disposeMonacoResources, editorRef, runSsmlDiagnostics, scheduleSsmlDiagnostics, syncActiveTags]
4556
5205
  );
5206
+ (0, import_react2.useEffect)(() => {
5207
+ const editor = editorRef.current;
5208
+ const monaco = monacoRef.current;
5209
+ if (!editor || !monaco) {
5210
+ return;
5211
+ }
5212
+ codeLensProviderRef.current?.dispose();
5213
+ codeLensProviderRef.current = enableCodeLens ? registerSsmlCodeLens(monaco, editor, (action) => onOpenPopoverRef.current(action)) : null;
5214
+ }, [editorRef, enableCodeLens]);
4557
5215
  (0, import_react2.useEffect)(() => {
4558
5216
  const monaco = monacoRef.current;
4559
5217
  if (!monaco) {
@@ -4594,7 +5252,7 @@ var TEXT_POPOVER_TAGS = /* @__PURE__ */ new Set(["sub", "say-as", "phoneme", "w"
4594
5252
  function localizedText(value) {
4595
5253
  return { ja: value, en: value };
4596
5254
  }
4597
- function escapeXmlAttribute(value) {
5255
+ function escapeXmlAttribute2(value) {
4598
5256
  return value.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "&apos;");
4599
5257
  }
4600
5258
  function createSsmlEditorInsertionDefinition(definition) {
@@ -4611,7 +5269,7 @@ function createSsmlEditorInsertionDefinition(definition) {
4611
5269
  parameterDescription: definition.parameterDescription ?? localizedText(`Selects the value for the <${definition.tagName}> element.`),
4612
5270
  options: definition.options,
4613
5271
  createTemplate: (value) => {
4614
- const attributeValue = definition.attribute ? `${attribute}${escapeXmlAttribute(value)}"` : "";
5272
+ const attributeValue = definition.attribute ? `${attribute}${escapeXmlAttribute2(value)}"` : "";
4615
5273
  if (mode === "wrap") {
4616
5274
  return {
4617
5275
  prefix: `<${definition.tagName}${attributeValue}>`,
@@ -4940,6 +5598,8 @@ var STYLE_CSS = `
4940
5598
  --ssml-editor-border: #d1d5db;
4941
5599
  --ssml-editor-control-bg: #f9fafb;
4942
5600
  --ssml-editor-control-border: #9ca3af;
5601
+ --ssml-editor-active-bg: #dbeafe;
5602
+ --ssml-editor-active-border: #2563eb;
4943
5603
  --ssml-editor-preview-bg: #f3f4f6;
4944
5604
  --ssml-editor-error: #b91c1c;
4945
5605
  --ssml-editor-error-bg: #fef2f2;
@@ -4957,6 +5617,8 @@ var STYLE_CSS = `
4957
5617
  --ssml-editor-border: #374151;
4958
5618
  --ssml-editor-control-bg: #111827;
4959
5619
  --ssml-editor-control-border: #4b5563;
5620
+ --ssml-editor-active-bg: #1e3a8a;
5621
+ --ssml-editor-active-border: #60a5fa;
4960
5622
  --ssml-editor-preview-bg: #111827;
4961
5623
  --ssml-editor-error: #fca5a5;
4962
5624
  --ssml-editor-error-bg: #450a0a;
@@ -5070,6 +5732,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5070
5732
  showToolbarIcons = true,
5071
5733
  showToolbarLabels = false,
5072
5734
  showDecorations = false,
5735
+ enableCodeLens = true,
5073
5736
  buttonVisibility,
5074
5737
  editorOptions,
5075
5738
  settings,
@@ -5176,6 +5839,7 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5176
5839
  editorRef,
5177
5840
  text,
5178
5841
  selectionOverlay,
5842
+ activeTags,
5179
5843
  isDarkTheme,
5180
5844
  decorationsVisible,
5181
5845
  setDecorationsVisible,
@@ -5196,10 +5860,12 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5196
5860
  previewSelection,
5197
5861
  canPreviewSelection,
5198
5862
  refreshSelectionOverlay,
5863
+ updateActiveTags,
5199
5864
  getSelectedSsml: getSelectedSsml2,
5200
5865
  getCurrentLineSsml: getCurrentLineSsml2,
5201
5866
  getFullSsml,
5202
5867
  getOuterVoiceName,
5868
+ handleCodeLensAction,
5203
5869
  openPopoverId,
5204
5870
  popoverVoiceName,
5205
5871
  popoverPosition,
@@ -5224,7 +5890,10 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5224
5890
  decorationsVisible,
5225
5891
  getOuterVoiceName,
5226
5892
  onSelectionOverlayChange: refreshSelectionOverlay,
5227
- onSyntaxErrorChange: setSyntaxError
5893
+ onActiveTagsChange: updateActiveTags,
5894
+ onSyntaxErrorChange: setSyntaxError,
5895
+ enableCodeLens,
5896
+ onOpenPopover: handleCodeLensAction
5228
5897
  });
5229
5898
  (0, import_react3.useImperativeHandle)(
5230
5899
  ref,
@@ -5239,12 +5908,13 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5239
5908
  const isTimingPopover = insertion.tagName !== void 0 && TIMING_POPOVER_TAGS.has(insertion.tagName);
5240
5909
  const isProsodyPopover = insertion.tagName !== void 0 && PROSODY_POPOVER_TAGS.has(insertion.tagName);
5241
5910
  const isTextPopover = insertion.tagName !== void 0 && TEXT_POPOVER_TAGS.has(insertion.tagName);
5911
+ const insertionButtonStyle = insertion.tagName && activeTags.has(insertion.tagName.toLowerCase()) ? { ...toolbarButtonStyle, ...editorStyles.toolbarButtonActive } : toolbarButtonStyle;
5242
5912
  const props = {
5243
5913
  language,
5244
5914
  isDarkTheme,
5245
5915
  showToolbarIcons,
5246
5916
  showToolbarText,
5247
- toolbarButtonStyle,
5917
+ toolbarButtonStyle: insertionButtonStyle,
5248
5918
  emptyOptionsMessage: copy.noAvailableOptions,
5249
5919
  isReadOnly,
5250
5920
  openPopoverId,
@@ -5502,6 +6172,8 @@ var SsmlEditor = (0, import_react3.forwardRef)(function SsmlEditor2({
5502
6172
  SSML_HOVER_COPY,
5503
6173
  SSML_INSERTIONS,
5504
6174
  SsmlEditor,
5505
- createSsmlEditorInsertionDefinition
6175
+ createSsmlEditorInsertionDefinition,
6176
+ registerSsmlCodeLens,
6177
+ updateTagAttribute
5506
6178
  });
5507
6179
  //# sourceMappingURL=react.js.map