@ssml-builder-js/ssml-editor-elements 2.8.1 → 2.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -0
- package/dist/index.d.mts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +369 -6
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +370 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/SsmlEditorElement.ts +322 -3
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssml-builder-js/ssml-editor-elements",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.10.0",
|
|
4
4
|
"description": "Framework-independent SSML editor custom element",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,6 +37,6 @@
|
|
|
37
37
|
"monaco-editor": "^0.56.0"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@ssml-builder-js/ssml-core": "^2.
|
|
40
|
+
"@ssml-builder-js/ssml-core": "^2.10.0"
|
|
41
41
|
}
|
|
42
42
|
}
|
package/src/SsmlEditorElement.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type * as monaco from "monaco-editor";
|
|
2
|
-
import { buildSsml, parseSsml } from "@ssml-builder-js/ssml-core";
|
|
3
|
-
import type { SsmlDocument } from "@ssml-builder-js/ssml-core";
|
|
2
|
+
import { buildSsml, parseSsml, validateAzureSsml } from "@ssml-builder-js/ssml-core";
|
|
3
|
+
import type { SsmlDocument, SsmlElement, SsmlNode } from "@ssml-builder-js/ssml-core";
|
|
4
4
|
import { clearSsmlDocument } from "../../ssml-editor-react/src/clearSsmlDocument";
|
|
5
5
|
import { getEditableRegion, getEditableText, updateEditableText } from "../../ssml-editor-react/src/editableSsml";
|
|
6
6
|
import { formatXmlFragment } from "../../ssml-editor-react/src/formatXml";
|
|
@@ -245,6 +245,60 @@ section[data-ssml-editor] {
|
|
|
245
245
|
border-radius: 0.25rem;
|
|
246
246
|
overflow: visible;
|
|
247
247
|
}
|
|
248
|
+
[data-ssml-editor] .ssml-editor-visual {
|
|
249
|
+
display: grid;
|
|
250
|
+
gap: 0.75rem;
|
|
251
|
+
min-height: 8rem;
|
|
252
|
+
padding: 0.75rem;
|
|
253
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
254
|
+
border-radius: 0.25rem;
|
|
255
|
+
}
|
|
256
|
+
[data-ssml-editor] .ssml-editor-visual-layout {
|
|
257
|
+
display: grid;
|
|
258
|
+
grid-template-columns: minmax(12rem, 0.35fr) minmax(16rem, 1fr);
|
|
259
|
+
gap: 1rem;
|
|
260
|
+
}
|
|
261
|
+
[data-ssml-editor] .ssml-editor-visual-tree ul {
|
|
262
|
+
margin: 0;
|
|
263
|
+
padding-left: 1.25rem;
|
|
264
|
+
}
|
|
265
|
+
[data-ssml-editor] .ssml-editor-visual button {
|
|
266
|
+
padding: 0.35rem 0.5rem;
|
|
267
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
268
|
+
border-radius: 0.25rem;
|
|
269
|
+
color: var(--ssml-editor-color);
|
|
270
|
+
background: var(--ssml-editor-control-bg);
|
|
271
|
+
cursor: pointer;
|
|
272
|
+
}
|
|
273
|
+
[data-ssml-editor] .ssml-editor-visual button[data-selected="true"] {
|
|
274
|
+
border-color: var(--ssml-editor-active-border);
|
|
275
|
+
background: var(--ssml-editor-active-bg);
|
|
276
|
+
}
|
|
277
|
+
[data-ssml-editor] .ssml-editor-visual textarea {
|
|
278
|
+
box-sizing: border-box;
|
|
279
|
+
width: 100%;
|
|
280
|
+
min-height: 6rem;
|
|
281
|
+
padding: 0.5rem;
|
|
282
|
+
color: var(--ssml-editor-color);
|
|
283
|
+
background: var(--ssml-editor-control-bg);
|
|
284
|
+
border: 1px solid var(--ssml-editor-control-border);
|
|
285
|
+
border-radius: 0.25rem;
|
|
286
|
+
font: inherit;
|
|
287
|
+
}
|
|
288
|
+
[data-ssml-editor] .ssml-editor-visual-actions,
|
|
289
|
+
[data-ssml-editor] .ssml-editor-visual-breadcrumb {
|
|
290
|
+
display: flex;
|
|
291
|
+
flex-wrap: wrap;
|
|
292
|
+
gap: 0.4rem;
|
|
293
|
+
align-items: center;
|
|
294
|
+
}
|
|
295
|
+
[data-ssml-editor] .ssml-editor-visual-errors {
|
|
296
|
+
padding: 0.5rem;
|
|
297
|
+
color: #b91c1c;
|
|
298
|
+
background: #fef2f2;
|
|
299
|
+
border: 1px solid #b91c1c;
|
|
300
|
+
border-radius: 0.25rem;
|
|
301
|
+
}
|
|
248
302
|
`.trim();
|
|
249
303
|
|
|
250
304
|
function injectStyles(): void {
|
|
@@ -262,6 +316,78 @@ function isDarkTheme(theme: SsmlEditorTheme): boolean {
|
|
|
262
316
|
return theme === "vs-dark" || theme.toLowerCase().includes("dark");
|
|
263
317
|
}
|
|
264
318
|
|
|
319
|
+
function isElement(node: SsmlNode): node is SsmlElement {
|
|
320
|
+
return typeof node !== "string" && node.type !== "text";
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function visualElementName(element: SsmlElement): string {
|
|
324
|
+
return element.type === "custom" || element.type === "element" ? element.name : element.type;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
interface VisualTextLeaf {
|
|
328
|
+
path: number[];
|
|
329
|
+
value: string;
|
|
330
|
+
ancestors: string[];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function collectVisualTextLeaves(nodes: SsmlNode[], path: number[] = [], ancestors: string[] = []): VisualTextLeaf[] {
|
|
334
|
+
return nodes.flatMap((node, index) => {
|
|
335
|
+
const currentPath = [...path, index];
|
|
336
|
+
if (typeof node === "string") return [{ path: currentPath, value: node, ancestors }];
|
|
337
|
+
if (node.type === "text") return [{ path: currentPath, value: node.value, ancestors }];
|
|
338
|
+
return collectVisualTextLeaves(node.children ?? [], currentPath, [...ancestors, visualElementName(node)]);
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function updateVisualNodes(
|
|
343
|
+
nodes: SsmlNode[],
|
|
344
|
+
path: number[],
|
|
345
|
+
update: (node: SsmlNode) => SsmlNode | SsmlNode[],
|
|
346
|
+
): SsmlNode[] {
|
|
347
|
+
if (path.length === 0) return nodes;
|
|
348
|
+
const [index, ...rest] = path;
|
|
349
|
+
return nodes.flatMap((node, nodeIndex) => {
|
|
350
|
+
if (nodeIndex !== index) return [node];
|
|
351
|
+
if (rest.length === 0) {
|
|
352
|
+
const next = update(node);
|
|
353
|
+
return Array.isArray(next) ? next : [next];
|
|
354
|
+
}
|
|
355
|
+
if (!isElement(node)) return [node];
|
|
356
|
+
return [{ ...node, children: updateVisualNodes(node.children ?? [], rest, update) }];
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function updateVisualText(document: SsmlDocument, path: number[], value: string): SsmlDocument {
|
|
361
|
+
return { ...document, children: updateVisualNodes(document.children ?? [], path, () => value) };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function wrapVisualText(
|
|
365
|
+
document: SsmlDocument,
|
|
366
|
+
path: number[],
|
|
367
|
+
start: number,
|
|
368
|
+
end: number,
|
|
369
|
+
type: SsmlElement["type"],
|
|
370
|
+
attributes: Record<string, string>,
|
|
371
|
+
): SsmlDocument {
|
|
372
|
+
return {
|
|
373
|
+
...document,
|
|
374
|
+
children: updateVisualNodes(document.children ?? [], path, (node) => {
|
|
375
|
+
const value = typeof node === "string" ? node : node.type === "text" ? node.value : "";
|
|
376
|
+
if (!value || start === end) return node;
|
|
377
|
+
if (type === "break") {
|
|
378
|
+
return [value.slice(0, start), { type, attributes, children: [] } as SsmlElement, value.slice(start)].filter(
|
|
379
|
+
(part) => (typeof part === "string" ? part.length > 0 : true),
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
const selected = value.slice(start, end);
|
|
383
|
+
const wrapper = { type, attributes, children: [selected] } as SsmlElement;
|
|
384
|
+
return [value.slice(0, start), wrapper, value.slice(end)].filter((part) =>
|
|
385
|
+
typeof part === "string" ? part.length > 0 : true,
|
|
386
|
+
);
|
|
387
|
+
}),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
265
391
|
function getMenuPosition(trigger: HTMLElement, menu: HTMLElement): { top: number; left: number } {
|
|
266
392
|
const bounds = trigger.getBoundingClientRect();
|
|
267
393
|
const margin = 8;
|
|
@@ -283,6 +409,7 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
283
409
|
"show-toolbar",
|
|
284
410
|
"show-toolbar-labels",
|
|
285
411
|
"show-decorations",
|
|
412
|
+
"edit-mode",
|
|
286
413
|
];
|
|
287
414
|
|
|
288
415
|
private editor: MonacoEditor | null = null;
|
|
@@ -293,6 +420,7 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
293
420
|
private toolbarActions: HTMLElement | null = null;
|
|
294
421
|
private display: HTMLElement | null = null;
|
|
295
422
|
private editorContainer: HTMLDivElement | null = null;
|
|
423
|
+
private visualContainer: HTMLDivElement | null = null;
|
|
296
424
|
private helpPanel: HTMLElement | null = null;
|
|
297
425
|
private openMenu: HTMLElement | null = null;
|
|
298
426
|
private openMenuTrigger: HTMLButtonElement | null = null;
|
|
@@ -307,6 +435,8 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
307
435
|
private documentState: SsmlDocument | null = null;
|
|
308
436
|
private decorationsVisible = false;
|
|
309
437
|
private helpOpen = false;
|
|
438
|
+
private visualSelectedPath: number[] | null = null;
|
|
439
|
+
private visualSelection = { start: 0, end: 0 };
|
|
310
440
|
|
|
311
441
|
get value(): string {
|
|
312
442
|
return this.valueState ?? this.getAttribute("value") ?? "";
|
|
@@ -345,6 +475,14 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
345
475
|
this.setAttribute("locale", locale);
|
|
346
476
|
}
|
|
347
477
|
|
|
478
|
+
get editMode(): "code" | "visual" {
|
|
479
|
+
return this.getAttribute("edit-mode") === "visual" ? "visual" : "code";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
set editMode(mode: "code" | "visual") {
|
|
483
|
+
this.setAttribute("edit-mode", mode);
|
|
484
|
+
}
|
|
485
|
+
|
|
348
486
|
private prepareDocument(value: string): string {
|
|
349
487
|
try {
|
|
350
488
|
this.documentState = parseSsml(value);
|
|
@@ -391,6 +529,7 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
391
529
|
}
|
|
392
530
|
}
|
|
393
531
|
}
|
|
532
|
+
this.renderVisualEditor();
|
|
394
533
|
return;
|
|
395
534
|
}
|
|
396
535
|
|
|
@@ -412,6 +551,10 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
412
551
|
return;
|
|
413
552
|
}
|
|
414
553
|
|
|
554
|
+
if (name === "edit-mode") {
|
|
555
|
+
this.updateEditMode();
|
|
556
|
+
}
|
|
557
|
+
|
|
415
558
|
if (name === "show-decorations") {
|
|
416
559
|
this.decorationsVisible = newValue !== null;
|
|
417
560
|
this.updateDecorations();
|
|
@@ -439,7 +582,9 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
439
582
|
display.dataset.ssmlEditorDisplay = "";
|
|
440
583
|
const editorContainer = document.createElement("div");
|
|
441
584
|
editorContainer.className = "ssml-editor-editor";
|
|
442
|
-
|
|
585
|
+
const visualContainer = document.createElement("div");
|
|
586
|
+
visualContainer.className = "ssml-editor-visual";
|
|
587
|
+
display.append(editorContainer, visualContainer);
|
|
443
588
|
root.append(toolbar, display);
|
|
444
589
|
this.replaceChildren(root);
|
|
445
590
|
|
|
@@ -448,8 +593,11 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
448
593
|
this.toolbarActions = toolbarActions;
|
|
449
594
|
this.display = display;
|
|
450
595
|
this.editorContainer = editorContainer;
|
|
596
|
+
this.visualContainer = visualContainer;
|
|
451
597
|
this.renderToolbar();
|
|
452
598
|
this.renderHelp();
|
|
599
|
+
this.updateEditMode();
|
|
600
|
+
this.renderVisualEditor();
|
|
453
601
|
}
|
|
454
602
|
|
|
455
603
|
private renderToolbar(): void {
|
|
@@ -513,9 +661,26 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
513
661
|
toolbarActions.append(this.createActionButton(id));
|
|
514
662
|
}
|
|
515
663
|
}
|
|
664
|
+
const separator = document.createElement("span");
|
|
665
|
+
separator.className = "ssml-editor-toolbar-separator";
|
|
666
|
+
separator.setAttribute("aria-hidden", "true");
|
|
667
|
+
toolbarActions.append(separator, this.createModeButton("visual", "Visual"), this.createModeButton("code", "Code"));
|
|
516
668
|
this.updateActiveButtons();
|
|
517
669
|
}
|
|
518
670
|
|
|
671
|
+
private createModeButton(mode: "code" | "visual", label: string): HTMLButtonElement {
|
|
672
|
+
const button = document.createElement("button");
|
|
673
|
+
button.type = "button";
|
|
674
|
+
button.className = "ssml-editor-toolbar-button";
|
|
675
|
+
button.dataset.ssmlEditorButton = `edit-mode-${mode}`;
|
|
676
|
+
button.setAttribute("aria-pressed", String(this.editMode === mode));
|
|
677
|
+
button.textContent = label;
|
|
678
|
+
button.addEventListener("click", () => {
|
|
679
|
+
this.editMode = mode;
|
|
680
|
+
});
|
|
681
|
+
return button;
|
|
682
|
+
}
|
|
683
|
+
|
|
519
684
|
private createActionButton(id: string): HTMLButtonElement {
|
|
520
685
|
const copy = EDITOR_COPY[this.locale];
|
|
521
686
|
const labels: Record<string, string> = {
|
|
@@ -938,6 +1103,159 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
938
1103
|
}
|
|
939
1104
|
}
|
|
940
1105
|
|
|
1106
|
+
private updateEditMode(): void {
|
|
1107
|
+
if (this.editorContainer && this.visualContainer) {
|
|
1108
|
+
const visual = this.editMode === "visual";
|
|
1109
|
+
this.editorContainer.hidden = visual;
|
|
1110
|
+
this.visualContainer.hidden = !visual;
|
|
1111
|
+
}
|
|
1112
|
+
for (const mode of ["visual", "code"] as const) {
|
|
1113
|
+
const button = this.toolbarActions?.querySelector<HTMLButtonElement>(
|
|
1114
|
+
`[data-ssml-editor-button="edit-mode-${mode}"]`,
|
|
1115
|
+
);
|
|
1116
|
+
button?.setAttribute("aria-pressed", String(this.editMode === mode));
|
|
1117
|
+
button?.toggleAttribute("data-active", this.editMode === mode);
|
|
1118
|
+
}
|
|
1119
|
+
this.renderVisualEditor();
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
private renderVisualEditor(): void {
|
|
1123
|
+
const container = this.visualContainer;
|
|
1124
|
+
if (!container) return;
|
|
1125
|
+
container.replaceChildren();
|
|
1126
|
+
if (!this.documentState) {
|
|
1127
|
+
const error = document.createElement("p");
|
|
1128
|
+
error.className = "ssml-editor-visual-errors";
|
|
1129
|
+
error.textContent = "SSML syntax must be valid before visual editing is available.";
|
|
1130
|
+
container.append(error);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
const documentState = this.documentState;
|
|
1135
|
+
const leaves = collectVisualTextLeaves(documentState.children ?? []);
|
|
1136
|
+
const selectedLeaf = leaves.find((leaf) => leaf.path.join(".") === this.visualSelectedPath?.join(".")) ?? leaves[0];
|
|
1137
|
+
const breadcrumb = document.createElement("div");
|
|
1138
|
+
breadcrumb.className = "ssml-editor-visual-breadcrumb";
|
|
1139
|
+
breadcrumb.textContent = `<speak>${selectedLeaf ? ` / ${selectedLeaf.ancestors.map((name) => `<${name}>`).join(" / ")}` : ""}`;
|
|
1140
|
+
const clear = document.createElement("button");
|
|
1141
|
+
clear.type = "button";
|
|
1142
|
+
clear.textContent = "Clear parent";
|
|
1143
|
+
clear.disabled = !this.visualSelectedPath;
|
|
1144
|
+
clear.addEventListener("click", () => {
|
|
1145
|
+
this.visualSelectedPath = null;
|
|
1146
|
+
this.renderVisualEditor();
|
|
1147
|
+
});
|
|
1148
|
+
breadcrumb.append(clear);
|
|
1149
|
+
container.append(breadcrumb);
|
|
1150
|
+
|
|
1151
|
+
const diagnostics = validateAzureSsml(buildSsml(documentState));
|
|
1152
|
+
if (diagnostics.length > 0) {
|
|
1153
|
+
const errors = document.createElement("div");
|
|
1154
|
+
errors.className = "ssml-editor-visual-errors";
|
|
1155
|
+
errors.setAttribute("role", "alert");
|
|
1156
|
+
for (const diagnostic of diagnostics) {
|
|
1157
|
+
const message = document.createElement("div");
|
|
1158
|
+
message.textContent = diagnostic.message;
|
|
1159
|
+
errors.append(message);
|
|
1160
|
+
}
|
|
1161
|
+
container.append(errors);
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
const layout = document.createElement("div");
|
|
1165
|
+
layout.className = "ssml-editor-visual-layout";
|
|
1166
|
+
const tree = document.createElement("nav");
|
|
1167
|
+
tree.className = "ssml-editor-visual-tree";
|
|
1168
|
+
tree.setAttribute("aria-label", "SSML structure tree");
|
|
1169
|
+
tree.append(document.createTextNode("Structure"));
|
|
1170
|
+
const treeList = document.createElement("ul");
|
|
1171
|
+
const renderTree = (nodes: SsmlNode[], parent: HTMLElement, parentPath: number[] = []): void => {
|
|
1172
|
+
nodes.forEach((node, index) => {
|
|
1173
|
+
if (!isElement(node)) return;
|
|
1174
|
+
const path = [...parentPath, index];
|
|
1175
|
+
const item = document.createElement("li");
|
|
1176
|
+
const button = document.createElement("button");
|
|
1177
|
+
button.type = "button";
|
|
1178
|
+
button.textContent = `<${visualElementName(node)}>`;
|
|
1179
|
+
button.dataset.selected = String(path.join(".") === this.visualSelectedPath?.join("."));
|
|
1180
|
+
button.addEventListener("click", () => {
|
|
1181
|
+
this.visualSelectedPath = path;
|
|
1182
|
+
this.renderVisualEditor();
|
|
1183
|
+
});
|
|
1184
|
+
item.append(button);
|
|
1185
|
+
if ((node.children ?? []).some(isElement)) {
|
|
1186
|
+
const childList = document.createElement("ul");
|
|
1187
|
+
renderTree(node.children ?? [], childList, path);
|
|
1188
|
+
item.append(childList);
|
|
1189
|
+
}
|
|
1190
|
+
parent.append(item);
|
|
1191
|
+
});
|
|
1192
|
+
};
|
|
1193
|
+
renderTree(documentState.children ?? [], treeList);
|
|
1194
|
+
tree.append(treeList);
|
|
1195
|
+
layout.append(tree);
|
|
1196
|
+
|
|
1197
|
+
const form = document.createElement("div");
|
|
1198
|
+
form.className = "ssml-editor-visual-form";
|
|
1199
|
+
if (selectedLeaf) {
|
|
1200
|
+
const label = document.createElement("label");
|
|
1201
|
+
label.append(document.createTextNode("Text"));
|
|
1202
|
+
const textarea = document.createElement("textarea");
|
|
1203
|
+
textarea.value = selectedLeaf.value;
|
|
1204
|
+
textarea.readOnly = this.readonly;
|
|
1205
|
+
textarea.addEventListener("select", () => {
|
|
1206
|
+
this.visualSelection = { start: textarea.selectionStart, end: textarea.selectionEnd };
|
|
1207
|
+
});
|
|
1208
|
+
textarea.addEventListener("input", () => {
|
|
1209
|
+
if (!this.readonly) this.replaceDocument(updateVisualText(documentState, selectedLeaf.path, textarea.value));
|
|
1210
|
+
});
|
|
1211
|
+
label.append(textarea);
|
|
1212
|
+
form.append(label);
|
|
1213
|
+
const actions = document.createElement("div");
|
|
1214
|
+
actions.className = "ssml-editor-visual-actions";
|
|
1215
|
+
const wrappers: readonly [string, SsmlElement["type"], Record<string, string>][] = [
|
|
1216
|
+
["Rate", "prosody", { rate: "slow" }],
|
|
1217
|
+
["Pitch", "prosody", { pitch: "high" }],
|
|
1218
|
+
["Emotion", "mstts:express-as", { style: "cheerful" }],
|
|
1219
|
+
["Pause", "break", { time: "500ms" }],
|
|
1220
|
+
["Pronunciation", "phoneme", { alphabet: "ipa", ph: selectedLeaf.value }],
|
|
1221
|
+
];
|
|
1222
|
+
for (const [labelText, type, attributes] of wrappers) {
|
|
1223
|
+
const button = document.createElement("button");
|
|
1224
|
+
button.type = "button";
|
|
1225
|
+
button.textContent = labelText;
|
|
1226
|
+
button.disabled = this.readonly;
|
|
1227
|
+
button.addEventListener("click", () => {
|
|
1228
|
+
const start = this.visualSelection.start === this.visualSelection.end ? 0 : this.visualSelection.start;
|
|
1229
|
+
const end =
|
|
1230
|
+
this.visualSelection.start === this.visualSelection.end
|
|
1231
|
+
? selectedLeaf.value.length
|
|
1232
|
+
: this.visualSelection.end;
|
|
1233
|
+
this.replaceDocument(wrapVisualText(documentState, selectedLeaf.path, start, end, type, attributes));
|
|
1234
|
+
this.renderVisualEditor();
|
|
1235
|
+
});
|
|
1236
|
+
actions.append(button);
|
|
1237
|
+
}
|
|
1238
|
+
const preview = document.createElement("button");
|
|
1239
|
+
preview.type = "button";
|
|
1240
|
+
preview.textContent = "Preview selection";
|
|
1241
|
+
preview.addEventListener("click", () => {
|
|
1242
|
+
this.dispatchEvent(
|
|
1243
|
+
new CustomEvent("preview-selection", {
|
|
1244
|
+
detail: { ssml: buildSsml({ ...documentState, children: [selectedLeaf.value] }) },
|
|
1245
|
+
bubbles: true,
|
|
1246
|
+
composed: true,
|
|
1247
|
+
}),
|
|
1248
|
+
);
|
|
1249
|
+
});
|
|
1250
|
+
actions.append(preview);
|
|
1251
|
+
form.append(actions);
|
|
1252
|
+
} else {
|
|
1253
|
+
form.textContent = "Select an element or text node to edit it.";
|
|
1254
|
+
}
|
|
1255
|
+
layout.append(form);
|
|
1256
|
+
container.append(layout);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
941
1259
|
private updateActiveButtons(): void {
|
|
942
1260
|
const editor = this.editor;
|
|
943
1261
|
const model = this.model;
|
|
@@ -969,6 +1287,7 @@ export class SsmlEditorElement extends HTMLElementBase {
|
|
|
969
1287
|
}
|
|
970
1288
|
|
|
971
1289
|
const model = monacoModule.editor.createModel(this.prepareDocument(this.value), "xml");
|
|
1290
|
+
this.renderVisualEditor();
|
|
972
1291
|
const editor = monacoModule.editor.create(container, {
|
|
973
1292
|
model,
|
|
974
1293
|
theme: this.theme,
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { SsmlEditorElement } from "./SsmlEditorElement";
|
|
|
2
2
|
|
|
3
3
|
export { SsmlEditorElement } from "./SsmlEditorElement";
|
|
4
4
|
export type { SsmlEditorChangeDetail, SsmlEditorLocale, SsmlEditorTheme } from "./SsmlEditorElement";
|
|
5
|
+
export type SsmlEditorEditMode = "code" | "visual";
|
|
5
6
|
|
|
6
7
|
export function defineSsmlEditorElement(): void {
|
|
7
8
|
if (typeof customElements === "undefined" || customElements.get(SsmlEditorElement.tagName)) {
|