testomatio-editor-blocks 0.3.0 → 0.4.1

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.
@@ -1,9 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import OverType from "overtype";
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4
+ import { useComponentsContext } from "@blocknote/react";
5
+ import { EditLinkMenuItems } from "@blocknote/react";
4
6
  import { useStepAutocomplete } from "../stepAutocomplete";
5
7
  import { useStepImageUpload } from "../stepImageUpload";
6
8
  import { escapeMarkdownText, normalizePlainText } from "./markdown";
9
+ import { useAutoResize } from "./useAutoResize";
7
10
  const READ_ONLY_ALLOWED_KEYS = new Set([
8
11
  "ArrowDown",
9
12
  "ArrowUp",
@@ -12,6 +15,329 @@ const READ_ONLY_ALLOWED_KEYS = new Set([
12
15
  ]);
13
16
  const AUTOCOMPLETE_TRIGGER_KEYS = new Set([" ", "Space"]);
14
17
  const markdownParser = OverType.MarkdownParser;
18
+ function ImageUploadIcon() {
19
+ return (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", xmlns: "http://www.w3.org/2000/svg", "aria-hidden": "true", focusable: "false", children: _jsx("path", { d: "M12.667 2C13.0335 2.00008 13.3474 2.13057 13.6084 2.3916C13.8694 2.65264 13.9999 2.96648 14 3.33301V12.667C13.9999 13.0335 13.8694 13.3474 13.6084 13.6084C13.3474 13.8694 13.0335 13.9999 12.667 14H3.33301C2.96648 13.9999 2.65264 13.8694 2.3916 13.6084C2.13057 13.3474 2.00008 13.0335 2 12.667V3.33301C2.00008 2.96648 2.13057 2.65264 2.3916 2.3916C2.65264 2.13057 2.96648 2.00008 3.33301 2H12.667ZM3.33301 12.667H12.667V3.33301H3.33301V12.667ZM12 11.333H4L6 8.66699L7.5 10.667L9.5 8L12 11.333ZM5.66699 4.66699C5.94455 4.66707 6.18066 4.76375 6.375 4.95801C6.56944 5.15245 6.66699 5.38921 6.66699 5.66699C6.66692 5.94463 6.56937 6.18063 6.375 6.375C6.18063 6.56937 5.94463 6.66692 5.66699 6.66699C5.38921 6.66699 5.15245 6.56944 4.95801 6.375C4.76375 6.18066 4.66707 5.94455 4.66699 5.66699C4.66699 5.38921 4.76356 5.15245 4.95801 4.95801C5.15245 4.76356 5.38921 4.66699 5.66699 4.66699Z", fill: "currentColor" }) }));
20
+ }
21
+ function stripInlineMarkdown(markdown) {
22
+ const links = [];
23
+ const formatting = [];
24
+ let plainText = "";
25
+ let i = 0;
26
+ while (i < markdown.length) {
27
+ // Skip image syntax ![alt](url) — keep as-is
28
+ if (markdown[i] === "!" && markdown[i + 1] === "[") {
29
+ const endBracket = markdown.indexOf("]", i + 2);
30
+ if (endBracket !== -1 && markdown[endBracket + 1] === "(") {
31
+ const endParen = markdown.indexOf(")", endBracket + 2);
32
+ if (endParen !== -1) {
33
+ plainText += markdown.slice(i, endParen + 1);
34
+ i = endParen + 1;
35
+ continue;
36
+ }
37
+ }
38
+ }
39
+ // Links: [text](url)
40
+ if (markdown[i] === "[") {
41
+ const endBracket = markdown.indexOf("]", i + 1);
42
+ if (endBracket !== -1 && markdown[endBracket + 1] === "(") {
43
+ const endParen = markdown.indexOf(")", endBracket + 2);
44
+ if (endParen !== -1) {
45
+ const text = markdown.slice(i + 1, endBracket);
46
+ const url = markdown.slice(endBracket + 2, endParen);
47
+ links.push({ start: plainText.length, end: plainText.length + text.length, url });
48
+ plainText += text;
49
+ i = endParen + 1;
50
+ continue;
51
+ }
52
+ }
53
+ }
54
+ // Bold+Italic: *** or ___
55
+ if ((markdown[i] === "*" && markdown[i + 1] === "*" && markdown[i + 2] === "*") ||
56
+ (markdown[i] === "_" && markdown[i + 1] === "_" && markdown[i + 2] === "_")) {
57
+ const marker = markdown.slice(i, i + 3);
58
+ const closeIdx = markdown.indexOf(marker, i + 3);
59
+ if (closeIdx !== -1) {
60
+ const inner = markdown.slice(i + 3, closeIdx);
61
+ const start = plainText.length;
62
+ const innerResult = stripInlineMarkdown(inner);
63
+ plainText += innerResult.plainText;
64
+ for (const link of innerResult.links) {
65
+ links.push({ start: start + link.start, end: start + link.end, url: link.url });
66
+ }
67
+ for (const fmt of innerResult.formatting) {
68
+ formatting.push({ start: start + fmt.start, end: start + fmt.end, type: fmt.type });
69
+ }
70
+ formatting.push({ start, end: plainText.length, type: "bold" });
71
+ formatting.push({ start, end: plainText.length, type: "italic" });
72
+ i = closeIdx + 3;
73
+ continue;
74
+ }
75
+ }
76
+ // Bold: ** or __
77
+ if (markdown[i] === "*" && markdown[i + 1] === "*" && markdown[i + 2] !== "*" ||
78
+ markdown[i] === "_" && markdown[i + 1] === "_" && markdown[i + 2] !== "_") {
79
+ const marker = markdown.slice(i, i + 2);
80
+ // Find closing ** that isn't part of ***
81
+ let closeIdx = markdown.indexOf(marker, i + 2);
82
+ while (closeIdx !== -1 && markdown[closeIdx + 2] === marker[0]) {
83
+ closeIdx = markdown.indexOf(marker, closeIdx + 2);
84
+ }
85
+ if (closeIdx !== -1) {
86
+ const inner = markdown.slice(i + 2, closeIdx);
87
+ const start = plainText.length;
88
+ const innerResult = stripInlineMarkdown(inner);
89
+ plainText += innerResult.plainText;
90
+ for (const link of innerResult.links) {
91
+ links.push({ start: start + link.start, end: start + link.end, url: link.url });
92
+ }
93
+ for (const fmt of innerResult.formatting) {
94
+ formatting.push({ start: start + fmt.start, end: start + fmt.end, type: fmt.type });
95
+ }
96
+ formatting.push({ start, end: plainText.length, type: "bold" });
97
+ i = closeIdx + 2;
98
+ continue;
99
+ }
100
+ }
101
+ // Italic: single * or _
102
+ if ((markdown[i] === "*" && markdown[i + 1] !== "*") ||
103
+ (markdown[i] === "_" && markdown[i + 1] !== "_")) {
104
+ const marker = markdown[i];
105
+ // Find closing marker that isn't doubled
106
+ let closeIdx = i + 1;
107
+ while (closeIdx < markdown.length) {
108
+ closeIdx = markdown.indexOf(marker, closeIdx);
109
+ if (closeIdx === -1)
110
+ break;
111
+ if (markdown[closeIdx + 1] !== marker && markdown[closeIdx - 1] !== marker)
112
+ break;
113
+ closeIdx++;
114
+ }
115
+ if (closeIdx !== -1 && closeIdx > i + 1) {
116
+ const inner = markdown.slice(i + 1, closeIdx);
117
+ const start = plainText.length;
118
+ const innerResult = stripInlineMarkdown(inner);
119
+ plainText += innerResult.plainText;
120
+ for (const link of innerResult.links) {
121
+ links.push({ start: start + link.start, end: start + link.end, url: link.url });
122
+ }
123
+ for (const fmt of innerResult.formatting) {
124
+ formatting.push({ start: start + fmt.start, end: start + fmt.end, type: fmt.type });
125
+ }
126
+ formatting.push({ start, end: plainText.length, type: "italic" });
127
+ i = closeIdx + 1;
128
+ continue;
129
+ }
130
+ }
131
+ plainText += markdown[i];
132
+ i++;
133
+ }
134
+ return { plainText, links, formatting };
135
+ }
136
+ function buildFullMarkdown(plainText, links, formatting) {
137
+ if (links.length === 0 && formatting.length === 0)
138
+ return plainText;
139
+ const markers = [];
140
+ for (const fmt of formatting) {
141
+ const marker = fmt.type === "bold" ? "**" : "*";
142
+ // Opening: outer markers (bold) before inner (italic) → bold order=0, italic order=1
143
+ // Closing: inner markers (italic) before outer (bold) → italic order=0, bold order=1
144
+ const openOrder = fmt.type === "bold" ? 0 : 1;
145
+ const closeOrder = fmt.type === "bold" ? 1 : 0;
146
+ markers.push({ pos: fmt.start, text: marker, order: openOrder });
147
+ markers.push({ pos: fmt.end, text: marker, order: closeOrder });
148
+ }
149
+ for (const link of links) {
150
+ // Link brackets go outside formatting markers
151
+ markers.push({ pos: link.start, text: "[", order: -1 });
152
+ markers.push({ pos: link.end, text: `](${link.url})`, order: 2 });
153
+ }
154
+ // Sort by position descending so we insert from end to start (preserving earlier positions).
155
+ // At the same position, sort by order ascending.
156
+ markers.sort((a, b) => b.pos - a.pos || a.order - b.order);
157
+ let result = plainText;
158
+ for (const m of markers) {
159
+ result = result.slice(0, m.pos) + m.text + result.slice(m.pos);
160
+ }
161
+ return result;
162
+ }
163
+ function adjustFormattingForEdit(formatting, editPos, delta) {
164
+ return formatting
165
+ .map((fmt) => {
166
+ if (editPos <= fmt.start) {
167
+ return { ...fmt, start: fmt.start + delta, end: fmt.end + delta };
168
+ }
169
+ if (editPos >= fmt.end) {
170
+ return fmt;
171
+ }
172
+ return { ...fmt, end: fmt.end + delta };
173
+ })
174
+ .filter((fmt) => fmt.end > fmt.start);
175
+ }
176
+ function getCaretRectInPreview(preview, offset) {
177
+ const walker = document.createTreeWalker(preview, NodeFilter.SHOW_TEXT);
178
+ let currentOffset = 0;
179
+ while (walker.nextNode()) {
180
+ const textNode = walker.currentNode;
181
+ const nodeLen = textNode.length;
182
+ if (offset <= currentOffset + nodeLen) {
183
+ const localOffset = offset - currentOffset;
184
+ try {
185
+ const range = document.createRange();
186
+ range.setStart(textNode, localOffset);
187
+ range.collapse(true);
188
+ const rect = range.getBoundingClientRect();
189
+ const previewRect = preview.getBoundingClientRect();
190
+ return {
191
+ top: rect.top - previewRect.top + preview.scrollTop,
192
+ left: rect.left - previewRect.left + preview.scrollLeft,
193
+ height: rect.height || parseFloat(getComputedStyle(preview).lineHeight) || 20,
194
+ };
195
+ }
196
+ catch {
197
+ return null;
198
+ }
199
+ }
200
+ currentOffset += nodeLen;
201
+ }
202
+ return null;
203
+ }
204
+ function applyFormattingHighlights(preview, formatting) {
205
+ if (formatting.length === 0)
206
+ return;
207
+ // Remove previous formatting highlights
208
+ const existingBold = preview.querySelectorAll("strong.step-preview-bold");
209
+ for (let i = 0; i < existingBold.length; i++) {
210
+ const el = existingBold[i];
211
+ const parent = el.parentNode;
212
+ if (parent) {
213
+ while (el.firstChild) {
214
+ parent.insertBefore(el.firstChild, el);
215
+ }
216
+ parent.removeChild(el);
217
+ }
218
+ }
219
+ const existingItalic = preview.querySelectorAll("em.step-preview-italic");
220
+ for (let i = 0; i < existingItalic.length; i++) {
221
+ const el = existingItalic[i];
222
+ const parent = el.parentNode;
223
+ if (parent) {
224
+ while (el.firstChild) {
225
+ parent.insertBefore(el.firstChild, el);
226
+ }
227
+ parent.removeChild(el);
228
+ }
229
+ }
230
+ const sorted = [...formatting].sort((a, b) => b.start - a.start);
231
+ for (const fmt of sorted) {
232
+ const walker = document.createTreeWalker(preview, NodeFilter.SHOW_TEXT);
233
+ let currentOffset = 0;
234
+ let startNode = null;
235
+ let startLocalOffset = 0;
236
+ let endNode = null;
237
+ let endLocalOffset = 0;
238
+ while (walker.nextNode()) {
239
+ const textNode = walker.currentNode;
240
+ const nodeStart = currentOffset;
241
+ const nodeEnd = currentOffset + textNode.length;
242
+ if (!startNode && fmt.start >= nodeStart && fmt.start < nodeEnd) {
243
+ startNode = textNode;
244
+ startLocalOffset = fmt.start - nodeStart;
245
+ }
246
+ if (!endNode && fmt.end > nodeStart && fmt.end <= nodeEnd) {
247
+ endNode = textNode;
248
+ endLocalOffset = fmt.end - nodeStart;
249
+ }
250
+ currentOffset = nodeEnd;
251
+ if (startNode && endNode)
252
+ break;
253
+ }
254
+ if (!startNode || !endNode)
255
+ continue;
256
+ try {
257
+ const range = document.createRange();
258
+ range.setStart(startNode, startLocalOffset);
259
+ range.setEnd(endNode, endLocalOffset);
260
+ const wrapper = document.createElement(fmt.type === "bold" ? "strong" : "em");
261
+ wrapper.className = fmt.type === "bold" ? "step-preview-bold" : "step-preview-italic";
262
+ const fragment = range.extractContents();
263
+ wrapper.appendChild(fragment);
264
+ range.insertNode(wrapper);
265
+ }
266
+ catch {
267
+ // DOM manipulation can fail if range crosses element boundaries
268
+ }
269
+ }
270
+ }
271
+ function adjustLinksForEdit(links, editPos, delta) {
272
+ return links
273
+ .map((link) => {
274
+ if (editPos <= link.start) {
275
+ return { ...link, start: link.start + delta, end: link.end + delta };
276
+ }
277
+ if (editPos >= link.end) {
278
+ return link;
279
+ }
280
+ return { ...link, end: link.end + delta };
281
+ })
282
+ .filter((link) => link.end > link.start);
283
+ }
284
+ function applyLinkHighlights(preview, links) {
285
+ if (links.length === 0)
286
+ return;
287
+ // Remove previous link highlights
288
+ const existing = preview.querySelectorAll("a.step-preview-link");
289
+ for (let i = 0; i < existing.length; i++) {
290
+ const el = existing[i];
291
+ const parent = el.parentNode;
292
+ if (parent) {
293
+ while (el.firstChild) {
294
+ parent.insertBefore(el.firstChild, el);
295
+ }
296
+ parent.removeChild(el);
297
+ }
298
+ }
299
+ const sorted = [...links].sort((a, b) => b.start - a.start);
300
+ for (const link of sorted) {
301
+ const walker = document.createTreeWalker(preview, NodeFilter.SHOW_TEXT);
302
+ let currentOffset = 0;
303
+ let startNode = null;
304
+ let startLocalOffset = 0;
305
+ let endNode = null;
306
+ let endLocalOffset = 0;
307
+ while (walker.nextNode()) {
308
+ const textNode = walker.currentNode;
309
+ const nodeStart = currentOffset;
310
+ const nodeEnd = currentOffset + textNode.length;
311
+ if (!startNode && link.start >= nodeStart && link.start < nodeEnd) {
312
+ startNode = textNode;
313
+ startLocalOffset = link.start - nodeStart;
314
+ }
315
+ if (!endNode && link.end > nodeStart && link.end <= nodeEnd) {
316
+ endNode = textNode;
317
+ endLocalOffset = link.end - nodeStart;
318
+ }
319
+ currentOffset = nodeEnd;
320
+ if (startNode && endNode)
321
+ break;
322
+ }
323
+ if (!startNode || !endNode)
324
+ continue;
325
+ try {
326
+ const range = document.createRange();
327
+ range.setStart(startNode, startLocalOffset);
328
+ range.setEnd(endNode, endLocalOffset);
329
+ const anchor = document.createElement("a");
330
+ anchor.href = link.url;
331
+ anchor.className = "step-preview-link";
332
+ const fragment = range.extractContents();
333
+ anchor.appendChild(fragment);
334
+ range.insertNode(anchor);
335
+ }
336
+ catch {
337
+ // DOM manipulation can fail if range crosses element boundaries unexpectedly
338
+ }
339
+ }
340
+ }
15
341
  function markdownToPlainText(markdown) {
16
342
  var _a;
17
343
  if (!markdown) {
@@ -30,7 +356,8 @@ function markdownToPlainText(markdown) {
30
356
  return markdown.replace(/!\[[^\]]*]\([^)]+\)/g, "").replace(/\[[^\]]*]\([^)]+\)/g, "").replace(/[*_`~]/g, "").replace(/\s+/g, " ").trim();
31
357
  }
32
358
  }
33
- export function StepField({ label, value, onChange, autoFocus, multiline = false, enableAutocomplete = false, fieldName, suggestionFilter, suggestionsOverride, onSuggestionSelect, readOnly = false, showSuggestionsOnFocus = false, enableImageUpload = false, onImageFile, rightAction, showFormattingButtons = false, showImageButton = false, onFieldFocus, }) {
359
+ export function StepField({ label, showLabel = true, labelToggle, labelAction, placeholder, value, onChange, autoFocus, focusSignal, multiline = false, enableAutocomplete = false, fieldName, suggestionFilter, suggestionsOverride, onSuggestionSelect, readOnly = false, showSuggestionsOnFocus = false, enableImageUpload = false, onImageFile, rightAction, showFormattingButtons = false, showImageButton = false, onFieldFocus, }) {
360
+ var _a, _b;
34
361
  const stepSuggestions = useStepAutocomplete();
35
362
  const suggestions = suggestionsOverride !== null && suggestionsOverride !== void 0 ? suggestionsOverride : stepSuggestions;
36
363
  const uploadImage = useStepImageUpload();
@@ -48,44 +375,160 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
48
375
  const [showAllSuggestions, setShowAllSuggestions] = useState(false);
49
376
  const [isUploading, setIsUploading] = useState(false);
50
377
  const [previewImageUrl, setPreviewImageUrl] = useState(null);
378
+ const [showLinkPopover, setShowLinkPopover] = useState(false);
379
+ const linkSelectionRef = useRef(null);
380
+ const linksRef = useRef([]);
381
+ const formattingRef = useRef([]);
382
+ const caretRef = useRef(null);
383
+ const prevTextRef = useRef("");
384
+ const isSyncingRef = useRef(false);
385
+ const [cursorLink, setCursorLink] = useState(null);
386
+ const Components = useComponentsContext();
387
+ const resolvedPlaceholder = placeholder !== null && placeholder !== void 0 ? placeholder : "";
51
388
  useEffect(() => {
52
389
  onChangeRef.current = onChange;
53
390
  }, [onChange]);
54
391
  const handleEditorChange = useCallback((nextValue) => {
55
392
  var _a;
393
+ if (isSyncingRef.current)
394
+ return;
395
+ const prevText = prevTextRef.current;
396
+ const delta = nextValue.length - prevText.length;
397
+ // Find where the edit happened by comparing old and new text
398
+ let editPos = 0;
399
+ const minLen = Math.min(prevText.length, nextValue.length);
400
+ while (editPos < minLen && prevText[editPos] === nextValue[editPos]) {
401
+ editPos++;
402
+ }
403
+ linksRef.current = adjustLinksForEdit(linksRef.current, editPos, delta);
404
+ formattingRef.current = adjustFormattingForEdit(formattingRef.current, editPos, delta);
405
+ prevTextRef.current = nextValue;
406
+ const markdown = buildFullMarkdown(nextValue, linksRef.current, formattingRef.current);
56
407
  setPlainTextValue((prev) => {
57
- const normalized = markdownToPlainText(nextValue);
408
+ const normalized = markdownToPlainText(markdown);
58
409
  return prev === normalized ? prev : normalized;
59
410
  });
60
- (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, nextValue);
411
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, markdown);
61
412
  }, []);
62
413
  useEffect(() => {
63
414
  const container = editorContainerRef.current;
64
415
  if (!container) {
65
416
  return;
66
417
  }
418
+ const { plainText, links, formatting } = stripInlineMarkdown(initialValueRef.current);
419
+ linksRef.current = links;
420
+ formattingRef.current = formatting;
421
+ prevTextRef.current = plainText;
67
422
  const [instance] = OverType.init(container, {
68
- value: initialValueRef.current,
423
+ value: plainText,
424
+ placeholder: resolvedPlaceholder,
69
425
  autoResize: multiline,
70
426
  minHeight: multiline ? "4rem" : "2.5rem",
71
427
  padding: "0.5rem 0.75rem",
72
428
  fontSize: "0.95rem",
73
429
  onChange: handleEditorChange,
74
430
  });
431
+ // Monkey-patch updatePreview to add link highlights
432
+ const originalUpdatePreview = instance.updatePreview.bind(instance);
433
+ instance.updatePreview = function () {
434
+ originalUpdatePreview();
435
+ applyFormattingHighlights(this.preview, formattingRef.current);
436
+ applyLinkHighlights(this.preview, linksRef.current);
437
+ };
438
+ // Apply initial highlights
439
+ applyFormattingHighlights(instance.preview, formattingRef.current);
440
+ applyLinkHighlights(instance.preview, linksRef.current);
441
+ // Create custom caret element inside the wrapper
442
+ const caretEl = document.createElement("div");
443
+ caretEl.className = "bn-step-custom-caret";
444
+ instance.wrapper.appendChild(caretEl);
445
+ caretRef.current = caretEl;
75
446
  editorInstanceRef.current = instance;
76
447
  setTextareaNode(instance.textarea);
77
448
  return () => {
449
+ caretRef.current = null;
78
450
  instance.destroy();
79
451
  editorInstanceRef.current = null;
80
452
  setTextareaNode(null);
81
453
  };
82
- }, [handleEditorChange, multiline]);
454
+ }, [handleEditorChange, multiline, resolvedPlaceholder]);
455
+ // Custom caret: position based on preview text metrics (handles bold/italic width differences)
456
+ useEffect(() => {
457
+ const instance = editorInstanceRef.current;
458
+ const caret = caretRef.current;
459
+ if (!textareaNode || !instance || !caret)
460
+ return;
461
+ const updateCaret = () => {
462
+ var _a, _b;
463
+ const hasFormatting = formattingRef.current.length > 0;
464
+ if (!hasFormatting) {
465
+ caret.style.display = "none";
466
+ textareaNode.classList.remove("bn-step-caret-hidden");
467
+ return;
468
+ }
469
+ // Always hide native caret when formatting exists
470
+ textareaNode.classList.add("bn-step-caret-hidden");
471
+ const isFocused = document.activeElement === textareaNode;
472
+ if (!isFocused) {
473
+ caret.style.display = "none";
474
+ return;
475
+ }
476
+ const pos = (_a = textareaNode.selectionStart) !== null && _a !== void 0 ? _a : 0;
477
+ const selEnd = (_b = textareaNode.selectionEnd) !== null && _b !== void 0 ? _b : 0;
478
+ // Hide custom caret when there's a selection range
479
+ if (pos !== selEnd) {
480
+ caret.style.display = "none";
481
+ return;
482
+ }
483
+ const rect = getCaretRectInPreview(instance.preview, pos);
484
+ if (rect) {
485
+ caret.style.display = "block";
486
+ caret.style.top = `${rect.top}px`;
487
+ caret.style.left = `${rect.left}px`;
488
+ caret.style.height = `${rect.height}px`;
489
+ }
490
+ else {
491
+ caret.style.display = "none";
492
+ }
493
+ };
494
+ const onSelectionChange = () => {
495
+ if (document.activeElement === textareaNode) {
496
+ updateCaret();
497
+ }
498
+ };
499
+ const onBlur = () => {
500
+ caret.style.display = "none";
501
+ };
502
+ const onFocus = () => {
503
+ updateCaret();
504
+ };
505
+ const deferredUpdate = () => requestAnimationFrame(updateCaret);
506
+ document.addEventListener("selectionchange", onSelectionChange);
507
+ textareaNode.addEventListener("input", deferredUpdate);
508
+ textareaNode.addEventListener("focus", onFocus);
509
+ textareaNode.addEventListener("blur", onBlur);
510
+ // Initial update
511
+ updateCaret();
512
+ return () => {
513
+ document.removeEventListener("selectionchange", onSelectionChange);
514
+ textareaNode.removeEventListener("input", deferredUpdate);
515
+ textareaNode.removeEventListener("focus", onFocus);
516
+ textareaNode.removeEventListener("blur", onBlur);
517
+ textareaNode.classList.remove("bn-step-caret-hidden");
518
+ };
519
+ }, [textareaNode]);
83
520
  useEffect(() => {
84
521
  if (pendingFocusRef.current && textareaNode) {
85
522
  pendingFocusRef.current = false;
86
523
  textareaNode.focus();
87
524
  }
88
525
  }, [textareaNode]);
526
+ useEffect(() => {
527
+ if (!textareaNode || !focusSignal) {
528
+ return;
529
+ }
530
+ textareaNode.focus();
531
+ }, [focusSignal, textareaNode]);
89
532
  useEffect(() => {
90
533
  const instance = editorInstanceRef.current;
91
534
  if (!instance) {
@@ -95,8 +538,19 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
95
538
  });
96
539
  return;
97
540
  }
98
- if (instance.getValue() !== value) {
99
- instance.setValue(value);
541
+ const { plainText, links, formatting } = stripInlineMarkdown(value);
542
+ linksRef.current = links;
543
+ formattingRef.current = formatting;
544
+ prevTextRef.current = plainText;
545
+ if (instance.getValue() !== plainText) {
546
+ isSyncingRef.current = true;
547
+ instance.setValue(plainText);
548
+ isSyncingRef.current = false;
549
+ }
550
+ else {
551
+ // Even if text didn't change, formatting/links might have — re-apply highlights
552
+ applyFormattingHighlights(instance.preview, formatting);
553
+ applyLinkHighlights(instance.preview, links);
100
554
  }
101
555
  setPlainTextValue((prev) => {
102
556
  const normalized = markdownToPlainText(value);
@@ -120,6 +574,12 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
120
574
  }
121
575
  textareaNode.readOnly = readOnly;
122
576
  }, [readOnly, textareaNode]);
577
+ useAutoResize({
578
+ textarea: textareaNode,
579
+ multiline,
580
+ minRows: 3,
581
+ maxRows: 16,
582
+ });
123
583
  useEffect(() => {
124
584
  if (!textareaNode) {
125
585
  return;
@@ -142,6 +602,28 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
142
602
  textareaNode.removeEventListener("blur", handleBlur);
143
603
  };
144
604
  }, [enableAutocomplete, onFieldFocus, showSuggestionsOnFocus, textareaNode]);
605
+ // Detect when cursor is inside a link for showing edit tooltip
606
+ useEffect(() => {
607
+ if (!textareaNode)
608
+ return;
609
+ const checkCursorInLink = () => {
610
+ const pos = textareaNode.selectionStart;
611
+ const isCollapsed = textareaNode.selectionStart === textareaNode.selectionEnd;
612
+ if (!isCollapsed) {
613
+ setCursorLink(null);
614
+ return;
615
+ }
616
+ const found = linksRef.current.find((l) => pos >= l.start && pos <= l.end);
617
+ setCursorLink(found !== null && found !== void 0 ? found : null);
618
+ };
619
+ textareaNode.addEventListener("click", checkCursorInLink);
620
+ textareaNode.addEventListener("keyup", checkCursorInLink);
621
+ textareaNode.addEventListener("blur", () => setCursorLink(null));
622
+ return () => {
623
+ textareaNode.removeEventListener("click", checkCursorInLink);
624
+ textareaNode.removeEventListener("keyup", checkCursorInLink);
625
+ };
626
+ }, [textareaNode]);
145
627
  useEffect(() => {
146
628
  if (!autoFocus || autoFocusRef.current || !textareaNode) {
147
629
  return;
@@ -161,7 +643,7 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
161
643
  return () => clearTimeout(timeout);
162
644
  }, [autoFocus, enableAutocomplete, showSuggestionsOnFocus, textareaNode]);
163
645
  const insertImageMarkdown = useCallback((url) => {
164
- var _a, _b, _c;
646
+ var _a, _b;
165
647
  const instance = editorInstanceRef.current;
166
648
  const textarea = textareaNode;
167
649
  if (!instance || !textarea) {
@@ -176,9 +658,8 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
176
658
  const needsAfterNewline = after.length > 0 && !after.startsWith("\n");
177
659
  const insertText = `${needsBeforeNewline ? "\n" : ""}![](${url})${needsAfterNewline ? "\n" : ""}`;
178
660
  const nextValue = `${before}${insertText}${after}`;
661
+ // setValue triggers updatePreview → handleEditorChange which reconstructs markdown with links
179
662
  instance.setValue(nextValue);
180
- setPlainTextValue(markdownToPlainText(nextValue));
181
- (_c = onChangeRef.current) === null || _c === void 0 ? void 0 : _c.call(onChangeRef, nextValue);
182
663
  requestAnimationFrame(() => {
183
664
  textarea.selectionStart = start + insertText.length;
184
665
  textarea.selectionEnd = start + insertText.length;
@@ -226,14 +707,110 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
226
707
  };
227
708
  }, [enableImageUpload, insertImageMarkdown, onImageFile, textareaNode, uploadImage]);
228
709
  const handleToolbarAction = useCallback((action) => {
229
- var _a;
230
- const shortcuts = (_a = editorInstanceRef.current) === null || _a === void 0 ? void 0 : _a.shortcuts;
231
- if (!textareaNode || !(shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.handleAction)) {
710
+ var _a, _b, _c;
711
+ const instance = editorInstanceRef.current;
712
+ if (!textareaNode || !instance) {
232
713
  return;
233
714
  }
234
715
  textareaNode.focus();
235
- shortcuts.handleAction(action);
716
+ const fmtType = action === "toggleBold" ? "bold" : "italic";
717
+ const start = (_a = textareaNode.selectionStart) !== null && _a !== void 0 ? _a : 0;
718
+ const end = (_b = textareaNode.selectionEnd) !== null && _b !== void 0 ? _b : 0;
719
+ // Check if selection is already formatted
720
+ const existingIdx = formattingRef.current.findIndex((f) => f.type === fmtType && f.start <= start && f.end >= end);
721
+ if (existingIdx !== -1) {
722
+ // Remove formatting
723
+ formattingRef.current = formattingRef.current.filter((_, i) => i !== existingIdx);
724
+ }
725
+ else if (start !== end) {
726
+ // Add formatting for selection
727
+ formattingRef.current = [...formattingRef.current, { start, end, type: fmtType }];
728
+ }
729
+ else {
730
+ // No selection — nothing to format
731
+ return;
732
+ }
733
+ const currentValue = instance.getValue();
734
+ prevTextRef.current = currentValue;
735
+ const markdown = buildFullMarkdown(currentValue, linksRef.current, formattingRef.current);
736
+ (_c = onChangeRef.current) === null || _c === void 0 ? void 0 : _c.call(onChangeRef, markdown);
737
+ setPlainTextValue(markdownToPlainText(markdown));
738
+ // Re-apply highlights
739
+ applyFormattingHighlights(instance.preview, formattingRef.current);
740
+ applyLinkHighlights(instance.preview, linksRef.current);
741
+ }, [textareaNode]);
742
+ const linkPopoverRef = useRef(null);
743
+ // Close link popover on outside click
744
+ useEffect(() => {
745
+ if (!showLinkPopover)
746
+ return;
747
+ const handleMouseDown = (event) => {
748
+ const popover = linkPopoverRef.current;
749
+ if (popover && !popover.contains(event.target)) {
750
+ setShowLinkPopover(false);
751
+ linkSelectionRef.current = null;
752
+ }
753
+ };
754
+ document.addEventListener("mousedown", handleMouseDown);
755
+ return () => document.removeEventListener("mousedown", handleMouseDown);
756
+ }, [showLinkPopover]);
757
+ const handleOpenLinkPopover = useCallback(() => {
758
+ var _a, _b;
759
+ if (!textareaNode) {
760
+ return;
761
+ }
762
+ const start = (_a = textareaNode.selectionStart) !== null && _a !== void 0 ? _a : 0;
763
+ const end = (_b = textareaNode.selectionEnd) !== null && _b !== void 0 ? _b : 0;
764
+ const text = textareaNode.value.slice(start, end);
765
+ linkSelectionRef.current = { start, end, text };
766
+ setShowLinkPopover(true);
236
767
  }, [textareaNode]);
768
+ const handleEditLink = useCallback((url, text) => {
769
+ var _a;
770
+ const instance = editorInstanceRef.current;
771
+ const sel = linkSelectionRef.current;
772
+ if (!instance || !sel || !url) {
773
+ setShowLinkPopover(false);
774
+ linkSelectionRef.current = null;
775
+ return;
776
+ }
777
+ const currentValue = instance.getValue();
778
+ const linkText = text || sel.text || url;
779
+ // Replace selected text with link display text (no markdown syntax in textarea)
780
+ const before = currentValue.slice(0, sel.start);
781
+ const after = currentValue.slice(sel.end);
782
+ const nextValue = `${before}${linkText}${after}`;
783
+ // Remove any existing link that overlaps this selection, then add the new one
784
+ const delta = linkText.length - (sel.end - sel.start);
785
+ const adjustedLinks = adjustLinksForEdit(linksRef.current.filter((l) => !(l.start < sel.end && l.end > sel.start)), sel.start, delta);
786
+ const newLink = { start: sel.start, end: sel.start + linkText.length, url };
787
+ linksRef.current = [...adjustedLinks, newLink];
788
+ formattingRef.current = adjustFormattingForEdit(formattingRef.current, sel.start, delta);
789
+ prevTextRef.current = nextValue;
790
+ isSyncingRef.current = true;
791
+ instance.setValue(nextValue);
792
+ isSyncingRef.current = false;
793
+ const markdown = buildFullMarkdown(nextValue, linksRef.current, formattingRef.current);
794
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, markdown);
795
+ setPlainTextValue(markdownToPlainText(markdown));
796
+ setShowLinkPopover(false);
797
+ linkSelectionRef.current = null;
798
+ setCursorLink(null);
799
+ requestAnimationFrame(() => textareaNode === null || textareaNode === void 0 ? void 0 : textareaNode.focus());
800
+ }, [textareaNode]);
801
+ const handleRemoveLink = useCallback(() => {
802
+ var _a;
803
+ linksRef.current = linksRef.current.filter((l) => l !== cursorLink);
804
+ setCursorLink(null);
805
+ const instance = editorInstanceRef.current;
806
+ if (instance) {
807
+ const markdown = buildFullMarkdown(instance.getValue(), linksRef.current, formattingRef.current);
808
+ (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, markdown);
809
+ // Re-apply highlights since links changed
810
+ applyFormattingHighlights(instance.preview, formattingRef.current);
811
+ applyLinkHighlights(instance.preview, linksRef.current);
812
+ }
813
+ }, [cursorLink]);
237
814
  const suggestionPool = useMemo(() => {
238
815
  if (!suggestionFilter) {
239
816
  return suggestions;
@@ -343,7 +920,7 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
343
920
  const keydownHandlerRef = useRef(null);
344
921
  useEffect(() => {
345
922
  keydownHandlerRef.current = (event) => {
346
- var _a;
923
+ var _a, _b;
347
924
  if (readOnly) {
348
925
  const openKeys = enableAutocomplete && (event.metaKey || event.ctrlKey) && AUTOCOMPLETE_TRIGGER_KEYS.has(event.code);
349
926
  if (!READ_ONLY_ALLOWED_KEYS.has(event.key) && !openKeys) {
@@ -351,6 +928,22 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
351
928
  return;
352
929
  }
353
930
  }
931
+ // Intercept Ctrl+B / Ctrl+I to use our formatting system instead of OverType's
932
+ const modKey = ((_a = navigator.platform) === null || _a === void 0 ? void 0 : _a.toLowerCase().includes("mac")) ? event.metaKey : event.ctrlKey;
933
+ if (modKey && !event.shiftKey) {
934
+ if (event.key === "b" || event.key === "B") {
935
+ event.preventDefault();
936
+ event.stopImmediatePropagation();
937
+ handleToolbarAction("toggleBold");
938
+ return;
939
+ }
940
+ if (event.key === "i" || event.key === "I") {
941
+ event.preventDefault();
942
+ event.stopImmediatePropagation();
943
+ handleToolbarAction("toggleItalic");
944
+ return;
945
+ }
946
+ }
354
947
  if (enableAutocomplete && shouldShowAutocomplete) {
355
948
  if (event.key === "ArrowDown") {
356
949
  event.preventDefault();
@@ -364,7 +957,7 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
364
957
  }
365
958
  if (event.key === "Enter" || event.key === "Tab") {
366
959
  event.preventDefault();
367
- const suggestion = (_a = filteredSuggestions[activeSuggestionIndex]) !== null && _a !== void 0 ? _a : filteredSuggestions[0];
960
+ const suggestion = (_b = filteredSuggestions[activeSuggestionIndex]) !== null && _b !== void 0 ? _b : filteredSuggestions[0];
368
961
  if (suggestion) {
369
962
  applySuggestion(suggestion);
370
963
  }
@@ -386,7 +979,7 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
386
979
  }
387
980
  }
388
981
  };
389
- }, [activeSuggestionIndex, applySuggestion, enableAutocomplete, filteredSuggestions, focusAdjacentField, readOnly, shouldShowAutocomplete]);
982
+ }, [activeSuggestionIndex, applySuggestion, enableAutocomplete, filteredSuggestions, focusAdjacentField, handleToolbarAction, readOnly, shouldShowAutocomplete]);
390
983
  useEffect(() => {
391
984
  if (!textareaNode) {
392
985
  return;
@@ -409,21 +1002,67 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
409
1002
  ]
410
1003
  .filter(Boolean)
411
1004
  .join(" ");
412
- return (_jsxs("div", { className: "bn-step-field", children: [_jsxs("div", { className: "bn-step-field__top", children: [_jsxs("span", { className: "bn-step-field__label", children: [label, enableAutocomplete && (_jsx("button", { type: "button", className: "bn-step-toolbar__button", onMouseDown: (event) => {
1005
+ const inputClassName = [
1006
+ "bn-step-field__input",
1007
+ multiline ? "bn-step-field__input--multiline" : "",
1008
+ isFocused ? "bn-step-field__input--focused" : "",
1009
+ readOnly ? "bn-step-field__input--readonly" : "",
1010
+ ]
1011
+ .filter(Boolean)
1012
+ .join(" ");
1013
+ const showToolbar = showFormattingButtons || (enableImageUpload && uploadImage && showImageButton) || Boolean(rightAction) || enableAutocomplete;
1014
+ return (_jsxs("div", { className: "bn-step-field", children: [showLabel && (_jsxs("div", { className: "bn-step-field__top", children: [_jsx("div", { className: "bn-step-field__label-row", children: labelToggle ? (_jsx("span", { className: "bn-step-field__label bn-step-field__label--toggle", role: "button", tabIndex: -1, onClick: labelToggle.onClick, onKeyDown: (event) => {
1015
+ if (event.key === "Enter" || event.key === " ") {
1016
+ event.preventDefault();
1017
+ labelToggle.onClick();
1018
+ }
1019
+ }, "aria-expanded": labelToggle.expanded, children: label })) : (_jsx("span", { className: "bn-step-field__label", children: label })) }), labelAction && _jsx("div", { className: "bn-step-field__label-action", children: labelAction })] })), _jsxs("div", { className: inputClassName, "aria-label": `${label} input`, children: [_jsx("div", { ref: editorContainerRef, className: editorClassName, "data-step-field": fieldName, tabIndex: -1, onFocus: (event) => {
1020
+ if (event.target === editorContainerRef.current) {
1021
+ if (textareaNode) {
1022
+ textareaNode.focus();
1023
+ }
1024
+ else {
1025
+ pendingFocusRef.current = true;
1026
+ }
1027
+ }
1028
+ } }), cursorLink && isFocused && (_jsxs("div", { className: "bn-step-link-tooltip", children: [_jsx("span", { className: "bn-step-link-tooltip__url", title: cursorLink.url, children: cursorLink.url.length > 40 ? `${cursorLink.url.slice(0, 40)}...` : cursorLink.url }), _jsx("button", { type: "button", className: "bn-step-link-tooltip__btn", onMouseDown: (event) => {
1029
+ event.preventDefault();
1030
+ linkSelectionRef.current = { start: cursorLink.start, end: cursorLink.end, text: "" };
1031
+ setShowLinkPopover(true);
1032
+ }, tabIndex: -1, children: "Edit link" }), _jsx("a", { className: "bn-step-link-tooltip__btn", href: cursorLink.url, target: "_blank", rel: "noopener noreferrer", onMouseDown: (event) => event.stopPropagation(), tabIndex: -1, children: _jsxs("svg", { width: "12", height: "12", viewBox: "0 0 20 20", fill: "currentColor", "aria-hidden": "true", children: [_jsx("path", { d: "M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z" }), _jsx("path", { d: "M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z" })] }) }), _jsx("button", { type: "button", className: "bn-step-link-tooltip__btn bn-step-link-tooltip__btn--danger", onMouseDown: (event) => {
413
1033
  event.preventDefault();
414
- setShowAllSuggestions(true);
415
- textareaNode === null || textareaNode === void 0 ? void 0 : textareaNode.focus();
416
- }, "aria-label": "Show suggestions", tabIndex: -1, children: "\u2304" }))] }), _jsxs("div", { className: "bn-step-toolbar", "aria-label": `${label} controls`, children: [showFormattingButtons && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "bn-step-toolbar__button", onMouseDown: (event) => {
1034
+ handleRemoveLink();
1035
+ }, tabIndex: -1, children: _jsx("svg", { width: "12", height: "12", viewBox: "0 0 16 16", fill: "currentColor", "aria-hidden": "true", children: _jsx("path", { d: "M7 3h2a1 1 0 0 0-2 0ZM6 3a2 2 0 1 1 4 0h4a.5.5 0 0 1 0 1h-.564l-1.205 8.838A2.5 2.5 0 0 1 9.754 15H6.246a2.5 2.5 0 0 1-2.477-2.162L2.564 4H2a.5.5 0 0 1 0-1h4Zm1 3.5a.5.5 0 0 0-1 0v5a.5.5 0 0 0 1 0v-5ZM9.5 6a.5.5 0 0 0-.5.5v5a.5.5 0 0 0 1 0v-5a.5.5 0 0 0-.5-.5Z" }) }) })] })), showToolbar && (_jsxs("div", { className: "bn-step-toolbar", "aria-label": `${label} controls`, children: [showFormattingButtons && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "bn-step-toolbar__button", onMouseDown: (event) => {
417
1036
  event.preventDefault();
418
1037
  handleToolbarAction("toggleBold");
419
- }, "aria-label": "Bold", tabIndex: -1, children: "B" }), _jsx("button", { type: "button", className: "bn-step-toolbar__button", onMouseDown: (event) => {
1038
+ }, "aria-label": "Bold", tabIndex: -1, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M4 2.66675H8.33333C8.92064 2.66677 9.49502 2.83918 9.98525 3.1626C10.4755 3.48602 10.86 3.94622 11.0911 4.48613C11.3223 5.02604 11.3898 5.62192 11.2855 6.19988C11.1811 6.77783 10.9094 7.31244 10.504 7.73741C11.0752 8.06825 11.5213 8.57823 11.7733 9.18833C12.0252 9.79844 12.0689 10.4746 11.8976 11.1121C11.7263 11.7495 11.3495 12.3127 10.8256 12.7143C10.3018 13.1159 9.66008 13.3335 9 13.3334H4V12.0001H4.66667V4.00008H4V2.66675ZM6 7.33341H8.33333C8.77536 7.33341 9.19928 7.15782 9.51184 6.84526C9.8244 6.5327 10 6.10878 10 5.66675C10 5.22472 9.8244 4.8008 9.51184 4.48824C9.19928 4.17568 8.77536 4.00008 8.33333 4.00008H6V7.33341ZM6 8.66675V12.0001H9C9.44203 12.0001 9.86595 11.8245 10.1785 11.5119C10.4911 11.1994 10.6667 10.7754 10.6667 10.3334C10.6667 9.89139 10.4911 9.46746 10.1785 9.1549C9.86595 8.84234 9.44203 8.66675 9 8.66675H6Z", fill: "currentColor" }) }) }), _jsx("button", { type: "button", className: "bn-step-toolbar__button", onMouseDown: (event) => {
420
1039
  event.preventDefault();
421
1040
  handleToolbarAction("toggleItalic");
422
- }, "aria-label": "Italic", tabIndex: -1, children: "I" })] })), enableImageUpload && uploadImage && showImageButton && (_jsx("button", { type: "button", className: "bn-step-toolbar__button", onMouseDown: (event) => {
1041
+ }, "aria-label": "Italic", tabIndex: -1, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M8.66699 13.3334H4.66699V12.0001H5.95166L8.69566 4.00008H7.33366V2.66675H11.3337V4.00008H10.049L7.30499 12.0001H8.66699V13.3334Z", fill: "currentColor" }) }) })] })), enableImageUpload && uploadImage && showImageButton && (_jsx("button", { type: "button", className: "bn-step-toolbar__button", onMouseDown: (event) => {
423
1042
  var _a;
424
1043
  event.preventDefault();
425
1044
  (_a = fileInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
426
- }, "aria-label": "Insert image", tabIndex: -1, disabled: isUploading, children: "Img" })), rightAction] })] }), enableImageUpload && (_jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: "none" }, onChange: async (event) => {
1045
+ }, "aria-label": "Insert image", tabIndex: -1, disabled: isUploading, children: _jsx(ImageUploadIcon, {}) })), showFormattingButtons && Components && (_jsxs(Components.Generic.Popover.Root, { opened: showLinkPopover, position: "top", children: [_jsx(Components.Generic.Popover.Trigger, { children: _jsx("button", { type: "button", className: "bn-step-toolbar__button", onMouseDown: (event) => {
1046
+ event.preventDefault();
1047
+ if (showLinkPopover) {
1048
+ setShowLinkPopover(false);
1049
+ linkSelectionRef.current = null;
1050
+ }
1051
+ else {
1052
+ handleOpenLinkPopover();
1053
+ }
1054
+ }, "aria-label": "Insert link", tabIndex: -1, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M6.66699 4.66699C6.85574 4.66707 7.0139 4.73069 7.1416 4.8584C7.26931 4.9861 7.33293 5.14426 7.33301 5.33301C7.33301 5.5219 7.26938 5.68082 7.1416 5.80859C7.01393 5.93619 6.85566 5.99993 6.66699 6H4.66699C4.11151 6 3.63886 6.19423 3.25 6.58301C2.86111 6.9719 2.66699 7.44444 2.66699 8C2.66699 8.55556 2.86111 9.0281 3.25 9.41699C3.63886 9.80577 4.11151 10 4.66699 10H6.66699C6.85566 10.0001 7.01393 10.0638 7.1416 10.1914C7.26938 10.3192 7.33301 10.4781 7.33301 10.667C7.33293 10.8557 7.26931 11.0139 7.1416 11.1416C7.0139 11.2693 6.85574 11.3329 6.66699 11.333H4.66699C3.74485 11.333 2.95856 11.0083 2.30859 10.3584C1.65859 9.7084 1.33301 8.92222 1.33301 8C1.33301 7.07778 1.65859 6.2916 2.30859 5.6416C2.95856 4.99171 3.74485 4.66699 4.66699 4.66699H6.66699ZM11.333 4.66699C12.2552 4.66699 13.0414 4.99171 13.6914 5.6416C14.3414 6.2916 14.667 7.07778 14.667 8C14.667 8.92222 14.3414 9.7084 13.6914 10.3584C13.0414 11.0083 12.2552 11.333 11.333 11.333H9.33301C9.14426 11.3329 8.9861 11.2693 8.8584 11.1416C8.73069 11.0139 8.66707 10.8557 8.66699 10.667C8.66699 10.4781 8.73062 10.3192 8.8584 10.1914C8.98607 10.0638 9.14434 10.0001 9.33301 10H11.333C11.8885 10 12.3611 9.80577 12.75 9.41699C13.1389 9.0281 13.333 8.55556 13.333 8C13.333 7.44444 13.1389 6.9719 12.75 6.58301C12.3611 6.19423 11.8885 6 11.333 6H9.33301C9.14434 5.99993 8.98607 5.93619 8.8584 5.80859C8.73062 5.68082 8.66699 5.5219 8.66699 5.33301C8.66707 5.14426 8.73069 4.9861 8.8584 4.8584C8.9861 4.73069 9.14426 4.66707 9.33301 4.66699H11.333ZM10 7.33301C10.1889 7.33301 10.3468 7.39761 10.4746 7.52539C10.6024 7.65317 10.667 7.81111 10.667 8C10.667 8.18889 10.6024 8.34683 10.4746 8.47461C10.3468 8.60239 10.1889 8.66699 10 8.66699H6C5.81111 8.66699 5.65317 8.60239 5.52539 8.47461C5.39761 8.34683 5.33301 8.18889 5.33301 8C5.33301 7.81111 5.39761 7.65317 5.52539 7.52539C5.65317 7.39761 5.81111 7.33301 6 7.33301H10Z", fill: "currentColor" }) }) }) }), _jsx(Components.Generic.Popover.Content, { className: "bn-popover-content bn-form-popover", variant: "form-popover", children: _jsx("div", { ref: linkPopoverRef, children: _jsx(EditLinkMenuItems, { url: (() => {
1055
+ var _a;
1056
+ const sel = linkSelectionRef.current;
1057
+ if (!sel)
1058
+ return "";
1059
+ const existing = linksRef.current.find((l) => l.start < sel.end && l.end > sel.start);
1060
+ return (_a = existing === null || existing === void 0 ? void 0 : existing.url) !== null && _a !== void 0 ? _a : "";
1061
+ })(), text: (_b = (_a = linkSelectionRef.current) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : "", editLink: handleEditLink }) }) })] })), enableAutocomplete && (_jsxs(_Fragment, { children: [_jsx("div", { className: "bn-step-toolbar__divider" }), _jsx("button", { type: "button", className: "bn-step-toolbar__button", onMouseDown: (event) => {
1062
+ event.preventDefault();
1063
+ setShowAllSuggestions(true);
1064
+ textareaNode === null || textareaNode === void 0 ? void 0 : textareaNode.focus();
1065
+ }, "aria-label": "Show suggestions", tabIndex: -1, children: _jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": "true", children: _jsx("path", { d: "M12 10.667H14V12H12V14H10.667V12H8.66699V10.667H10.667V8.66699H12V10.667ZM12 1.33301C12.74 1.33301 13.333 1.92699 13.333 2.66699V7.86621C12.9265 7.63301 12.4798 7.46669 12 7.38672V2.66699H2.66699V12H7.38672C7.46669 12.4798 7.63301 12.9265 7.86621 13.333H2.66699C1.92699 13.333 1.33301 12.74 1.33301 12V2.66699C1.33301 1.92699 1.92699 1.33301 2.66699 1.33301H12ZM7.33301 10.667H4V9.33301H7.33301V10.667ZM10.667 7.38672C10.1004 7.48005 9.5801 7.69336 9.12012 8H4V6.66699H10.667V7.38672ZM10.667 5.33301H4V4H10.667V5.33301Z", fill: "currentColor" }) }) })] })), rightAction] }))] }), enableImageUpload && (_jsx("input", { ref: fileInputRef, type: "file", accept: "image/*", style: { display: "none" }, onChange: async (event) => {
427
1066
  var _a;
428
1067
  const file = (_a = event.target.files) === null || _a === void 0 ? void 0 : _a[0];
429
1068
  if (!file || !uploadImage) {
@@ -443,16 +1082,7 @@ export function StepField({ label, value, onChange, autoFocus, multiline = false
443
1082
  setIsUploading(false);
444
1083
  event.target.value = "";
445
1084
  }
446
- } })), _jsx("div", { ref: editorContainerRef, className: editorClassName, "data-step-field": fieldName, tabIndex: -1, onFocus: (event) => {
447
- if (event.target === editorContainerRef.current) {
448
- if (textareaNode) {
449
- textareaNode.focus();
450
- }
451
- else {
452
- pendingFocusRef.current = true;
453
- }
454
- }
455
- } }), extractedImages.length > 0 && (_jsx("div", { className: "bn-step-images", role: "list", children: extractedImages.map((image) => (_jsxs("div", { className: "bn-step-image-thumb", role: "listitem", children: [_jsx("button", { type: "button", className: "bn-step-image-thumb__button", onClick: () => handleImageClick(image.url), "aria-label": "Preview image", children: _jsx("img", { src: image.url, alt: image.alt || "Step image" }) }), _jsx("button", { type: "button", className: "bn-step-image-thumb__remove", onClick: (event) => {
1085
+ } })), extractedImages.length > 0 && (_jsx("div", { className: "bn-step-images", role: "list", children: extractedImages.map((image) => (_jsxs("div", { className: "bn-step-image-thumb", role: "listitem", children: [_jsx("button", { type: "button", className: "bn-step-image-thumb__button", onClick: () => handleImageClick(image.url), "aria-label": "Preview image", children: _jsx("img", { src: image.url, alt: image.alt || "Step image" }) }), _jsx("button", { type: "button", className: "bn-step-image-thumb__remove", onClick: (event) => {
456
1086
  event.stopPropagation();
457
1087
  handleRemoveImage(image);
458
1088
  }, "aria-label": "Remove image", children: "\u00D7" })] }, image.id))) })), shouldShowAutocomplete && (_jsx("div", { className: "bn-step-suggestions", role: "listbox", "aria-label": `${label} suggestions`, children: filteredSuggestions.map((suggestion, index) => (_jsxs("button", { type: "button", role: "option", "aria-selected": index === activeSuggestionIndex, className: index === activeSuggestionIndex