@jxsuite/studio 0.33.0 → 0.35.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.
Files changed (106) hide show
  1. package/dist/iframe-entry.js +6238 -0
  2. package/dist/iframe-entry.js.map +35 -0
  3. package/dist/studio.js +30972 -17222
  4. package/dist/studio.js.map +328 -83
  5. package/package.json +9 -9
  6. package/src/browse/browse.ts +11 -4
  7. package/src/canvas/canvas-helpers.ts +2 -56
  8. package/src/canvas/canvas-live-render.ts +102 -435
  9. package/src/canvas/canvas-origin.ts +66 -0
  10. package/src/canvas/canvas-patcher.ts +63 -403
  11. package/src/canvas/canvas-render.ts +70 -212
  12. package/src/canvas/canvas-utils.ts +37 -65
  13. package/src/canvas/iframe-channel.ts +154 -0
  14. package/src/canvas/iframe-drop.ts +484 -0
  15. package/src/canvas/iframe-entry.ts +600 -0
  16. package/src/canvas/iframe-host.ts +1373 -0
  17. package/src/canvas/iframe-inline-edit.ts +367 -0
  18. package/src/canvas/iframe-insert.ts +164 -0
  19. package/src/canvas/iframe-interaction.ts +176 -0
  20. package/src/canvas/iframe-keys.ts +85 -0
  21. package/src/canvas/iframe-overlay.ts +218 -0
  22. package/src/canvas/iframe-patch.ts +363 -0
  23. package/src/canvas/iframe-protocol.ts +361 -0
  24. package/src/canvas/iframe-render.ts +458 -0
  25. package/src/canvas/iframe-slash.ts +114 -0
  26. package/src/canvas/iframe-subtree.ts +113 -0
  27. package/src/canvas/path-mapping.ts +86 -0
  28. package/src/canvas/serialize-scope.ts +65 -0
  29. package/src/editor/canvas-context-menu.ts +40 -0
  30. package/src/editor/canvas-slash-bridge.ts +21 -0
  31. package/src/editor/context-menu.ts +2 -1
  32. package/src/editor/inline-edit-apply.ts +183 -0
  33. package/src/editor/inline-edit.ts +99 -21
  34. package/src/editor/inline-link.ts +89 -0
  35. package/src/editor/insert-zone-action.ts +35 -0
  36. package/src/editor/merge-tags.ts +26 -2
  37. package/src/editor/repeater-scope.ts +144 -0
  38. package/src/editor/shortcuts.ts +14 -28
  39. package/src/editor/slash-menu.ts +73 -42
  40. package/src/files/files.ts +5 -1
  41. package/src/new-project/design-fields.ts +260 -0
  42. package/src/new-project/import-tab.ts +322 -0
  43. package/src/new-project/new-project-modal.ts +472 -89
  44. package/src/new-project/templates.ts +61 -0
  45. package/src/packages/ensure-deps.ts +6 -0
  46. package/src/packages/pull-package-sync.ts +339 -0
  47. package/src/page-params.ts +383 -0
  48. package/src/panels/ai-chat/attached-context.ts +49 -0
  49. package/src/panels/ai-chat/chat-markdown.ts +38 -0
  50. package/src/panels/ai-chat/chat-view.ts +250 -0
  51. package/src/panels/ai-chat/composer.ts +315 -0
  52. package/src/panels/ai-chat/sessions-view.ts +98 -0
  53. package/src/panels/ai-panel.ts +189 -457
  54. package/src/panels/block-action-bar.ts +296 -138
  55. package/src/panels/canvas-dnd-bridge.ts +397 -0
  56. package/src/panels/component-preview.ts +56 -0
  57. package/src/panels/dnd.ts +41 -17
  58. package/src/panels/drag-ghost.ts +62 -0
  59. package/src/panels/editors.ts +1 -1
  60. package/src/panels/git-panel.ts +16 -1
  61. package/src/panels/overlays.ts +10 -125
  62. package/src/panels/properties-panel.ts +210 -0
  63. package/src/panels/right-panel.ts +21 -7
  64. package/src/panels/signals-panel.ts +136 -22
  65. package/src/panels/stylebook-doc.ts +373 -0
  66. package/src/panels/stylebook-panel.ts +46 -689
  67. package/src/panels/tab-bar.ts +159 -13
  68. package/src/panels/toolbar.ts +3 -2
  69. package/src/platforms/devserver.ts +45 -1
  70. package/src/services/agent-seed.ts +91 -0
  71. package/src/services/ai-models.ts +64 -0
  72. package/src/services/ai-session-store.ts +250 -0
  73. package/src/services/ai-settings.ts +5 -0
  74. package/src/services/automation.ts +140 -0
  75. package/src/services/document-assistant.ts +98 -38
  76. package/src/services/import-client.ts +134 -0
  77. package/src/services/monaco-setup.ts +12 -0
  78. package/src/services/render-critic.ts +9 -9
  79. package/src/services/settings-store.ts +93 -0
  80. package/src/settings/css-vars-editor.ts +2 -2
  81. package/src/store.ts +4 -62
  82. package/src/studio.ts +115 -40
  83. package/src/tabs/doc-op-apply.ts +89 -0
  84. package/src/tabs/patch-ops.ts +6 -2
  85. package/src/tabs/tab.ts +23 -4
  86. package/src/tabs/transact.ts +2 -74
  87. package/src/types.ts +98 -18
  88. package/src/ui/ai-credentials-form.ts +205 -0
  89. package/src/ui/jx-theme.ts +63 -0
  90. package/src/ui/media-picker.ts +6 -4
  91. package/src/ui/spectrum.ts +13 -0
  92. package/src/utils/canvas-media.ts +0 -137
  93. package/src/utils/edit-display.ts +23 -3
  94. package/src/utils/geometry.ts +43 -0
  95. package/src/utils/link-target.ts +93 -0
  96. package/src/utils/strip-events.ts +54 -0
  97. package/src/view.ts +0 -23
  98. package/src/workspace/workspace.ts +14 -1
  99. package/src/canvas/canvas-subtree-render.ts +0 -113
  100. package/src/editor/component-inline-edit.ts +0 -349
  101. package/src/editor/content-inline-edit.ts +0 -207
  102. package/src/editor/insertion-helper.ts +0 -308
  103. package/src/panels/canvas-dnd.ts +0 -329
  104. package/src/panels/panel-events.ts +0 -306
  105. package/src/panels/preview-render.ts +0 -132
  106. package/src/panels/pseudo-preview.ts +0 -75
@@ -1,349 +0,0 @@
1
- /// <reference lib="dom" />
2
- /**
3
- * Component inline edit — extracted from studio.js (Phase 4j). Manages plaintext-only editing on
4
- * canvas elements in design mode, with slash menu delegation for block insertion.
5
- */
6
-
7
- import {
8
- canvasPanels,
9
- childIndex,
10
- elToPath,
11
- getNodeAtPath,
12
- parentElementPath,
13
- renderOnly,
14
- updateUi,
15
- } from "../store";
16
- import { activeTab } from "../workspace/workspace";
17
- import type { JxPath } from "../state";
18
- import {
19
- mutateInsertNode,
20
- mutateRemoveNode,
21
- mutateUpdateProperty,
22
- transactDoc,
23
- } from "../tabs/transact";
24
- import { view } from "../view";
25
- import { panelMediaToActiveMedia } from "../canvas/canvas-helpers";
26
- import { dismissSlashMenu, isSlashMenuOpen, showSlashMenu } from "./slash-menu";
27
- import { renderBlockActionBar } from "../panels/block-action-bar";
28
- import { defaultDef } from "../panels/shared";
29
-
30
- /**
31
- * Initialize the component inline edit module.
32
- *
33
- * @param {{
34
- * findCanvasElement: (
35
- * path: import("../state").JxPath,
36
- * canvasEl: HTMLElement,
37
- * ) => HTMLElement | null;
38
- * }} _ctx
39
- */
40
- export function initComponentInlineEdit(_ctx: {
41
- findCanvasElement: (path: JxPath, canvasEl: HTMLElement) => HTMLElement | null;
42
- }) {
43
- // Public init hook retained for API symmetry; the ctx it once stored is no longer read here.
44
- void _ctx;
45
- }
46
-
47
- /**
48
- * Enter plaintext inline editing on a canvas element.
49
- *
50
- * @param {HTMLElement} el
51
- * @param {JxPath} path
52
- */
53
- export function enterComponentInlineEdit(el: HTMLElement, path: JxPath) {
54
- if (view.componentInlineEdit && view.componentInlineEdit.el === el) {
55
- return;
56
- }
57
-
58
- const node = getNodeAtPath(activeTab.value!.doc.document, path);
59
- if (!node) {
60
- return;
61
- }
62
-
63
- const tc = node.textContent;
64
- if (node.$props && (node.tagName || "").includes("-")) {
65
- return;
66
- }
67
- if (Array.isArray(node.children) && node.children.length > 0) {
68
- return;
69
- }
70
- if (node.children && typeof node.children === "object") {
71
- return;
72
- }
73
- if (tc && typeof tc === "object") {
74
- return;
75
- }
76
- const voids = new Set(["img", "input", "br", "hr", "video", "audio", "source", "embed", "slot"]);
77
- if (voids.has(node.tagName || "")) {
78
- return;
79
- }
80
-
81
- for (const p of canvasPanels) {
82
- const boxes = p.overlay.querySelectorAll(".overlay-box") as NodeListOf<HTMLElement>;
83
- for (const box of boxes) {
84
- box.style.border = "none";
85
- }
86
- p.overlayClk.style.pointerEvents = "none";
87
- }
88
-
89
- el.contentEditable = "plaintext-only";
90
- el.style.pointerEvents = "auto";
91
- el.style.cursor = "text";
92
- el.style.outline = "1px solid var(--accent, #4f8bc7)";
93
- el.style.outlineOffset = "-1px";
94
- el.style.minHeight = "1em";
95
-
96
- const rawText = typeof tc === "string" ? tc : "";
97
- el.textContent = rawText;
98
-
99
- view.componentInlineEdit = {
100
- el,
101
- mediaName: canvasPanels.find((p) => p.canvas.contains(el))?.mediaName || null,
102
- originalText: rawText,
103
- path,
104
- };
105
-
106
- el.focus();
107
- const sel = window.getSelection();
108
- const range = document.createRange();
109
- range.selectNodeContents(el);
110
- range.collapse(false);
111
- sel?.removeAllRanges();
112
- sel?.addRange(range);
113
-
114
- el.addEventListener("keydown", componentInlineKeydown);
115
- el.addEventListener("input", componentInlineInput);
116
-
117
- const outsideHandler = (evt: MouseEvent) => {
118
- if (!view.componentInlineEdit) {
119
- document.removeEventListener("mousedown", outsideHandler, true);
120
- return;
121
- }
122
- if (view.componentInlineEdit.el.contains(evt.target as Node)) {
123
- return;
124
- }
125
- if (isSlashMenuOpen()) {
126
- return;
127
- }
128
- if (view.blockActionBarEl && view.blockActionBarEl.contains(evt.target as Node)) {
129
- return;
130
- }
131
- document.removeEventListener("mousedown", outsideHandler, true);
132
-
133
- let hitMedia = null;
134
- let hitPath: (string | number)[] | null = null;
135
- for (const p of canvasPanels) {
136
- const els = p.canvas.querySelectorAll("*") as NodeListOf<HTMLElement>;
137
- for (const child of els) {
138
- child.style.pointerEvents = "auto";
139
- }
140
- p.overlayClk.style.display = "none";
141
- const found = document.elementsFromPoint(evt.clientX, evt.clientY);
142
- p.overlayClk.style.display = "";
143
- for (const child of els) {
144
- child.style.pointerEvents = "none";
145
- }
146
- for (const hit of found) {
147
- if (p.canvas.contains(hit) && hit !== p.canvas) {
148
- const hitElPath = elToPath.get(hit);
149
- if (hitElPath) {
150
- hitPath = hitElPath;
151
- hitMedia = p.mediaName;
152
- break;
153
- }
154
- }
155
- }
156
- if (hitPath) {
157
- break;
158
- }
159
- }
160
-
161
- const { el: editEl, path: editPath, originalText } = view.componentInlineEdit;
162
- const newText = (editEl.textContent ?? "").trim();
163
- cleanupComponentInlineEdit(editEl);
164
-
165
- const isEmpty = !newText;
166
- const pPath = parentElementPath(editPath);
167
-
168
- if (hitPath) {
169
- let hp = hitPath;
170
- const media = panelMediaToActiveMedia(hitMedia);
171
- updateUi("pendingInlineEdit", { mediaName: hitMedia, path: hp });
172
- activeTab.value!.session.ui.activeMedia = media;
173
- if (isEmpty && pPath) {
174
- transactDoc(activeTab.value, (t) => {
175
- mutateRemoveNode(t, editPath);
176
- const removedIdx = childIndex(editPath) as number;
177
- const hitIdx = childIndex(hp) as number;
178
- const hitParent = parentElementPath(hp);
179
- if (
180
- hitParent &&
181
- pPath &&
182
- hitParent.join("/") === pPath.join("/") &&
183
- hitIdx > removedIdx
184
- ) {
185
- hp = [...pPath, "children", hitIdx - 1];
186
- updateUi("pendingInlineEdit", { mediaName: hitMedia, path: hp });
187
- }
188
- t.session.selection = hp;
189
- });
190
- } else if (newText !== originalText) {
191
- transactDoc(activeTab.value, (t) => {
192
- mutateUpdateProperty(t, editPath, "textContent", newText || undefined);
193
- t.session.selection = hp;
194
- });
195
- } else {
196
- activeTab.value!.session.selection = hp;
197
- }
198
- } else if (isEmpty && pPath) {
199
- transactDoc(activeTab.value, (t) => mutateRemoveNode(t, editPath));
200
- } else if (newText !== originalText) {
201
- transactDoc(activeTab.value, (t) =>
202
- mutateUpdateProperty(t, editPath, "textContent", newText || undefined),
203
- );
204
- } else {
205
- renderOnly("canvas");
206
- renderOnly("overlays");
207
- }
208
- };
209
- document.addEventListener("mousedown", outsideHandler, true);
210
- view.componentInlineEdit._outsideHandler = outsideHandler;
211
-
212
- renderBlockActionBar();
213
- }
214
-
215
- /** @param {KeyboardEvent} e */
216
- function componentInlineKeydown(e: KeyboardEvent) {
217
- if (isSlashMenuOpen() && ["ArrowDown", "ArrowUp", "Enter", "Escape"].includes(e.key)) {
218
- return;
219
- }
220
-
221
- if (e.key === "Enter" && !e.shiftKey) {
222
- e.preventDefault();
223
- splitParagraph();
224
- } else if (e.key === "Escape") {
225
- e.preventDefault();
226
- cancelComponentInlineEdit();
227
- }
228
- e.stopPropagation();
229
- }
230
-
231
- function splitParagraph() {
232
- if (!view.componentInlineEdit) {
233
- return;
234
- }
235
- const { el, path, mediaName } = view.componentInlineEdit;
236
-
237
- const sel = el.ownerDocument.defaultView?.getSelection() as Selection | null;
238
- const fullText = el.textContent || "";
239
- let offset = fullText.length;
240
- if (sel && sel.rangeCount) {
241
- const range = sel.getRangeAt(0);
242
- const preRange = document.createRange();
243
- preRange.selectNodeContents(el);
244
- preRange.setEnd(range.startContainer, range.startOffset);
245
- offset = preRange.toString().length;
246
- }
247
-
248
- const textBefore = fullText.slice(0, offset);
249
- const textAfter = fullText.slice(offset);
250
-
251
- const tag = "p";
252
- const pPath = parentElementPath(path) as JxPath;
253
- const idx = childIndex(path) as number;
254
- if (!pPath) {
255
- return;
256
- }
257
-
258
- const newDef = { tagName: tag, textContent: textAfter };
259
- const newPath = [...pPath, "children", idx + 1];
260
-
261
- cleanupComponentInlineEdit(el);
262
-
263
- transactDoc(activeTab.value, (t) => {
264
- mutateUpdateProperty(t, path, "textContent", textBefore || undefined);
265
- mutateInsertNode(t, pPath, idx + 1, newDef);
266
- t.session.selection = newPath;
267
- });
268
-
269
- updateUi("pendingInlineEdit", { mediaName, path: newPath });
270
- }
271
-
272
- function cancelComponentInlineEdit() {
273
- if (!view.componentInlineEdit) {
274
- return;
275
- }
276
- const { el } = view.componentInlineEdit;
277
- cleanupComponentInlineEdit(el);
278
- renderOnly("canvas");
279
- renderOnly("overlays");
280
- }
281
-
282
- /** @param {HTMLElement} el */
283
- function cleanupComponentInlineEdit(el: HTMLElement) {
284
- el.removeEventListener("keydown", componentInlineKeydown);
285
- el.removeEventListener("input", componentInlineInput);
286
- dismissSlashMenu();
287
- el.removeAttribute("contenteditable");
288
- el.style.cursor = "";
289
- el.style.outline = "";
290
- el.style.outlineOffset = "";
291
- el.style.minHeight = "";
292
- el.style.pointerEvents = "";
293
-
294
- if (view.componentInlineEdit?._outsideHandler) {
295
- document.removeEventListener("mousedown", view.componentInlineEdit._outsideHandler, true);
296
- }
297
- view.componentInlineEdit = null;
298
-
299
- for (const p of canvasPanels) {
300
- p.overlay.style.display = "";
301
- p.overlayClk.style.pointerEvents = "";
302
- }
303
- }
304
-
305
- // ─── Component-mode slash commands ──────────────────────────────────────────
306
-
307
- function componentInlineInput() {
308
- if (!view.componentInlineEdit) {
309
- return;
310
- }
311
- const { el, originalText } = view.componentInlineEdit;
312
- const text = el.textContent || "";
313
-
314
- if (originalText === "" && text.startsWith("/")) {
315
- const filter = text.slice(1).toLowerCase();
316
- showSlashMenu(el, filter, { onSelect: handleComponentSlashSelect });
317
- } else {
318
- dismissSlashMenu();
319
- }
320
- }
321
-
322
- /** @param {{ tag: string; label: string; description: string }} cmd */
323
- function handleComponentSlashSelect(cmd: { tag: string; label: string; description: string }) {
324
- if (!view.componentInlineEdit) {
325
- return;
326
- }
327
- const { el, path, mediaName } = view.componentInlineEdit;
328
- const pPath = parentElementPath(path);
329
- const idx = childIndex(path) as number;
330
- if (!pPath) {
331
- return;
332
- }
333
-
334
- cleanupComponentInlineEdit(el);
335
-
336
- const newDef = defaultDef(cmd.tag);
337
- const newPath = [...pPath, "children", idx];
338
-
339
- transactDoc(activeTab.value, (t) => {
340
- mutateRemoveNode(t, path);
341
- mutateInsertNode(t, pPath, idx, newDef);
342
- t.session.selection = newPath;
343
- });
344
-
345
- const hasText = newDef.textContent != null;
346
- if (hasText) {
347
- updateUi("pendingInlineEdit", { mediaName, path: newPath });
348
- }
349
- }
@@ -1,207 +0,0 @@
1
- /// <reference lib="dom" />
2
- import { canvasPanels, childIndex, getNodeAtPath, parentElementPath, renderOnly } from "../store";
3
- import { activeTab } from "../workspace/workspace";
4
- import { mutateInsertNode, mutateUpdateProperty, transactDoc } from "../tabs/transact";
5
- import { view } from "../view";
6
- import { isEditableBlock, startEditing } from "./inline-edit";
7
- import { restoreTemplateExpressions } from "../utils/edit-display";
8
- import { renderBlockActionBar } from "../panels/block-action-bar";
9
- import { defaultDef } from "../panels/shared";
10
- import { findCanvasElement, getActivePanel } from "../canvas/canvas-helpers";
11
-
12
- import type { JxContentResult, SlashCommand } from "./inline-edit";
13
- import type { JxPath } from "../state";
14
- import type { JxMutableNode } from "@jxsuite/schema/types";
15
-
16
- /**
17
- * Enter rich-text inline editing on a canvas element (edit/content mode).
18
- *
19
- * @param {HTMLElement} el
20
- * @param {JxPath} path
21
- */
22
- export function enterInlineEdit(el: HTMLElement, path: JxPath) {
23
- // Restore raw template expressions before editing.
24
- // PrepareForEditMode renders ${expr} as ❪ expr ❫ for display;
25
- // Revert so the user edits the real syntax and commits it back intact.
26
- restoreTemplateExpressions(el);
27
-
28
- // Hide overlays while editing
29
- for (const p of canvasPanels) {
30
- p.overlay.style.display = "none";
31
- p.overlayClk.style.pointerEvents = "none";
32
- }
33
-
34
- startEditing(el, path, {
35
- onCommit(
36
- commitPath: JxPath,
37
- children: (JxMutableNode | string)[] | null,
38
- textContent: string | null,
39
- ) {
40
- const node = getNodeAtPath(activeTab.value!.doc.document, commitPath);
41
- if (children) {
42
- if (node && JSON.stringify(node.children) === JSON.stringify(children)) {
43
- return;
44
- }
45
- transactDoc(activeTab.value, (t) => {
46
- mutateUpdateProperty(t, commitPath, "textContent");
47
- mutateUpdateProperty(t, commitPath, "children", children);
48
- });
49
- } else if (textContent != null) {
50
- if (node && node.textContent === textContent && !node.children) {
51
- return;
52
- }
53
- transactDoc(activeTab.value, (t) => {
54
- mutateUpdateProperty(t, commitPath, "children");
55
- mutateUpdateProperty(t, commitPath, "textContent", textContent);
56
- });
57
- }
58
- },
59
-
60
- onEnd() {
61
- if (view.inlineEditCleanup) {
62
- view.inlineEditCleanup();
63
- view.inlineEditCleanup = null;
64
- }
65
- for (const p of canvasPanels) {
66
- p.overlay.style.display = "";
67
- p.overlayClk.style.pointerEvents = "";
68
- }
69
- renderOnly("overlays");
70
- },
71
-
72
- onInsert(afterPath: JxPath, cmd: SlashCommand, commitData: JxContentResult | undefined) {
73
- const isEmpty =
74
- !commitData ||
75
- (commitData.textContent != null && commitData.textContent.trim() === "") ||
76
- (commitData.children &&
77
- (commitData.children.length === 0 ||
78
- (commitData.children.length === 1 &&
79
- typeof commitData.children[0] === "string" &&
80
- commitData.children[0].trim() === "") ||
81
- (commitData.children.length === 1 &&
82
- typeof commitData.children[0] === "object" &&
83
- commitData.children[0]?.tagName === "br")));
84
-
85
- // If the element is empty, swap its tagName instead of inserting after
86
- if (isEmpty) {
87
- transactDoc(activeTab.value, (t) => {
88
- mutateUpdateProperty(t, afterPath, "tagName", cmd.tag);
89
- mutateUpdateProperty(t, afterPath, "children");
90
- const def = defaultDef(cmd.tag);
91
- if (def.textContent && def.textContent !== "Paragraph text") {
92
- mutateUpdateProperty(t, afterPath, "textContent", def.textContent);
93
- } else {
94
- mutateUpdateProperty(t, afterPath, "textContent");
95
- }
96
- t.session.selection = afterPath;
97
- });
98
-
99
- requestAnimationFrame(() => {
100
- const activePanel = getActivePanel();
101
- if (activePanel) {
102
- const nextEl = findCanvasElement(afterPath, activePanel.canvas);
103
- if (nextEl && isEditableBlock(nextEl)) {
104
- enterInlineEdit(nextEl, afterPath);
105
- }
106
- }
107
- });
108
- return;
109
- }
110
-
111
- const elementDef = defaultDef(cmd.tag);
112
- const parentPath = parentElementPath(afterPath) as JxPath;
113
- const idx = childIndex(afterPath) as number;
114
- const newPath = [...parentPath, "children", idx + 1];
115
-
116
- // Apply pending commit from inline edit first (batched to avoid double render)
117
- transactDoc(activeTab.value, (t) => {
118
- if (commitData) {
119
- if (commitData.children) {
120
- mutateUpdateProperty(t, afterPath, "textContent");
121
- mutateUpdateProperty(t, afterPath, "children", commitData.children);
122
- } else if (commitData.textContent != null) {
123
- mutateUpdateProperty(t, afterPath, "children");
124
- mutateUpdateProperty(t, afterPath, "textContent", commitData.textContent);
125
- }
126
- }
127
- mutateInsertNode(t, parentPath, idx + 1, structuredClone(elementDef));
128
- t.session.selection = newPath;
129
- });
130
-
131
- // If the inserted element is editable, enter editing
132
- requestAnimationFrame(() => {
133
- const activePanel = getActivePanel();
134
- if (activePanel) {
135
- const newEl = findCanvasElement(newPath, activePanel.canvas);
136
- if (newEl && isEditableBlock(newEl)) {
137
- enterInlineEdit(newEl, newPath);
138
- }
139
- }
140
- });
141
- },
142
-
143
- onSplit(splitPath: JxPath, before: JxContentResult, after: JxContentResult) {
144
- const tag = "p";
145
-
146
- // Insert new element after with "after" content
147
- const parentPath = parentElementPath(splitPath) as JxPath;
148
- const idx = childIndex(splitPath) as number;
149
- const newNode: JxMutableNode = { tagName: tag };
150
- if (after.textContent != null) {
151
- newNode.textContent = after.textContent;
152
- } else if (after.children) {
153
- newNode.children = after.children;
154
- } else {
155
- newNode.textContent = "";
156
- }
157
-
158
- const newPath = [...parentPath, "children", idx + 1];
159
-
160
- transactDoc(activeTab.value, (t) => {
161
- if (before.textContent != null) {
162
- mutateUpdateProperty(t, splitPath, "children");
163
- mutateUpdateProperty(t, splitPath, "textContent", before.textContent);
164
- } else if (before.children) {
165
- mutateUpdateProperty(t, splitPath, "textContent");
166
- mutateUpdateProperty(t, splitPath, "children", before.children);
167
- }
168
- mutateInsertNode(t, parentPath, idx + 1, newNode);
169
- t.session.selection = newPath;
170
- });
171
-
172
- // Re-enter editing on the new element after render
173
- requestAnimationFrame(() => {
174
- const activePanel = getActivePanel();
175
- if (activePanel) {
176
- const newEl = findCanvasElement(newPath, activePanel.canvas);
177
- if (newEl && isEditableBlock(newEl)) {
178
- enterInlineEdit(newEl, newPath);
179
- // Place cursor at start of new element
180
- const sel = window.getSelection();
181
- const range = document.createRange();
182
- range.selectNodeContents(newEl);
183
- range.collapse(true);
184
- sel?.removeAllRanges();
185
- sel?.addRange(range);
186
- }
187
- }
188
- });
189
- },
190
- });
191
-
192
- // Show the block action bar (with inline formatting buttons) on the viewport
193
- requestAnimationFrame(() => renderBlockActionBar());
194
-
195
- // Re-render action bar when selection changes inside contenteditable
196
- const selectionHandler = () => renderBlockActionBar();
197
- document.addEventListener("selectionchange", selectionHandler);
198
- el.addEventListener("mouseup", selectionHandler);
199
- el.addEventListener("keyup", selectionHandler);
200
-
201
- const inlineEditCleanup = () => {
202
- document.removeEventListener("selectionchange", selectionHandler);
203
- el.removeEventListener("mouseup", selectionHandler);
204
- el.removeEventListener("keyup", selectionHandler);
205
- };
206
- view.inlineEditCleanup = inlineEditCleanup;
207
- }