ssml-builder-js 2.2.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.mjs CHANGED
@@ -40,8 +40,9 @@ import {
40
40
  registerSsmlCompletionProvider,
41
41
  resolveExpressAsStyles,
42
42
  updateEditableText,
43
+ updateTagAttribute,
43
44
  validateSsml
44
- } from "./chunk-2XJER2BG.mjs";
45
+ } from "./chunk-NPZ44CHU.mjs";
45
46
  import "./chunk-6S5ODO6A.mjs";
46
47
 
47
48
  // packages/ssml-editor-react/src/SsmlEditor.tsx
@@ -705,6 +706,7 @@ function useSsmlEditorState({
705
706
  const [openPopoverId, setOpenPopoverId] = useState(null);
706
707
  const [popoverVoiceName, setPopoverVoiceName] = useState(void 0);
707
708
  const [popoverPosition, setPopoverPosition] = useState(null);
709
+ const pendingCodeLensAttributeRef = useRef(null);
708
710
  const helpPanelId = useId();
709
711
  const draftDocumentRef = useRef(document2);
710
712
  const onSelectionChangeRef = useRef(onSelectionChange);
@@ -730,6 +732,7 @@ function useSsmlEditorState({
730
732
  setDecorationsVisible(showDecorations);
731
733
  }, [showDecorations]);
732
734
  const closePopover = useCallback((restoreFocus = false) => {
735
+ pendingCodeLensAttributeRef.current = null;
733
736
  setOpenPopoverId(null);
734
737
  setPopoverVoiceName(void 0);
735
738
  setPopoverPosition(null);
@@ -738,6 +741,7 @@ function useSsmlEditorState({
738
741
  }
739
742
  }, []);
740
743
  const togglePopover = useCallback((id, trigger) => {
744
+ pendingCodeLensAttributeRef.current = null;
741
745
  setOpenPopoverId((currentId) => {
742
746
  if (currentId === id) {
743
747
  setPopoverVoiceName(void 0);
@@ -761,13 +765,41 @@ function useSsmlEditorState({
761
765
  }
762
766
  const updatePopoverPosition = () => {
763
767
  const trigger = activePopoverTriggerRef.current;
764
- if (!trigger) {
768
+ if (trigger) {
769
+ const triggerBounds = trigger.getBoundingClientRect();
770
+ setPopoverPosition({
771
+ top: triggerBounds.bottom + 4,
772
+ left: triggerBounds.left
773
+ });
765
774
  return;
766
775
  }
767
- const triggerBounds = trigger.getBoundingClientRect();
776
+ const pending = pendingCodeLensAttributeRef.current;
777
+ const editor = editorRef.current;
778
+ const model = editor?.getModel();
779
+ if (!pending || !editor || !model) {
780
+ return;
781
+ }
782
+ const position = model.getPositionAt(pending.tagRange.start);
783
+ const visiblePosition = editor.getScrolledVisiblePosition(position);
784
+ const editorElement = editor.getDomNode();
785
+ if (!visiblePosition || !editorElement) {
786
+ return;
787
+ }
788
+ const editorBounds = editorElement.getBoundingClientRect();
789
+ const menu = activePopoverMenuRef.current;
790
+ const margin = 8;
768
791
  setPopoverPosition({
769
- top: triggerBounds.bottom + 4,
770
- left: triggerBounds.left
792
+ top: Math.max(
793
+ margin,
794
+ Math.min(
795
+ editorBounds.top + visiblePosition.top + visiblePosition.height + 4,
796
+ window.innerHeight - (menu?.offsetHeight ?? 0) - margin
797
+ )
798
+ ),
799
+ left: Math.max(
800
+ margin,
801
+ Math.min(editorBounds.left + visiblePosition.left, window.innerWidth - (menu?.offsetWidth ?? 0) - margin)
802
+ )
771
803
  });
772
804
  };
773
805
  const handlePointerDown = (event) => {
@@ -832,11 +864,48 @@ function useSsmlEditorState({
832
864
  );
833
865
  const handleInsert = useCallback(
834
866
  (insertion, option) => {
835
- if (editorRef.current) {
836
- applySsmlInsertion(editorRef.current, insertion, option);
867
+ const editor = editorRef.current;
868
+ const pending = pendingCodeLensAttributeRef.current;
869
+ if (!editor) {
870
+ return;
837
871
  }
872
+ if (pending?.insertionId === insertion.id) {
873
+ const model = editor.getModel();
874
+ if (model && model.getVersionId() === pending.modelVersionId) {
875
+ const source = model.getValue();
876
+ const tagText = source.slice(pending.tagRange.start, pending.tagRange.end);
877
+ const updatedTag = updateTagAttribute(
878
+ tagText,
879
+ { start: 0, end: tagText.length },
880
+ pending.attributeName,
881
+ option.value
882
+ );
883
+ const start = model.getPositionAt(pending.tagRange.start);
884
+ const end = model.getPositionAt(pending.tagRange.end);
885
+ editor.pushUndoStop();
886
+ editor.executeEdits("ssml-code-lens", [
887
+ {
888
+ range: {
889
+ startLineNumber: start.lineNumber,
890
+ startColumn: start.column,
891
+ endLineNumber: end.lineNumber,
892
+ endColumn: end.column
893
+ },
894
+ text: updatedTag
895
+ }
896
+ ]);
897
+ editor.pushUndoStop();
898
+ editor.focus();
899
+ }
900
+ pendingCodeLensAttributeRef.current = null;
901
+ if (!model || model.getVersionId() !== pending.modelVersionId) {
902
+ closePopover();
903
+ }
904
+ return;
905
+ }
906
+ applySsmlInsertion(editor, insertion, option);
838
907
  },
839
- []
908
+ [closePopover]
840
909
  );
841
910
  const handleInsertBreak = useCallback(
842
911
  (insertion, option) => {
@@ -902,6 +971,54 @@ function useSsmlEditorState({
902
971
  []
903
972
  );
904
973
  const getOuterVoiceName = useCallback(() => getEditableRegion(draftDocumentRef.current).voiceName, []);
974
+ const handleCodeLensAction = useCallback((action) => {
975
+ const editor = editorRef.current;
976
+ const model = editor?.getModel();
977
+ if (!editor || !model) {
978
+ return;
979
+ }
980
+ if (action.type === "attribute") {
981
+ pendingCodeLensAttributeRef.current = {
982
+ insertionId: action.insertionId,
983
+ attributeName: action.attributeName,
984
+ tagRange: action.tagRange,
985
+ modelVersionId: model.getVersionId()
986
+ };
987
+ activePopoverTriggerRef.current = null;
988
+ setPopoverVoiceName(getEffectiveVoiceName(editor, draftDocumentRef.current));
989
+ setOpenPopoverId(action.insertionId);
990
+ setPopoverPosition(null);
991
+ return;
992
+ }
993
+ const source = model.getValue();
994
+ const toEditorRange = (range) => {
995
+ const start = model.getPositionAt(range.start);
996
+ const end = model.getPositionAt(range.end);
997
+ return {
998
+ startLineNumber: start.lineNumber,
999
+ startColumn: start.column,
1000
+ endLineNumber: end.lineNumber,
1001
+ endColumn: end.column
1002
+ };
1003
+ };
1004
+ const edits = action.type === "delete" ? [{ range: toEditorRange(action.tagRange), text: "" }] : (() => {
1005
+ const elementText = source.slice(action.elementRange.start, action.elementRange.end);
1006
+ const closingOffset = elementText.search(/<\/\s*prosody\s*>/i);
1007
+ const closingStart = closingOffset === -1 ? action.elementRange.end : action.elementRange.start + closingOffset;
1008
+ const closingEnd = closingOffset === -1 ? action.elementRange.end : source.indexOf(">", closingStart) === -1 ? action.elementRange.end : source.indexOf(">", closingStart) + 1;
1009
+ return [
1010
+ { range: toEditorRange(action.tagRange), text: "" },
1011
+ { range: toEditorRange({ start: closingStart, end: closingEnd }), text: "" }
1012
+ ];
1013
+ })();
1014
+ editor.pushUndoStop();
1015
+ editor.executeEdits("ssml-code-lens", edits);
1016
+ editor.pushUndoStop();
1017
+ pendingCodeLensAttributeRef.current = null;
1018
+ setOpenPopoverId(null);
1019
+ setPopoverPosition(null);
1020
+ editor.focus();
1021
+ }, []);
905
1022
  return {
906
1023
  editorRef,
907
1024
  draftDocument,
@@ -934,6 +1051,7 @@ function useSsmlEditorState({
934
1051
  getCurrentLineSsml: getCurrentLineSsmlValue,
935
1052
  getFullSsml,
936
1053
  getOuterVoiceName,
1054
+ handleCodeLensAction,
937
1055
  openPopoverId,
938
1056
  popoverVoiceName,
939
1057
  popoverPosition,
@@ -1190,6 +1308,180 @@ function registerSsmlCodeActions(monaco) {
1190
1308
  });
1191
1309
  }
1192
1310
 
1311
+ // packages/ssml-editor-react/src/ssmlCodeLens.ts
1312
+ var CODE_LENS_COMMAND = "ssml-editor.codeLens";
1313
+ function findTagEnd(source, start) {
1314
+ let quote;
1315
+ for (let index = start + 1; index < source.length; index += 1) {
1316
+ const character = source[index];
1317
+ if (quote !== void 0) {
1318
+ if (character === quote) {
1319
+ quote = void 0;
1320
+ }
1321
+ } else if (character === '"' || character === "'") {
1322
+ quote = character;
1323
+ } else if (character === ">") {
1324
+ return index;
1325
+ }
1326
+ }
1327
+ return -1;
1328
+ }
1329
+ function getAttributeValue(tag, attributeName) {
1330
+ const escapedName = attributeName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1331
+ return tag.match(new RegExp(`\\b${escapedName}\\s*=\\s*(["'])([\\s\\S]*?)\\1`, "i"))?.[2];
1332
+ }
1333
+ function getTagRange(start, end) {
1334
+ return { start, end };
1335
+ }
1336
+ function getElementEnd(source, tagName, tagEnd) {
1337
+ const openingTag = source.slice(0, tagEnd + 1);
1338
+ if (/\/\s*>$/.test(openingTag)) {
1339
+ return tagEnd + 1;
1340
+ }
1341
+ let depth = 1;
1342
+ let index = tagEnd + 1;
1343
+ while (index < source.length) {
1344
+ const nextStart = source.indexOf("<", index);
1345
+ if (nextStart === -1) {
1346
+ break;
1347
+ }
1348
+ if (source.startsWith("<!--", nextStart)) {
1349
+ index = source.indexOf("-->", nextStart + 4);
1350
+ index = index === -1 ? source.length : index + 3;
1351
+ continue;
1352
+ }
1353
+ const nextEnd = findTagEnd(source, nextStart);
1354
+ if (nextEnd === -1) {
1355
+ break;
1356
+ }
1357
+ const nextTag = source.slice(nextStart, nextEnd + 1);
1358
+ const closingMatch = nextTag.match(/^<\s*\/\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
1359
+ const openingMatch = nextTag.match(/^<\s*([A-Za-z_][A-Za-z0-9_.:-]*)/);
1360
+ if (closingMatch?.[1]?.toLowerCase() === tagName) {
1361
+ depth -= 1;
1362
+ if (depth === 0) {
1363
+ return nextEnd + 1;
1364
+ }
1365
+ } else if (openingMatch?.[1]?.toLowerCase() === tagName && !/\/\s*>$/.test(nextTag)) {
1366
+ depth += 1;
1367
+ }
1368
+ index = nextEnd + 1;
1369
+ }
1370
+ return tagEnd + 1;
1371
+ }
1372
+ function createLens(model, start, end, title, action) {
1373
+ const startPosition = model.getPositionAt(start);
1374
+ const endPosition = model.getPositionAt(end);
1375
+ return {
1376
+ range: {
1377
+ startLineNumber: startPosition.lineNumber,
1378
+ startColumn: startPosition.column,
1379
+ endLineNumber: endPosition.lineNumber,
1380
+ endColumn: endPosition.column
1381
+ },
1382
+ command: {
1383
+ id: CODE_LENS_COMMAND,
1384
+ title,
1385
+ arguments: [action]
1386
+ }
1387
+ };
1388
+ }
1389
+ function registerSsmlCodeLens(monaco, editor, onOpenPopover) {
1390
+ const provider = {
1391
+ provideCodeLenses(model) {
1392
+ const source = model.getValue();
1393
+ const lenses = [];
1394
+ let index = 0;
1395
+ while (index < source.length) {
1396
+ const tagStart = source.indexOf("<", index);
1397
+ if (tagStart === -1) {
1398
+ break;
1399
+ }
1400
+ if (source.startsWith("<!--", tagStart)) {
1401
+ index = source.indexOf("-->", tagStart + 4);
1402
+ index = index === -1 ? source.length : index + 3;
1403
+ continue;
1404
+ }
1405
+ const tagEnd = findTagEnd(source, tagStart);
1406
+ if (tagEnd === -1) {
1407
+ break;
1408
+ }
1409
+ const tag = source.slice(tagStart, tagEnd + 1);
1410
+ const tagName = tag.match(/^<\s*(prosody|break)\b/i)?.[1]?.toLowerCase();
1411
+ index = tagEnd + 1;
1412
+ if (!tagName) {
1413
+ continue;
1414
+ }
1415
+ const tagRange = getTagRange(tagStart, tagEnd + 1);
1416
+ const elementEnd = getElementEnd(source, tagName, tagEnd);
1417
+ const elementRange = { start: tagStart, end: elementEnd };
1418
+ if (tagName === "prosody") {
1419
+ lenses.push(
1420
+ createLens(
1421
+ model,
1422
+ tagStart,
1423
+ tagEnd + 1,
1424
+ `\u26A1 Rate: ${getAttributeValue(tag, "rate") ?? "default"} (Click to edit)`,
1425
+ { type: "attribute", insertionId: "rate", attributeName: "rate", tagRange }
1426
+ ),
1427
+ createLens(
1428
+ model,
1429
+ tagStart,
1430
+ tagEnd + 1,
1431
+ `\u26A1 Pitch: ${getAttributeValue(tag, "pitch") ?? "default"} (Click to edit)`,
1432
+ { type: "attribute", insertionId: "pitch", attributeName: "pitch", tagRange }
1433
+ ),
1434
+ createLens(model, tagStart, tagEnd + 1, "Unwrap", {
1435
+ type: "unwrap",
1436
+ tagRange,
1437
+ elementRange
1438
+ })
1439
+ );
1440
+ } else if (/\/\s*>$/.test(tag)) {
1441
+ lenses.push(
1442
+ createLens(
1443
+ model,
1444
+ tagStart,
1445
+ tagEnd + 1,
1446
+ `\u26A1 Time: ${getAttributeValue(tag, "time") ?? "default"} (Click to edit)`,
1447
+ { type: "attribute", insertionId: "break", attributeName: "time", tagRange }
1448
+ ),
1449
+ createLens(model, tagStart, tagEnd + 1, "Delete", {
1450
+ type: "delete",
1451
+ tagRange,
1452
+ elementRange
1453
+ })
1454
+ );
1455
+ }
1456
+ }
1457
+ return { lenses, dispose: () => void 0 };
1458
+ }
1459
+ };
1460
+ const disposable = monaco.languages.registerCodeLensProvider("xml", provider);
1461
+ const commandHandler = (_accessor, ...args) => {
1462
+ const action = args[0];
1463
+ if (action && typeof action === "object" && "type" in action) {
1464
+ onOpenPopover(action);
1465
+ }
1466
+ };
1467
+ 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({
1468
+ id: CODE_LENS_COMMAND,
1469
+ label: "SSML CodeLens",
1470
+ run: (_editor, ...args) => {
1471
+ const action = args[0];
1472
+ if (action && typeof action === "object" && "type" in action) {
1473
+ onOpenPopover(action);
1474
+ }
1475
+ }
1476
+ });
1477
+ return {
1478
+ dispose: () => {
1479
+ commandDisposable.dispose();
1480
+ disposable.dispose();
1481
+ }
1482
+ };
1483
+ }
1484
+
1193
1485
  // packages/ssml-editor-react/src/hooks/useSsmlMonaco.ts
1194
1486
  var SSML_DIAGNOSTICS_DEBOUNCE_MS = 300;
1195
1487
  var hoverProviderRegistrations = /* @__PURE__ */ new WeakMap();
@@ -1291,7 +1583,9 @@ function useSsmlMonaco({
1291
1583
  language,
1292
1584
  text,
1293
1585
  decorationsVisible,
1586
+ enableCodeLens,
1294
1587
  getOuterVoiceName,
1588
+ onOpenPopover,
1295
1589
  onSelectionOverlayChange,
1296
1590
  onActiveTagsChange,
1297
1591
  onSyntaxErrorChange
@@ -1301,6 +1595,7 @@ function useSsmlMonaco({
1301
1595
  const monacoRef = useRef2(null);
1302
1596
  const completionProviderRef = useRef2(null);
1303
1597
  const codeActionProviderRef = useRef2(null);
1598
+ const codeLensProviderRef = useRef2(null);
1304
1599
  const releaseHoverProviderRef = useRef2(null);
1305
1600
  const selectionChangeRef = useRef2(null);
1306
1601
  const cursorPositionChangeRef = useRef2(null);
@@ -1310,16 +1605,20 @@ function useSsmlMonaco({
1310
1605
  const inlineDecorationIdsRef = useRef2([]);
1311
1606
  const languageRef = useRef2(language);
1312
1607
  const decorationsVisibleRef = useRef2(decorationsVisible);
1608
+ const enableCodeLensRef = useRef2(enableCodeLens);
1313
1609
  const getOuterVoiceNameRef = useRef2(getOuterVoiceName);
1314
1610
  const onSelectionOverlayChangeRef = useRef2(onSelectionOverlayChange);
1315
1611
  const onActiveTagsChangeRef = useRef2(onActiveTagsChange);
1316
1612
  const onSyntaxErrorChangeRef = useRef2(onSyntaxErrorChange);
1613
+ const onOpenPopoverRef = useRef2(onOpenPopover);
1317
1614
  languageRef.current = language;
1318
1615
  decorationsVisibleRef.current = decorationsVisible;
1616
+ enableCodeLensRef.current = enableCodeLens;
1319
1617
  getOuterVoiceNameRef.current = getOuterVoiceName;
1320
1618
  onSelectionOverlayChangeRef.current = onSelectionOverlayChange;
1321
1619
  onActiveTagsChangeRef.current = onActiveTagsChange;
1322
1620
  onSyntaxErrorChangeRef.current = onSyntaxErrorChange;
1621
+ onOpenPopoverRef.current = onOpenPopover;
1323
1622
  const runSsmlDiagnostics = useCallback2((editor, monaco) => {
1324
1623
  const model = editor.getModel();
1325
1624
  if (!model) {
@@ -1372,6 +1671,8 @@ function useSsmlMonaco({
1372
1671
  completionProviderRef.current = null;
1373
1672
  codeActionProviderRef.current?.dispose();
1374
1673
  codeActionProviderRef.current = null;
1674
+ codeLensProviderRef.current?.dispose();
1675
+ codeLensProviderRef.current = null;
1375
1676
  for (const disposable of selectionLayoutDisposablesRef.current) {
1376
1677
  disposable.dispose();
1377
1678
  }
@@ -1416,6 +1717,13 @@ function useSsmlMonaco({
1416
1717
  model: editor.getModel()
1417
1718
  });
1418
1719
  codeActionProviderRef.current = registerSsmlCodeActions(monaco);
1720
+ if (enableCodeLensRef.current) {
1721
+ codeLensProviderRef.current = registerSsmlCodeLens(
1722
+ monaco,
1723
+ editor,
1724
+ (action) => onOpenPopoverRef.current(action)
1725
+ );
1726
+ }
1419
1727
  releaseHoverProviderRef.current = acquireSsmlHoverProvider(monaco, languageRef.current);
1420
1728
  runSsmlDiagnostics(editor, monaco);
1421
1729
  const model = editor.getModel();
@@ -1433,6 +1741,15 @@ function useSsmlMonaco({
1433
1741
  },
1434
1742
  [disposeMonacoResources, editorRef, runSsmlDiagnostics, scheduleSsmlDiagnostics, syncActiveTags]
1435
1743
  );
1744
+ useEffect2(() => {
1745
+ const editor = editorRef.current;
1746
+ const monaco = monacoRef.current;
1747
+ if (!editor || !monaco) {
1748
+ return;
1749
+ }
1750
+ codeLensProviderRef.current?.dispose();
1751
+ codeLensProviderRef.current = enableCodeLens ? registerSsmlCodeLens(monaco, editor, (action) => onOpenPopoverRef.current(action)) : null;
1752
+ }, [editorRef, enableCodeLens]);
1436
1753
  useEffect2(() => {
1437
1754
  const monaco = monacoRef.current;
1438
1755
  if (!monaco) {
@@ -1953,6 +2270,7 @@ var SsmlEditor = forwardRef(function SsmlEditor2({
1953
2270
  showToolbarIcons = true,
1954
2271
  showToolbarLabels = false,
1955
2272
  showDecorations = false,
2273
+ enableCodeLens = true,
1956
2274
  buttonVisibility,
1957
2275
  editorOptions,
1958
2276
  settings,
@@ -2085,6 +2403,7 @@ var SsmlEditor = forwardRef(function SsmlEditor2({
2085
2403
  getCurrentLineSsml: getCurrentLineSsml2,
2086
2404
  getFullSsml,
2087
2405
  getOuterVoiceName,
2406
+ handleCodeLensAction,
2088
2407
  openPopoverId,
2089
2408
  popoverVoiceName,
2090
2409
  popoverPosition,
@@ -2110,7 +2429,9 @@ var SsmlEditor = forwardRef(function SsmlEditor2({
2110
2429
  getOuterVoiceName,
2111
2430
  onSelectionOverlayChange: refreshSelectionOverlay,
2112
2431
  onActiveTagsChange: updateActiveTags,
2113
- onSyntaxErrorChange: setSyntaxError
2432
+ onSyntaxErrorChange: setSyntaxError,
2433
+ enableCodeLens,
2434
+ onOpenPopover: handleCodeLensAction
2114
2435
  });
2115
2436
  useImperativeHandle(
2116
2437
  ref,
@@ -2388,6 +2709,8 @@ export {
2388
2709
  SSML_HOVER_COPY,
2389
2710
  SSML_INSERTIONS,
2390
2711
  SsmlEditor,
2391
- createSsmlEditorInsertionDefinition
2712
+ createSsmlEditorInsertionDefinition,
2713
+ registerSsmlCodeLens,
2714
+ updateTagAttribute
2392
2715
  };
2393
2716
  //# sourceMappingURL=react.mjs.map