smartrte-react 0.3.0 → 0.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.1
4
+
5
+ - Make caret-based superscript and subscript toggling deterministic in controlled React applications.
6
+ - Preserve normal text before and after script spans and prevent nested superscript/subscript markup.
7
+
3
8
  ## 0.3.0
4
9
 
5
10
  - Rebuild list selection and conversion behavior for checklists, bullets, numbers, alphabetic lists, and Roman numerals.
@@ -254,12 +254,8 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
254
254
  italic: queryState("italic"),
255
255
  underline: queryState("underline"),
256
256
  strikeThrough: queryState("strikeThrough"),
257
- subscript: overrideApplies && scriptOverride?.command === "subscript"
258
- ? false
259
- : subscriptActive,
260
- superscript: overrideApplies && scriptOverride?.command === "superscript"
261
- ? false
262
- : superscriptActive,
257
+ subscript: overrideApplies ? scriptOverride?.command === "subscript" : subscriptActive,
258
+ superscript: overrideApplies ? scriptOverride?.command === "superscript" : superscriptActive,
263
259
  checklist: Boolean(element?.closest('[data-srte-checklist="true"]')),
264
260
  unorderedList: Boolean(element?.closest("ul:not([data-srte-checklist=\"true\"])")),
265
261
  orderedList: Boolean(element?.closest("ol")),
@@ -398,55 +394,121 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
398
394
  const range = getSelectionRangeInEditor();
399
395
  if (!editor || !range)
400
396
  return;
401
- const tagName = command === "subscript" ? "sub" : "sup";
402
- const script = range.collapsed
403
- ? getScriptAncestor(range.startContainer, tagName)
404
- : null;
405
- if (range.collapsed && document.queryCommandState(command)) {
406
- document.execCommand(command, false);
407
- const currentRange = getSelectionRangeInEditor();
408
- savedRangeRef.current = currentRange?.cloneRange() || null;
409
- inlineScriptCaretOverrideRef.current = currentRange?.collapsed
410
- ? {
411
- command,
412
- container: currentRange.startContainer,
413
- offset: currentRange.startOffset,
414
- }
415
- : null;
416
- setActiveState((current) => ({
417
- ...current,
418
- subscript: command === "subscript" ? false : current.subscript,
419
- superscript: command === "superscript" ? false : current.superscript,
420
- }));
421
- handleInput();
422
- requestAnimationFrame(updateActiveState);
423
- return;
424
- }
425
- if (script) {
426
- const nextRange = document.createRange();
427
- nextRange.setStartAfter(script);
428
- nextRange.collapse(true);
429
- safeSelectRange(nextRange);
430
- savedRangeRef.current = nextRange.cloneRange();
397
+ if (range.collapsed) {
398
+ const tagName = command === "subscript" ? "sub" : "sup";
399
+ const pending = inlineScriptCaretOverrideRef.current;
400
+ const pendingApplies = pending &&
401
+ pending.container === range.startContainer && pending.offset === range.startOffset;
402
+ const active = pendingApplies
403
+ ? pending.command === command
404
+ : Boolean(getScriptAncestor(range.startContainer, tagName));
405
+ const nextCommand = active ? "normal" : command;
431
406
  inlineScriptCaretOverrideRef.current = {
432
- command,
433
- container: nextRange.startContainer,
434
- offset: nextRange.startOffset,
407
+ command: nextCommand,
408
+ container: range.startContainer,
409
+ offset: range.startOffset,
435
410
  };
436
411
  setActiveState((current) => ({
437
412
  ...current,
438
- subscript: command === "subscript" ? false : current.subscript,
439
- superscript: command === "superscript" ? false : current.superscript,
413
+ subscript: nextCommand === "subscript",
414
+ superscript: nextCommand === "superscript",
440
415
  }));
441
- requestAnimationFrame(updateActiveState);
416
+ savedRangeRef.current = range.cloneRange();
442
417
  return;
443
418
  }
444
419
  inlineScriptCaretOverrideRef.current = null;
445
- exec(command);
420
+ pushEditorHistory();
421
+ const result = getCoreInlineMarkResult(editor, command);
422
+ if (result) {
423
+ editor.innerHTML = result.html;
424
+ ensureTableWrappers(editor);
425
+ addTableResizeHandles();
426
+ restoreSelectionToDom(editor, result.selectionAfter);
427
+ handleInput();
428
+ }
429
+ else {
430
+ exec(command);
431
+ }
446
432
  requestAnimationFrame(updateActiveState);
447
433
  }
448
434
  catch { }
449
435
  };
436
+ useEffect(() => {
437
+ const editor = editableRef.current;
438
+ if (!editor)
439
+ return;
440
+ const splitScriptAtRange = (script, range) => {
441
+ const rightRange = document.createRange();
442
+ rightRange.selectNodeContents(script);
443
+ rightRange.setStart(range.startContainer, range.startOffset);
444
+ const rightContent = rightRange.extractContents();
445
+ const rightScript = script.cloneNode(false);
446
+ rightScript.appendChild(rightContent);
447
+ script.parentNode?.insertBefore(rightScript, script.nextSibling);
448
+ const insertion = document.createRange();
449
+ insertion.setStartAfter(script);
450
+ insertion.collapse(true);
451
+ if (!script.textContent)
452
+ script.remove();
453
+ if (!rightScript.textContent)
454
+ rightScript.remove();
455
+ return insertion;
456
+ };
457
+ const applyPendingInlineScript = (event) => {
458
+ const pending = inlineScriptCaretOverrideRef.current;
459
+ if (!pending || event.inputType !== "insertText" || !event.data)
460
+ return;
461
+ const range = getSelectionRangeInEditor();
462
+ if (!range?.collapsed || range.startContainer !== pending.container || range.startOffset !== pending.offset)
463
+ return;
464
+ event.preventDefault();
465
+ event.stopImmediatePropagation();
466
+ pushEditorHistory();
467
+ const currentScript = getScriptAncestor(range.startContainer, "sub") ||
468
+ getScriptAncestor(range.startContainer, "sup");
469
+ const desiredTag = pending.command === "normal"
470
+ ? null
471
+ : pending.command === "subscript" ? "sub" : "sup";
472
+ let insertion = range;
473
+ if (currentScript && currentScript.tagName.toLowerCase() !== desiredTag) {
474
+ insertion = splitScriptAtRange(currentScript, range);
475
+ }
476
+ const text = document.createTextNode(event.data);
477
+ let inserted = text;
478
+ if (desiredTag && currentScript?.tagName.toLowerCase() !== desiredTag) {
479
+ const script = document.createElement(desiredTag);
480
+ script.appendChild(text);
481
+ inserted = script;
482
+ }
483
+ const pendingSize = pendingFontSizeRef.current;
484
+ if (pendingSize) {
485
+ const span = document.createElement("span");
486
+ span.style.fontSize = `${pendingSize.valuePx}px`;
487
+ span.appendChild(inserted);
488
+ inserted = span;
489
+ }
490
+ insertion.insertNode(inserted);
491
+ const nextRange = document.createRange();
492
+ nextRange.setStartAfter(text);
493
+ nextRange.collapse(true);
494
+ safeSelectRange(nextRange);
495
+ inlineScriptCaretOverrideRef.current = {
496
+ command: pending.command,
497
+ container: nextRange.startContainer,
498
+ offset: nextRange.startOffset,
499
+ };
500
+ if (pendingSize)
501
+ pendingFontSizeRef.current = {
502
+ valuePx: pendingSize.valuePx,
503
+ container: nextRange.startContainer,
504
+ offset: nextRange.startOffset,
505
+ };
506
+ savedRangeRef.current = nextRange.cloneRange();
507
+ handleInput();
508
+ };
509
+ editor.addEventListener("beforeinput", applyPendingInlineScript);
510
+ return () => editor.removeEventListener("beforeinput", applyPendingInlineScript);
511
+ }, []);
450
512
  const applyFormatBlock = (blockName) => {
451
513
  try {
452
514
  if (!restoreSavedSelection()) {
@@ -4967,7 +5029,7 @@ export function ClassicEditor({ value, onChange, placeholder = "Type here…", m
4967
5029
  borderRadius: 6,
4968
5030
  background: "var(--srte-input-bg)",
4969
5031
  color: "var(--srte-input-text)",
4970
- }, children: _jsx("span", { style: { fontWeight: 700, padding: "1px 4px", background: "var(--srte-accent-bg)", borderRadius: 3 }, children: "A" }) }), _jsxs("button", { title: "Subscript", onClick: () => toggleInlineScript("subscript"), "aria-pressed": activeState.subscript, style: activeButtonStyle(activeState.subscript), children: ["X", _jsx("sub", { children: "2" })] }), _jsxs("button", { title: "Superscript", onClick: () => toggleInlineScript("superscript"), "aria-pressed": activeState.superscript, style: activeButtonStyle(activeState.superscript), children: ["X", _jsx("sup", { children: "2" })] }), [
5032
+ }, children: _jsx("span", { style: { fontWeight: 700, padding: "1px 4px", background: "var(--srte-accent-bg)", borderRadius: 3 }, children: "A" }) }), _jsxs("button", { title: "Subscript", onMouseDown: preserveEditorSelection, onClick: () => toggleInlineScript("subscript"), "aria-pressed": activeState.subscript, style: activeButtonStyle(activeState.subscript), children: ["X", _jsx("sub", { children: "2" })] }), _jsxs("button", { title: "Superscript", onMouseDown: preserveEditorSelection, onClick: () => toggleInlineScript("superscript"), "aria-pressed": activeState.superscript, style: activeButtonStyle(activeState.superscript), children: ["X", _jsx("sup", { children: "2" })] }), [
4971
5033
  {
4972
5034
  key: "check",
4973
5035
  label: "☐",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smartrte-react",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "A powerful, feature-rich Rich Text Editor for React with support for tables, mathematical formulas (LaTeX/KaTeX), and media management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -79,6 +79,6 @@
79
79
  "jszip": "^3.10.1",
80
80
  "mammoth": "^1.11.0",
81
81
  "pdfjs-dist": "^5.4.530",
82
- "smartrte-core": "^0.2.0"
82
+ "smartrte-core": "^0.2.1"
83
83
  }
84
84
  }