ssml-builder-js 2.16.0 → 2.17.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.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ReactElement, CSSProperties } from 'react';
3
- import { d as SsmlDocument, e as SsmlElement } from './index.d-DR5Qz43p.mjs';
3
+ import { d as SsmlDocument, e as SsmlElement, f as AzureVoiceCatalogMetadata } from './index.d-tpKoP1jl.mjs';
4
4
  import { OnMount, Monaco } from '@monaco-editor/react';
5
5
 
6
6
  type SsmlEditorInsertionButton = "break" | "emphasis" | "rate" | "pitch" | "volume" | "emotion" | "say-as" | "phoneme" | "audio" | "sub" | "lang" | "mark" | "bookmark" | "mstts:silence" | "mstts:audioduration" | "mstts:viseme" | (string & {});
@@ -119,8 +119,9 @@ interface VisualSsmlEditorProps {
119
119
  voiceModel?: string;
120
120
  /** Alias for voiceModel. */
121
121
  model?: string;
122
+ voiceCatalogMetadata?: AzureVoiceCatalogMetadata;
122
123
  }
123
- declare function VisualSsmlEditor({ document, readOnly, onChange, onPreviewSelection, locale, customInspectors, renderVoiceSelector, voiceCatalog, voiceLocale, voiceRegion, voiceStyle, voiceModel, model, }: VisualSsmlEditorProps): ReactElement;
124
+ declare function VisualSsmlEditor({ document, readOnly, onChange, onPreviewSelection, locale, customInspectors, renderVoiceSelector, voiceCatalog, voiceLocale, voiceRegion, voiceStyle, voiceModel, model, voiceCatalogMetadata, }: VisualSsmlEditorProps): ReactElement;
124
125
 
125
126
  type SsmlEditorInsertionMode = "insert" | "wrap";
126
127
  interface SsmlEditorInsertionOption {
@@ -310,6 +311,8 @@ interface SsmlEditorProps {
310
311
  voiceModel?: string;
311
312
  /** Alias for voiceModel. */
312
313
  model?: string;
314
+ /** Metadata for the voice catalog snapshot, used to warn when it is stale. */
315
+ voiceCatalogMetadata?: AzureVoiceCatalogMetadata;
313
316
  /** Candidate style values shown by the built-in emotion insertion. */
314
317
  emotionStyles?: readonly string[];
315
318
  /** Class name applied to the editor container. */
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode, ReactElement, CSSProperties } from 'react';
3
- import { d as SsmlDocument, e as SsmlElement } from './index.d-DR5Qz43p.js';
3
+ import { d as SsmlDocument, e as SsmlElement, f as AzureVoiceCatalogMetadata } from './index.d-tpKoP1jl.js';
4
4
  import { OnMount, Monaco } from '@monaco-editor/react';
5
5
 
6
6
  type SsmlEditorInsertionButton = "break" | "emphasis" | "rate" | "pitch" | "volume" | "emotion" | "say-as" | "phoneme" | "audio" | "sub" | "lang" | "mark" | "bookmark" | "mstts:silence" | "mstts:audioduration" | "mstts:viseme" | (string & {});
@@ -119,8 +119,9 @@ interface VisualSsmlEditorProps {
119
119
  voiceModel?: string;
120
120
  /** Alias for voiceModel. */
121
121
  model?: string;
122
+ voiceCatalogMetadata?: AzureVoiceCatalogMetadata;
122
123
  }
123
- declare function VisualSsmlEditor({ document, readOnly, onChange, onPreviewSelection, locale, customInspectors, renderVoiceSelector, voiceCatalog, voiceLocale, voiceRegion, voiceStyle, voiceModel, model, }: VisualSsmlEditorProps): ReactElement;
124
+ declare function VisualSsmlEditor({ document, readOnly, onChange, onPreviewSelection, locale, customInspectors, renderVoiceSelector, voiceCatalog, voiceLocale, voiceRegion, voiceStyle, voiceModel, model, voiceCatalogMetadata, }: VisualSsmlEditorProps): ReactElement;
124
125
 
125
126
  type SsmlEditorInsertionMode = "insert" | "wrap";
126
127
  interface SsmlEditorInsertionOption {
@@ -310,6 +311,8 @@ interface SsmlEditorProps {
310
311
  voiceModel?: string;
311
312
  /** Alias for voiceModel. */
312
313
  model?: string;
314
+ /** Metadata for the voice catalog snapshot, used to warn when it is stale. */
315
+ voiceCatalogMetadata?: AzureVoiceCatalogMetadata;
313
316
  /** Candidate style values shown by the built-in emotion insertion. */
314
317
  emotionStyles?: readonly string[];
315
318
  /** Class name applied to the editor container. */
package/dist/react.js CHANGED
@@ -1743,6 +1743,8 @@ function InsertionPopover({
1743
1743
  toolbarButtonStyle,
1744
1744
  emptyOptionsMessage,
1745
1745
  isReadOnly,
1746
+ disabled = false,
1747
+ disabledReason,
1746
1748
  isOpen,
1747
1749
  menuPosition,
1748
1750
  menuRef,
@@ -1760,7 +1762,7 @@ function InsertionPopover({
1760
1762
  role: "menuitem",
1761
1763
  style: editorStyles.toolbarOption,
1762
1764
  title: option.descriptions?.[language] ?? insertion.descriptions[language],
1763
- disabled: isReadOnly,
1765
+ disabled: isReadOnly || disabled,
1764
1766
  onMouseDown: (event) => event.preventDefault(),
1765
1767
  onClick: () => {
1766
1768
  if (!isReadOnly) {
@@ -1789,11 +1791,12 @@ function InsertionPopover({
1789
1791
  {
1790
1792
  type: "button",
1791
1793
  style: toolbarButtonStyle,
1792
- title: getInsertionTitle(insertion, language),
1794
+ title: disabledReason ?? getInsertionTitle(insertion, language),
1793
1795
  "aria-label": insertion.labels[language],
1794
1796
  "aria-haspopup": "menu",
1795
1797
  "aria-expanded": isOpen,
1796
1798
  "aria-controls": isOpen ? `ssml-editor-popover-${insertion.id}` : void 0,
1799
+ disabled: isReadOnly || disabled,
1797
1800
  onClick: (event) => onToggle(event.currentTarget)
1798
1801
  },
1799
1802
  showToolbarIcons && /* @__PURE__ */ React.createElement("span", { style: editorStyles.toolbarIcon, "aria-hidden": "true" }, insertion.icon),
@@ -3747,7 +3750,9 @@ var AZURE_VOICE_CATALOG_METADATA = {
3747
3750
  apiVersion: "2025-10-01",
3748
3751
  generatedAt: "2026-08-28T00:00:00.000Z",
3749
3752
  regions: [],
3750
- voiceCount: AZURE_VOICE_DEFINITIONS.length
3753
+ voiceCount: AZURE_VOICE_DEFINITIONS.length,
3754
+ expiresAt: "2026-09-04T00:00:00.000Z",
3755
+ regionDiffs: {}
3751
3756
  };
3752
3757
 
3753
3758
  // packages/ssml-editor-react/src/clearSsmlDocument.ts
@@ -6884,6 +6889,21 @@ function filteredVoices(voiceCatalog, locale, region, style) {
6884
6889
  return true;
6885
6890
  });
6886
6891
  }
6892
+ function voiceForPath(document2, path, voiceCatalog) {
6893
+ if (!path || !voiceCatalog) return void 0;
6894
+ const ancestors = getElementAncestors(document2.children ?? [], path);
6895
+ const node = getNodeAtPath(document2.children ?? [], path);
6896
+ const candidates = [...ancestors, ...node && isElement(node) ? [node] : []].reverse();
6897
+ const voiceName = candidates.find((element) => element.type === "voice")?.name ?? candidates.find((element) => element.type === "mstts:turn")?.voice;
6898
+ return voiceName ? voiceCatalog.find((voice) => voice.name.toLowerCase() === voiceName.toLowerCase()) : void 0;
6899
+ }
6900
+ function isVisualTagSupported(tagName, voice) {
6901
+ if (!voice) return true;
6902
+ const tag = tagName.toLowerCase();
6903
+ const unsupported = new Set((voice.unsupportedTags ?? []).map((candidate) => candidate.toLowerCase()));
6904
+ const supported = voice.supportedTags?.map((candidate) => candidate.toLowerCase());
6905
+ return !unsupported.has(tag) && (supported === void 0 || supported.includes(tag));
6906
+ }
6887
6907
  function VoiceCapabilityMatrix({
6888
6908
  voice,
6889
6909
  locale
@@ -6958,10 +6978,12 @@ function VisualElementInspector({
6958
6978
  voiceLocale,
6959
6979
  voiceRegion,
6960
6980
  voiceStyle,
6961
- diagnostics
6981
+ diagnostics,
6982
+ voiceDefinition
6962
6983
  }) {
6984
+ const tagSupported = isVisualTagSupported(elementLabel(element), voiceDefinition);
6963
6985
  if (customInspector) {
6964
- return /* @__PURE__ */ React.createElement(React.Fragment, null, customInspector({ document: document2, element, path, readOnly, onChange: commit, locale }));
6986
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, customInspector({ document: document2, element, path, readOnly: readOnly || !tagSupported, onChange: commit, locale }));
6965
6987
  }
6966
6988
  const fields = getElementFields(element);
6967
6989
  const availableVoices = filteredVoices(voiceCatalog, voiceLocale, voiceRegion, voiceStyle);
@@ -6969,7 +6991,7 @@ function VisualElementInspector({
6969
6991
  (voice) => voice.name === (element.type === "voice" ? element.name : void 0)
6970
6992
  );
6971
6993
  const renderSelector = renderVoiceSelector ?? DefaultVoiceSelector;
6972
- return /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, `<${elementLabel(element)}>`, " ", /* @__PURE__ */ React.createElement(WarningBadge, { messages: elementWarnings(diagnostics, element) })), fields.length === 0 ? /* @__PURE__ */ React.createElement("p", null, "This element is preserved in the visual tree. Edit its attributes in Code mode.") : fields.map((field) => {
6994
+ return /* @__PURE__ */ React.createElement("fieldset", { disabled: readOnly || !tagSupported, style: !tagSupported ? { border: "1px solid #c0392b" } : void 0 }, /* @__PURE__ */ React.createElement("legend", null, `<${elementLabel(element)}>`, " ", /* @__PURE__ */ React.createElement(WarningBadge, { messages: elementWarnings(diagnostics, element) })), fields.length === 0 ? /* @__PURE__ */ React.createElement("p", null, "This element is preserved in the visual tree. Edit its attributes in Code mode.") : fields.map((field) => {
6973
6995
  const value = element[field.key];
6974
6996
  const inputValue = value === void 0 ? "" : String(value);
6975
6997
  const inputId = `ssml-visual-${field.key}`;
@@ -7018,6 +7040,7 @@ function VisualElementInspector({
7018
7040
  {
7019
7041
  id: inputId,
7020
7042
  value: inputValue,
7043
+ disabled: readOnly || !tagSupported,
7021
7044
  readOnly,
7022
7045
  onChange: (event) => commit(updateOptionalElementProperty(document2, path, field.key, event.target.value))
7023
7046
  }
@@ -7026,6 +7049,7 @@ function VisualElementInspector({
7026
7049
  {
7027
7050
  id: inputId,
7028
7051
  value: inputValue,
7052
+ disabled: readOnly || !tagSupported,
7029
7053
  readOnly,
7030
7054
  onChange: (event) => commit(updateOptionalElementProperty(document2, path, field.key, event.target.value))
7031
7055
  }
@@ -7089,18 +7113,27 @@ function TreeNode({
7089
7113
  }) {
7090
7114
  if (!isElement(node)) return null;
7091
7115
  const name = elementLabel(node);
7116
+ const messages = getWarnings(node);
7092
7117
  const isSelected = selectedPath?.join(".") === path.join(".");
7093
- return /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement("button", { type: "button", "aria-current": isSelected ? "true" : void 0, onClick: () => onSelect(path) }, `<${name}>`, /* @__PURE__ */ React.createElement(WarningBadge, { messages: getWarnings(node) })), (node.children ?? []).length > 0 && /* @__PURE__ */ React.createElement("ul", null, node.children?.map((child, index) => /* @__PURE__ */ React.createElement(
7094
- TreeNode,
7118
+ return /* @__PURE__ */ React.createElement(
7119
+ "li",
7095
7120
  {
7096
- key: `${path.join(".")}/${isElement(child) ? elementLabel(child) : JSON.stringify(child)}`,
7097
- node: child,
7098
- path: [...path, index],
7099
- selectedPath,
7100
- onSelect,
7101
- getWarnings
7102
- }
7103
- ))));
7121
+ "data-ssml-diagnostic": messages.length > 0 ? "error" : void 0,
7122
+ style: messages.length > 0 ? { border: "1px solid #c0392b", borderRadius: "0.25rem" } : void 0
7123
+ },
7124
+ /* @__PURE__ */ React.createElement("button", { type: "button", "aria-current": isSelected ? "true" : void 0, onClick: () => onSelect(path) }, `<${name}>`, /* @__PURE__ */ React.createElement(WarningBadge, { messages: getWarnings(node) })),
7125
+ (node.children ?? []).length > 0 && /* @__PURE__ */ React.createElement("ul", null, node.children?.map((child, index) => /* @__PURE__ */ React.createElement(
7126
+ TreeNode,
7127
+ {
7128
+ key: `${path.join(".")}/${isElement(child) ? elementLabel(child) : JSON.stringify(child)}`,
7129
+ node: child,
7130
+ path: [...path, index],
7131
+ selectedPath,
7132
+ onSelect,
7133
+ getWarnings
7134
+ }
7135
+ )))
7136
+ );
7104
7137
  }
7105
7138
  function VisualSsmlEditor({
7106
7139
  document: document2,
@@ -7115,13 +7148,16 @@ function VisualSsmlEditor({
7115
7148
  voiceRegion,
7116
7149
  voiceStyle,
7117
7150
  voiceModel,
7118
- model
7151
+ model,
7152
+ voiceCatalogMetadata
7119
7153
  }) {
7120
7154
  const [selectedPath, setSelectedPath] = (0, import_react3.useState)(null);
7121
7155
  const [selection, setSelection] = (0, import_react3.useState)({ start: 0, end: 0 });
7122
7156
  const textLeaves = (0, import_react3.useMemo)(() => collectTextLeaves(document2.children ?? []), [document2]);
7123
7157
  const selectedLeaf = textLeaves.find((leaf) => leaf.path.join(".") === selectedPath?.join(".")) ?? textLeaves[0];
7124
7158
  const selectedElement = selectedPath ? getNodeAtPath(document2.children ?? [], selectedPath) : void 0;
7159
+ const activeVoice = voiceForPath(document2, selectedPath ?? selectedLeaf?.path, voiceCatalog);
7160
+ const staleCatalog = voiceCatalogMetadata?.expiresAt ? Date.parse(voiceCatalogMetadata.expiresAt) <= Date.now() : false;
7125
7161
  const selectedCustomInspector = selectedElement && isElement(selectedElement) ? customInspectors?.[elementLabel(selectedElement)] ?? customInspectors?.[selectedElement.type] : void 0;
7126
7162
  const validationOptions = (0, import_react3.useMemo)(
7127
7163
  () => ({
@@ -7151,7 +7187,7 @@ function VisualSsmlEditor({
7151
7187
  const end = selection.start === selection.end ? selectedLeaf.value.length : selection.end;
7152
7188
  commit(wrapTextAtPath(document2, selectedLeaf.path, start, end, tag, attributes));
7153
7189
  };
7154
- return /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual", "data-ssml-editor-visual": "" }, /* @__PURE__ */ React.createElement("fieldset", { className: "ssml-editor-visual-breadcrumb" }, /* @__PURE__ */ React.createElement("legend", null, "SSML structure breadcrumb"), /* @__PURE__ */ React.createElement("span", null, "<speak>"), selectedLeaf?.ancestors.map((ancestor, index) => /* @__PURE__ */ React.createElement("span", { key: selectedLeaf?.ancestors.slice(0, index + 1).join("/") }, " / <", ancestor, ">")), selectedPath && /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setSelectedPath(null) }, "Clear parent")), diagnostics.length > 0 && /* @__PURE__ */ React.createElement("div", { role: "alert", "aria-invalid": "true", className: "ssml-editor-visual-errors" }, diagnostics.map((diagnostic) => /* @__PURE__ */ React.createElement("div", { key: `${diagnostic.code}-${diagnostic.message}` }, diagnostic.message))), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-layout" }, /* @__PURE__ */ React.createElement("nav", { "aria-label": "SSML structure tree", className: "ssml-editor-visual-tree" }, /* @__PURE__ */ React.createElement("strong", null, "Structure"), /* @__PURE__ */ React.createElement("ul", null, document2.children?.map((node, index) => /* @__PURE__ */ React.createElement(
7190
+ return /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual", "data-ssml-editor-visual": "" }, /* @__PURE__ */ React.createElement("fieldset", { className: "ssml-editor-visual-breadcrumb" }, /* @__PURE__ */ React.createElement("legend", null, "SSML structure breadcrumb"), /* @__PURE__ */ React.createElement("span", null, "<speak>"), selectedLeaf?.ancestors.map((ancestor, index) => /* @__PURE__ */ React.createElement("span", { key: selectedLeaf?.ancestors.slice(0, index + 1).join("/") }, " / <", ancestor, ">")), selectedPath && /* @__PURE__ */ React.createElement("button", { type: "button", onClick: () => setSelectedPath(null) }, "Clear parent")), diagnostics.length > 0 && /* @__PURE__ */ React.createElement("div", { role: "alert", "aria-invalid": "true", className: "ssml-editor-visual-errors" }, diagnostics.map((diagnostic) => /* @__PURE__ */ React.createElement("div", { key: `${diagnostic.code}-${diagnostic.message}` }, diagnostic.message))), staleCatalog && /* @__PURE__ */ React.createElement("div", { role: "status", className: "ssml-editor-visual-catalog-warning" }, locale === "ja" ? "\u97F3\u58F0\u30AB\u30BF\u30ED\u30B0\u304C\u53E4\u304F\u306A\u3063\u3066\u3044\u307E\u3059\u3002\u66F4\u65B0\u3057\u3066\u304F\u3060\u3055\u3044\u3002" : "The voice catalog is stale. Refresh it before synthesizing."), /* @__PURE__ */ React.createElement("div", { className: "ssml-editor-visual-layout" }, /* @__PURE__ */ React.createElement("nav", { "aria-label": "SSML structure tree", className: "ssml-editor-visual-tree" }, /* @__PURE__ */ React.createElement("strong", null, "Structure"), /* @__PURE__ */ React.createElement("ul", null, document2.children?.map((node, index) => /* @__PURE__ */ React.createElement(
7155
7191
  TreeNode,
7156
7192
  {
7157
7193
  key: isElement(node) ? elementLabel(node) : JSON.stringify(node),
@@ -7165,14 +7201,14 @@ function VisualSsmlEditor({
7165
7201
  document: document2,
7166
7202
  element: selectedElement,
7167
7203
  path: selectedPath ?? [],
7168
- readOnly,
7204
+ readOnly: readOnly || !isVisualTagSupported(elementLabel(selectedElement), activeVoice),
7169
7205
  onChange: commit,
7170
7206
  locale
7171
7207
  }) : selectedElement && isElement(selectedElement) && selectedElement.type === "mstts:dialog" ? /* @__PURE__ */ React.createElement("fieldset", null, /* @__PURE__ */ React.createElement("legend", null, localizedElementLabel(selectedElement, locale)), /* @__PURE__ */ React.createElement("p", null, SSML_ELEMENT_COPY[locale][elementLabel(selectedElement)]?.description), /* @__PURE__ */ React.createElement(
7172
7208
  "button",
7173
7209
  {
7174
7210
  type: "button",
7175
- disabled: readOnly,
7211
+ disabled: readOnly || !isVisualTagSupported("mstts:dialog", activeVoice),
7176
7212
  onClick: () => commit(addDialogTurn(document2, selectedPath ?? []))
7177
7213
  },
7178
7214
  locale === "ja" ? "\u30BF\u30FC\u30F3\u3092\u8FFD\u52A0" : "Add turn"
@@ -7254,7 +7290,8 @@ function VisualSsmlEditor({
7254
7290
  voiceLocale,
7255
7291
  voiceRegion,
7256
7292
  voiceStyle,
7257
- diagnostics
7293
+ diagnostics,
7294
+ voiceDefinition: activeVoice
7258
7295
  }
7259
7296
  ) : selectedLeaf ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Text", /* @__PURE__ */ React.createElement(
7260
7297
  "textarea",
@@ -7264,19 +7301,43 @@ function VisualSsmlEditor({
7264
7301
  onSelect: (event) => setSelection({ start: event.currentTarget.selectionStart, end: event.currentTarget.selectionEnd }),
7265
7302
  onChange: (event) => commit(updateTextAtPath(document2, selectedLeaf.path, event.target.value))
7266
7303
  }
7267
- )), /* @__PURE__ */ React.createElement("fieldset", { className: "ssml-editor-visual-actions" }, /* @__PURE__ */ React.createElement("legend", null, "Apply SSML formatting"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("prosody", { rate: "slow" }) }, "Rate"), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("prosody", { pitch: "high" }) }, "Pitch"), /* @__PURE__ */ React.createElement(
7304
+ )), /* @__PURE__ */ React.createElement("fieldset", { className: "ssml-editor-visual-actions" }, /* @__PURE__ */ React.createElement("legend", null, "Apply SSML formatting"), /* @__PURE__ */ React.createElement(
7268
7305
  "button",
7269
7306
  {
7270
7307
  type: "button",
7271
- disabled: readOnly,
7308
+ disabled: readOnly || !isVisualTagSupported("prosody", activeVoice),
7309
+ onClick: () => applyWrapper("prosody", { rate: "slow" })
7310
+ },
7311
+ "Rate"
7312
+ ), /* @__PURE__ */ React.createElement(
7313
+ "button",
7314
+ {
7315
+ type: "button",
7316
+ disabled: readOnly || !isVisualTagSupported("prosody", activeVoice),
7317
+ onClick: () => applyWrapper("prosody", { pitch: "high" })
7318
+ },
7319
+ "Pitch"
7320
+ ), /* @__PURE__ */ React.createElement(
7321
+ "button",
7322
+ {
7323
+ type: "button",
7324
+ disabled: readOnly || !isVisualTagSupported("mstts:express-as", activeVoice),
7272
7325
  onClick: () => applyWrapper("mstts:express-as", { style: "cheerful" })
7273
7326
  },
7274
7327
  "Emotion"
7275
- ), /* @__PURE__ */ React.createElement("button", { type: "button", disabled: readOnly, onClick: () => applyWrapper("break", { time: "500ms" }) }, "Pause"), /* @__PURE__ */ React.createElement(
7328
+ ), /* @__PURE__ */ React.createElement(
7276
7329
  "button",
7277
7330
  {
7278
7331
  type: "button",
7279
- disabled: readOnly,
7332
+ disabled: readOnly || !isVisualTagSupported("break", activeVoice),
7333
+ onClick: () => applyWrapper("break", { time: "500ms" })
7334
+ },
7335
+ "Pause"
7336
+ ), /* @__PURE__ */ React.createElement(
7337
+ "button",
7338
+ {
7339
+ type: "button",
7340
+ disabled: readOnly || !isVisualTagSupported("phoneme", activeVoice),
7280
7341
  onClick: () => applyWrapper("phoneme", { alphabet: "ipa", ph: selectedLeaf.value })
7281
7342
  },
7282
7343
  "Pronunciation"
@@ -7295,6 +7356,23 @@ var UNGROUPED_TOOLBAR_GROUP = "__ssml-editor-ungrouped__";
7295
7356
  var TIMING_POPOVER_TAGS = /* @__PURE__ */ new Set(["break", "mstts:silence", "mstts:audioduration"]);
7296
7357
  var PROSODY_POPOVER_TAGS = /* @__PURE__ */ new Set(["prosody", "mstts:express-as", "voice", "emphasis"]);
7297
7358
  var TEXT_POPOVER_TAGS = /* @__PURE__ */ new Set(["sub", "say-as", "phoneme", "w", "lang"]);
7359
+ function isVoiceTagSupported(tagName, voice) {
7360
+ if (!tagName || !voice) return true;
7361
+ const tag = tagName.toLowerCase();
7362
+ const unsupported = new Set((voice.unsupportedTags ?? []).map((candidate) => candidate.toLowerCase()));
7363
+ const supported = voice.supportedTags?.map((candidate) => candidate.toLowerCase());
7364
+ return !unsupported.has(tag) && (supported === void 0 || supported.includes(tag));
7365
+ }
7366
+ function findFirstVoiceName(nodes) {
7367
+ for (const node of nodes) {
7368
+ if (typeof node === "string" || node.type === "text") continue;
7369
+ if (node.type === "voice" && node.name) return node.name;
7370
+ if (node.type === "mstts:turn" && node.voice) return node.voice;
7371
+ const nested = findFirstVoiceName(node.children ?? []);
7372
+ if (nested) return nested;
7373
+ }
7374
+ return void 0;
7375
+ }
7298
7376
  function localizedText(value) {
7299
7377
  return { ja: value, en: value };
7300
7378
  }
@@ -7880,6 +7958,7 @@ var SsmlEditor = (0, import_react4.forwardRef)(function SsmlEditor2({
7880
7958
  voiceStyle,
7881
7959
  voiceModel,
7882
7960
  model,
7961
+ voiceCatalogMetadata,
7883
7962
  emotionStyles,
7884
7963
  className,
7885
7964
  style,
@@ -8043,6 +8122,10 @@ var SsmlEditor = (0, import_react4.forwardRef)(function SsmlEditor2({
8043
8122
  const isProsodyPopover = insertion.tagName !== void 0 && PROSODY_POPOVER_TAGS.has(insertion.tagName);
8044
8123
  const isTextPopover = insertion.tagName !== void 0 && TEXT_POPOVER_TAGS.has(insertion.tagName);
8045
8124
  const insertionButtonStyle = insertion.tagName && activeTags.has(insertion.tagName.toLowerCase()) ? { ...toolbarButtonStyle, ...editorStyles.toolbarButtonActive } : toolbarButtonStyle;
8125
+ const activeVoiceName = popoverVoiceName ?? findFirstVoiceName(draftDocument.children ?? []);
8126
+ const activeVoice = voiceCatalog?.find((voice) => voice.name.toLowerCase() === activeVoiceName?.toLowerCase());
8127
+ const insertionDisabled = !isVoiceTagSupported(insertion.tagName, activeVoice);
8128
+ const disabledReason = insertionDisabled ? language === "ja" ? `\u9078\u629E\u4E2D\u306E\u97F3\u58F0\u306F <${insertion.tagName}> \u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u305B\u3093\u3002` : `The selected voice does not support <${insertion.tagName}>.` : void 0;
8046
8129
  const props = {
8047
8130
  language,
8048
8131
  isDarkTheme,
@@ -8051,6 +8134,8 @@ var SsmlEditor = (0, import_react4.forwardRef)(function SsmlEditor2({
8051
8134
  toolbarButtonStyle: insertionButtonStyle,
8052
8135
  emptyOptionsMessage: copy.noAvailableOptions,
8053
8136
  isReadOnly,
8137
+ disabled: insertionDisabled,
8138
+ disabledReason,
8054
8139
  openPopoverId,
8055
8140
  menuPosition: popoverPosition,
8056
8141
  menuRef: setPopoverMenuRef,
@@ -8279,6 +8364,7 @@ var SsmlEditor = (0, import_react4.forwardRef)(function SsmlEditor2({
8279
8364
  voiceLocale,
8280
8365
  voiceRegion,
8281
8366
  voiceStyle,
8367
+ voiceCatalogMetadata,
8282
8368
  voiceModel,
8283
8369
  model
8284
8370
  }