@bayonai/rich-text-editor 0.1.2

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 (91) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +37 -0
  3. package/dist/index.d.ts +8 -0
  4. package/dist/index.js +5 -0
  5. package/dist/react/BlockActionTool.d.ts +15 -0
  6. package/dist/react/BlockActionTool.js +37 -0
  7. package/dist/react/EditorSessionProvider.d.ts +28 -0
  8. package/dist/react/EditorSessionProvider.js +74 -0
  9. package/dist/react/RichTextBody.d.ts +18 -0
  10. package/dist/react/RichTextBody.js +66 -0
  11. package/dist/react/RichTextDocumentSurface.d.ts +6 -0
  12. package/dist/react/RichTextDocumentSurface.js +5 -0
  13. package/dist/react/RichTextEditor.d.ts +45 -0
  14. package/dist/react/RichTextEditor.js +1096 -0
  15. package/dist/react/RichTextIcons.d.ts +21 -0
  16. package/dist/react/RichTextIcons.js +55 -0
  17. package/dist/react/RichTextRenderedBlock.d.ts +4 -0
  18. package/dist/react/RichTextRenderedBlock.js +36 -0
  19. package/dist/react/RichTextRenderer.d.ts +4 -0
  20. package/dist/react/RichTextRenderer.js +8 -0
  21. package/dist/react/RichTextStyles.d.ts +1 -0
  22. package/dist/react/RichTextStyles.js +719 -0
  23. package/dist/react/RichTextTitleInput.d.ts +20 -0
  24. package/dist/react/RichTextTitleInput.js +39 -0
  25. package/dist/react/SelectionFormatToolbar.d.ts +34 -0
  26. package/dist/react/SelectionFormatToolbar.js +18 -0
  27. package/dist/react/SpecialBlockOption.d.ts +7 -0
  28. package/dist/react/SpecialBlockOption.js +7 -0
  29. package/dist/react/SpecialBlockTool.d.ts +42 -0
  30. package/dist/react/SpecialBlockTool.js +50 -0
  31. package/dist/react/TranscriptionControl.d.ts +19 -0
  32. package/dist/react/TranscriptionControl.js +129 -0
  33. package/dist/react/UnsavedChangesDialog.d.ts +9 -0
  34. package/dist/react/UnsavedChangesDialog.js +13 -0
  35. package/dist/react/blockActionToolState.d.ts +18 -0
  36. package/dist/react/blockActionToolState.js +53 -0
  37. package/dist/react/blockActions.d.ts +8 -0
  38. package/dist/react/blockActions.js +111 -0
  39. package/dist/react/editorNavigation.d.ts +19 -0
  40. package/dist/react/editorNavigation.js +39 -0
  41. package/dist/react/editorShortcuts.d.ts +20 -0
  42. package/dist/react/editorShortcuts.js +25 -0
  43. package/dist/react/index.d.ts +9 -0
  44. package/dist/react/index.js +6 -0
  45. package/dist/react/richTextBlockStyles.d.ts +7 -0
  46. package/dist/react/richTextBlockStyles.js +7 -0
  47. package/dist/react/specialBlockStyles.d.ts +15 -0
  48. package/dist/react/specialBlockStyles.js +9 -0
  49. package/dist/richText.d.ts +15 -0
  50. package/dist/richText.js +297 -0
  51. package/dist/saveControl.d.ts +8 -0
  52. package/dist/saveControl.js +9 -0
  53. package/dist/session.d.ts +27 -0
  54. package/dist/session.js +78 -0
  55. package/dist/sessionRegistry.d.ts +24 -0
  56. package/dist/sessionRegistry.js +87 -0
  57. package/dist/types.d.ts +34 -0
  58. package/dist/types.js +1 -0
  59. package/dist/writingStats.d.ts +5 -0
  60. package/dist/writingStats.js +9 -0
  61. package/dist-cjs/index.js +22 -0
  62. package/dist-cjs/package.json +3 -0
  63. package/dist-cjs/react/BlockActionTool.js +40 -0
  64. package/dist-cjs/react/EditorSessionProvider.js +79 -0
  65. package/dist-cjs/react/RichTextBody.js +69 -0
  66. package/dist-cjs/react/RichTextDocumentSurface.js +8 -0
  67. package/dist-cjs/react/RichTextEditor.js +1108 -0
  68. package/dist-cjs/react/RichTextIcons.js +74 -0
  69. package/dist-cjs/react/RichTextRenderedBlock.js +39 -0
  70. package/dist-cjs/react/RichTextRenderer.js +11 -0
  71. package/dist-cjs/react/RichTextStyles.js +722 -0
  72. package/dist-cjs/react/RichTextTitleInput.js +44 -0
  73. package/dist-cjs/react/SelectionFormatToolbar.js +22 -0
  74. package/dist-cjs/react/SpecialBlockOption.js +10 -0
  75. package/dist-cjs/react/SpecialBlockTool.js +54 -0
  76. package/dist-cjs/react/TranscriptionControl.js +133 -0
  77. package/dist-cjs/react/UnsavedChangesDialog.js +16 -0
  78. package/dist-cjs/react/blockActionToolState.js +58 -0
  79. package/dist-cjs/react/blockActions.js +119 -0
  80. package/dist-cjs/react/editorNavigation.js +45 -0
  81. package/dist-cjs/react/editorShortcuts.js +28 -0
  82. package/dist-cjs/react/index.js +17 -0
  83. package/dist-cjs/react/richTextBlockStyles.js +10 -0
  84. package/dist-cjs/react/specialBlockStyles.js +12 -0
  85. package/dist-cjs/richText.js +307 -0
  86. package/dist-cjs/saveControl.js +12 -0
  87. package/dist-cjs/session.js +83 -0
  88. package/dist-cjs/sessionRegistry.js +90 -0
  89. package/dist-cjs/types.js +2 -0
  90. package/dist-cjs/writingStats.js +12 -0
  91. package/package.json +45 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,38 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.1.2 - 2026-06-24
6
+
7
+ - Added block-level action handles for non-empty editor blocks.
8
+ - Added handle drag-and-drop reordering with a visible drop indicator.
9
+ - Added compact block action menu controls for copy, cut, delete, and close.
10
+ - Added plain-text clipboard behavior for copy and cut, with delete fallback
11
+ preserving an empty paragraph when the document would otherwise be empty.
12
+ - Added checkbox sequence editing so Enter creates the next checkbox,
13
+ checkbox rows can be reordered individually, and adjacent checkbox rows
14
+ remain grouped for copy, cut, and delete actions. Backspace at the start of a
15
+ checkbox row converts that row back to a paragraph.
16
+ - Fixed Shift+Enter at the end of checkbox rows so the first press creates a
17
+ visible line break.
18
+ - Fixed manual soft line breaks so Shift+Enter works at the pressed position,
19
+ browser spacer spans do not stack at the end of checkbox rows, and block
20
+ deletion keeps focus inside the editor.
21
+ - Moved Enter from the title field into the first body text block instead of
22
+ adding another title row.
23
+ - Added an optional browser-native transcription control for editable document
24
+ bodies, defaulting to enabled for hosts that do not explicitly disable it.
25
+ - Kept the special block insertion tool scoped to empty, undesignated blocks,
26
+ with slower smoother show and hide transitions.
27
+ - Fixed block action handles so the focused body block takes precedence over
28
+ stale hover state while editing multiple sections.
29
+
30
+ ## 0.1.1 - 2026-06-12
31
+
32
+ - Removed MUI and Emotion from the rich text editor package peer and dev dependencies.
33
+ - Replaced rich text editor React internals with native HTML controls, local SVG icons, and package-local styles.
34
+ - Added a dependency-boundary test to prevent MUI and Emotion imports from returning to the package source.
35
+
36
+ ## 0.1.0 - 2026-06-12
37
+
38
+ - Initial reusable rich text editor package release.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @bayonai/rich-text-editor
2
+
3
+ Reusable rich-text document utilities, native React editing components, and
4
+ unsaved-change session controls.
5
+
6
+ ## Exports
7
+
8
+ - `@bayonai/rich-text-editor` provides document types, sanitization,
9
+ serialization, comparison, statistics, and session controllers.
10
+ - `@bayonai/rich-text-editor/react` provides the editor, renderer,
11
+ `EditorSessionProvider`, session hooks, and the Save/Discard/Stay dialog.
12
+
13
+ The package has no Next.js, Firebase, or application-domain dependencies.
14
+ Hosts provide controlled document values, optional metadata, persistence
15
+ callbacks, and navigation adapters.
16
+
17
+ ## Editor Capabilities
18
+
19
+ - Native rich-text body editing with headings, quotes, code, dividers,
20
+ checkboxes, image placeholders, and Markdown-compatible text storage.
21
+ - Empty, undesignated blocks expose the special block insertion tool.
22
+ - Non-empty top-level blocks expose a compact gutter handle for block actions.
23
+ - Dragging a block handle reorders blocks with a visible drop indicator.
24
+ - Clicking a block handle opens icon actions for copy, cut, delete, and close.
25
+ - Copy and cut write readable plain text to the clipboard; cut removes the
26
+ block only after the clipboard write succeeds.
27
+ - Deleting the last block preserves one empty paragraph so the editor remains
28
+ writable.
29
+ - Checkbox rows can be reordered individually; copy, cut, and delete actions
30
+ still apply to consecutive checkbox rows as one action group. Pressing Enter
31
+ from a checkbox creates the next checkbox, and Backspace at the start of a
32
+ checkbox row converts that row back to a paragraph.
33
+ - Browser-native transcription can be enabled by hosts with the editor's
34
+ `transcriptionEnabled` option. It defaults to enabled, uses the browser
35
+ `SpeechRecognition` or `webkitSpeechRecognition` API when available, inserts
36
+ final transcript text into the current editor body, and never uploads or
37
+ stores audio.
@@ -0,0 +1,8 @@
1
+ export { canonicalSerialize, createEditorSession, defaultEditorSessionEquals, } from "./session";
2
+ export { getEditorSaveControlState } from "./saveControl";
3
+ export { createEditorSessionRegistry } from "./sessionRegistry";
4
+ export { createEmptyRichTextBlocks, createRichTextBlockId, editorHtmlToMarkdown, isRichTextBlocksEmpty, markdownToEditorHtml, richTextBlocksToPlainText, sanitizeRichTextBlocks, sanitizeRichTextHtml, } from "./richText";
5
+ export { getWritingStats } from "./writingStats";
6
+ export type { EditorDirtyScope, EditorSession, EditorSessionOptions, EditorSessionValue, } from "./session";
7
+ export type { EditorExitAction, EditorSessionRegistry, EditorSessionRegistrySnapshot, } from "./sessionRegistry";
8
+ export type { RichTextBlock, RichTextDocument } from "./types";
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { canonicalSerialize, createEditorSession, defaultEditorSessionEquals, } from "./session.js";
2
+ export { getEditorSaveControlState } from "./saveControl.js";
3
+ export { createEditorSessionRegistry } from "./sessionRegistry.js";
4
+ export { createEmptyRichTextBlocks, createRichTextBlockId, editorHtmlToMarkdown, isRichTextBlocksEmpty, markdownToEditorHtml, richTextBlocksToPlainText, sanitizeRichTextBlocks, sanitizeRichTextHtml, } from "./richText.js";
5
+ export { getWritingStats } from "./writingStats.js";
@@ -0,0 +1,15 @@
1
+ import type { PointerEvent } from "react";
2
+ import type { BlockActionToolPlacement } from "./blockActionToolState";
3
+ type BlockActionToolProps = {
4
+ activeBlockId: string | null;
5
+ draggedBlockId: string | null;
6
+ onAction: (action: "copy" | "cut" | "delete", blockId: string) => void;
7
+ onHoverChange: (hovering: boolean) => void;
8
+ onPointerDragEnd: (event: PointerEvent<HTMLButtonElement>) => void;
9
+ onPointerDragMove: (event: PointerEvent<HTMLButtonElement>) => void;
10
+ onPointerDragStart: (blockId: string, event: PointerEvent<HTMLButtonElement>) => void;
11
+ onToggleMenu: (blockId: string) => void;
12
+ placement: BlockActionToolPlacement;
13
+ };
14
+ export declare function BlockActionTool({ activeBlockId, draggedBlockId, onAction, onHoverChange, onPointerDragEnd, onPointerDragMove, onPointerDragStart, onToggleMenu, placement, }: BlockActionToolProps): import("react/jsx-runtime").JSX.Element;
15
+ export {};
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useRef } from "react";
4
+ import { CloseIcon, CopyIcon, CutIcon, DeleteIcon, DragHandleIcon, } from "./RichTextIcons.js";
5
+ export function BlockActionTool({ activeBlockId, draggedBlockId, onAction, onHoverChange, onPointerDragEnd, onPointerDragMove, onPointerDragStart, onToggleMenu, placement, }) {
6
+ const menuOpen = activeBlockId === placement.blockId;
7
+ const dragging = draggedBlockId === placement.blockId;
8
+ const pointerStartRef = useRef(null);
9
+ const suppressClickRef = useRef(false);
10
+ return (_jsxs("div", { className: `bayon-rte-block-tool${menuOpen ? " bayon-rte-block-tool--menu-open" : ""}`, "data-block-action-tool": placement.blockId, onMouseEnter: () => onHoverChange(true), onMouseLeave: () => onHoverChange(false), style: { "--bayon-rte-block-tool-top": `${placement.top}px` }, children: [menuOpen ? (_jsx("button", { "aria-label": "Close block actions", className: "bayon-rte-icon-button bayon-rte-block-handle bayon-rte-block-handle--menu-open", onClick: () => onToggleMenu(placement.blockId), onMouseDown: (event) => event.preventDefault(), title: "Close block actions", type: "button", children: _jsx(CloseIcon, { size: 19 }) })) : (_jsx("button", { "aria-label": "Block actions", "aria-pressed": dragging, className: `bayon-rte-icon-button bayon-rte-block-handle${dragging ? " bayon-rte-block-handle--dragging" : ""}`, onClick: () => {
11
+ if (suppressClickRef.current) {
12
+ suppressClickRef.current = false;
13
+ return;
14
+ }
15
+ onToggleMenu(placement.blockId);
16
+ }, onPointerCancel: (event) => {
17
+ pointerStartRef.current = null;
18
+ onPointerDragEnd(event);
19
+ }, onPointerDown: (event) => {
20
+ pointerStartRef.current = { x: event.clientX, y: event.clientY };
21
+ event.currentTarget.setPointerCapture(event.pointerId);
22
+ onPointerDragStart(placement.blockId, event);
23
+ }, onPointerMove: (event) => {
24
+ const pointerStart = pointerStartRef.current;
25
+ if (pointerStart &&
26
+ Math.hypot(event.clientX - pointerStart.x, event.clientY - pointerStart.y) > 3) {
27
+ suppressClickRef.current = true;
28
+ }
29
+ onPointerDragMove(event);
30
+ }, onPointerUp: (event) => {
31
+ pointerStartRef.current = null;
32
+ onPointerDragEnd(event);
33
+ }, title: "Drag or open block actions", type: "button", children: _jsx(DragHandleIcon, { size: 19 }) })), menuOpen ? (_jsxs("div", { className: "bayon-rte-block-tool__actions", children: [_jsx(BlockActionButton, { label: "Copy block", onClick: () => onAction("copy", placement.blockId), children: _jsx(CopyIcon, { size: 17 }) }), _jsx(BlockActionButton, { label: "Cut block", onClick: () => onAction("cut", placement.blockId), children: _jsx(CutIcon, { size: 17 }) }), _jsx(BlockActionButton, { label: "Delete block", onClick: () => onAction("delete", placement.blockId), children: _jsx(DeleteIcon, { size: 17 }) })] })) : null] }));
34
+ }
35
+ function BlockActionButton({ children, label, onClick, }) {
36
+ return (_jsx("button", { "aria-label": label, className: "bayon-rte-icon-button bayon-rte-special-button", onClick: onClick, onMouseDown: (event) => event.preventDefault(), title: label, type: "button", children: children }));
37
+ }
@@ -0,0 +1,28 @@
1
+ import { type ReactNode } from "react";
2
+ import { type EditorDirtyScope, type EditorSessionValue } from "../session";
3
+ import { type EditorExitAction } from "../sessionRegistry";
4
+ export type EditorSessionProviderProps = {
5
+ children: ReactNode;
6
+ };
7
+ export declare function EditorSessionProvider({ children, }: EditorSessionProviderProps): import("react/jsx-runtime").JSX.Element;
8
+ export type UseEditorSessionOptions<Metadata> = {
9
+ dirtyScope?: EditorDirtyScope;
10
+ equals?: (left: EditorSessionValue<Metadata>, right: EditorSessionValue<Metadata>) => boolean;
11
+ initialBaselineValue?: EditorSessionValue<Metadata>;
12
+ onReset?: (value: EditorSessionValue<Metadata>) => void;
13
+ onSave: (value: EditorSessionValue<Metadata>) => Promise<unknown>;
14
+ value: EditorSessionValue<Metadata>;
15
+ };
16
+ export type EditorSessionControls<Metadata> = {
17
+ isDirty: boolean;
18
+ isSaving: boolean;
19
+ markClean: (value?: EditorSessionValue<Metadata>) => void;
20
+ requestExit: (action: EditorExitAction) => Promise<boolean>;
21
+ reset: () => EditorSessionValue<Metadata>;
22
+ save: () => Promise<unknown>;
23
+ };
24
+ export declare function useEditorSession<Metadata = unknown>({ dirtyScope, equals, initialBaselineValue, onReset, onSave, value, }: UseEditorSessionOptions<Metadata>): EditorSessionControls<Metadata>;
25
+ export declare function useEditorExitGuard(): {
26
+ hasDirtyChanges: boolean;
27
+ requestExit: (action: EditorExitAction) => Promise<boolean>;
28
+ };
@@ -0,0 +1,74 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { createContext, useContext, useEffect, useState, useSyncExternalStore, } from "react";
4
+ import { createEditorSession, } from "../session.js";
5
+ import { createEditorSessionRegistry, } from "../sessionRegistry.js";
6
+ import { canonicalSerialize } from "../session.js";
7
+ import { UnsavedChangesDialog } from "./UnsavedChangesDialog.js";
8
+ const EditorSessionRegistryContext = createContext(null);
9
+ export function EditorSessionProvider({ children, }) {
10
+ const [registry] = useState(createEditorSessionRegistry);
11
+ const snapshot = useSyncExternalStore(registry.subscribe, registry.getSnapshot, registry.getSnapshot);
12
+ useEffect(() => {
13
+ if (!snapshot.dirty) {
14
+ return;
15
+ }
16
+ function handleBeforeUnload(event) {
17
+ event.preventDefault();
18
+ event.returnValue = "";
19
+ }
20
+ window.addEventListener("beforeunload", handleBeforeUnload);
21
+ return () => window.removeEventListener("beforeunload", handleBeforeUnload);
22
+ }, [snapshot.dirty]);
23
+ return (_jsxs(EditorSessionRegistryContext.Provider, { value: registry, children: [children, _jsx(UnsavedChangesDialog, { error: snapshot.error, onDiscardAndLeave: registry.discardAndExit, onSaveAndLeave: () => {
24
+ void registry.saveAndExit().catch(() => undefined);
25
+ }, onStay: registry.stay, open: snapshot.pendingExit, saving: snapshot.saving })] }));
26
+ }
27
+ export function useEditorSession({ dirtyScope, equals, initialBaselineValue, onReset, onSave, value, }) {
28
+ const registry = useEditorSessionRegistry(false);
29
+ const [session] = useState(() => createEditorSession({
30
+ dirtyScope,
31
+ equals,
32
+ initialBaselineValue,
33
+ initialValue: value,
34
+ onSave,
35
+ }));
36
+ const serializedValue = canonicalSerialize(value);
37
+ const isDirty = useSyncExternalStore(session.subscribe, session.isDirty, () => false);
38
+ const isSaving = useSyncExternalStore(session.subscribe, session.isSaving, () => false);
39
+ useEffect(() => registry.register(session), [registry, session]);
40
+ useEffect(() => {
41
+ session.setOnSave(onSave);
42
+ }, [onSave, session]);
43
+ useEffect(() => {
44
+ session.update(JSON.parse(serializedValue));
45
+ }, [serializedValue, session]);
46
+ return {
47
+ isDirty,
48
+ isSaving,
49
+ markClean: session.markClean,
50
+ requestExit: registry.requestExit,
51
+ reset: () => {
52
+ const resetValue = session.reset();
53
+ onReset?.(resetValue);
54
+ return resetValue;
55
+ },
56
+ save: session.save,
57
+ };
58
+ }
59
+ export function useEditorExitGuard() {
60
+ const registry = useEditorSessionRegistry(true);
61
+ const snapshot = useSyncExternalStore(registry.subscribe, registry.getSnapshot, registry.getSnapshot);
62
+ return {
63
+ hasDirtyChanges: snapshot.dirty,
64
+ requestExit: registry.requestExit,
65
+ };
66
+ }
67
+ function useEditorSessionRegistry(required) {
68
+ const registry = useContext(EditorSessionRegistryContext);
69
+ const [standaloneRegistry] = useState(createEditorSessionRegistry);
70
+ if (!registry && required) {
71
+ throw new Error("Editor session hooks must be used inside EditorSessionProvider.");
72
+ }
73
+ return registry ?? standaloneRegistry;
74
+ }
@@ -0,0 +1,18 @@
1
+ import type { RefObject } from "react";
2
+ import { type EditorKeyboardShortcutCommand } from "./editorNavigation";
3
+ type RichTextBodyProps = {
4
+ bodyRef: RefObject<HTMLDivElement | null>;
5
+ disabled: boolean;
6
+ label: string;
7
+ onArrowUpFromFirstLine: () => void;
8
+ onBackspace: () => boolean;
9
+ onEnter: (event: {
10
+ shiftKey: boolean;
11
+ }) => boolean;
12
+ onFocus: () => void;
13
+ onInput: () => void;
14
+ onSelectionChange: () => void;
15
+ onShortcutCommand: (command: EditorKeyboardShortcutCommand) => void;
16
+ };
17
+ export declare function RichTextBody({ bodyRef, disabled, label, onArrowUpFromFirstLine, onBackspace, onEnter, onFocus, onInput, onSelectionChange, onShortcutCommand, }: RichTextBodyProps): import("react/jsx-runtime").JSX.Element;
18
+ export {};
@@ -0,0 +1,66 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { getCodeBlockSelectionTarget, getEditorKeyboardShortcut, isCursorOnFirstLine, isSelectAllShortcut, } from "./editorNavigation.js";
4
+ // Renders the editable rich-text body and reports input and selection changes.
5
+ export function RichTextBody({ bodyRef, disabled, label, onArrowUpFromFirstLine, onBackspace, onEnter, onFocus, onInput, onSelectionChange, onShortcutCommand, }) {
6
+ return (_jsx("div", { "aria-label": label, className: "bayon-rte-body", contentEditable: !disabled, "data-placeholder": "Tell your story...", dir: "auto", onBlur: onSelectionChange, onClick: (event) => {
7
+ if (event.target instanceof HTMLInputElement &&
8
+ event.target.type === "checkbox") {
9
+ onInput();
10
+ }
11
+ }, onFocus: onFocus, onInput: onInput, onKeyDown: (event) => {
12
+ if (isSelectAllShortcut(event)) {
13
+ const code = getCodeBlockSelectionTarget(event.currentTarget, window.getSelection());
14
+ if (code) {
15
+ event.preventDefault();
16
+ selectElementContents(code);
17
+ onSelectionChange();
18
+ return;
19
+ }
20
+ }
21
+ if (event.key === "Enter" && onEnter(event)) {
22
+ event.preventDefault();
23
+ return;
24
+ }
25
+ if (event.key === "Backspace" && onBackspace()) {
26
+ event.preventDefault();
27
+ return;
28
+ }
29
+ const shortcutCommand = getEditorKeyboardShortcut(event);
30
+ if (shortcutCommand) {
31
+ event.preventDefault();
32
+ onShortcutCommand(shortcutCommand);
33
+ return;
34
+ }
35
+ if (event.key === "ArrowUp" &&
36
+ isCursorOnFirstLine(event.currentTarget.textContent ?? "", getTextOffsetWithin(event.currentTarget))) {
37
+ event.preventDefault();
38
+ onArrowUpFromFirstLine();
39
+ }
40
+ }, onKeyUp: onSelectionChange, onMouseUp: onSelectionChange, onPaste: (event) => {
41
+ event.preventDefault();
42
+ document.execCommand("insertText", false, event.clipboardData.getData("text/plain"));
43
+ onInput();
44
+ }, ref: bodyRef, role: "textbox", suppressContentEditableWarning: true, tabIndex: disabled ? -1 : 0 }));
45
+ }
46
+ function selectElementContents(element) {
47
+ const range = document.createRange();
48
+ range.selectNodeContents(element);
49
+ const selection = window.getSelection();
50
+ selection?.removeAllRanges();
51
+ selection?.addRange(range);
52
+ }
53
+ function getTextOffsetWithin(root) {
54
+ const selection = window.getSelection();
55
+ if (!selection || selection.rangeCount === 0) {
56
+ return 0;
57
+ }
58
+ const selectionRange = selection.getRangeAt(0);
59
+ if (!root.contains(selectionRange.startContainer)) {
60
+ return 0;
61
+ }
62
+ const textRange = document.createRange();
63
+ textRange.selectNodeContents(root);
64
+ textRange.setEnd(selectionRange.startContainer, selectionRange.startOffset);
65
+ return textRange.toString().length;
66
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from "react";
2
+ export type RichTextDocumentSurfaceBackground = "transparent" | "panel";
3
+ export declare function RichTextDocumentSurface({ background, children, }: {
4
+ background?: RichTextDocumentSurfaceBackground;
5
+ children: ReactNode;
6
+ }): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ export function RichTextDocumentSurface({ background = "transparent", children, }) {
3
+ const hasPanelBackground = background === "panel";
4
+ return (_jsx("section", { className: `bayon-rte-surface${hasPanelBackground ? " bayon-rte-surface--panel" : ""}`, children: _jsx("div", { className: "bayon-rte-surface__inner", children: children }) }));
5
+ }
@@ -0,0 +1,45 @@
1
+ import { type RichTextDocumentSurfaceBackground } from "./RichTextDocumentSurface";
2
+ import type { RichTextBlock } from "../types";
3
+ type RichTextEditorProps = {
4
+ bodyLabel: string;
5
+ contentBlocks: RichTextBlock[];
6
+ disabled?: boolean;
7
+ documentBackground?: RichTextDocumentSurfaceBackground;
8
+ helperText?: string;
9
+ onContentBlocksChange: (value: RichTextBlock[]) => void;
10
+ onTitleChange: (value: string) => void;
11
+ required?: boolean;
12
+ title: string;
13
+ titleLabel: string;
14
+ titleValidationMessage?: string;
15
+ transcriptionEnabled?: boolean;
16
+ transcriptionLanguage?: string;
17
+ };
18
+ export declare function RichTextEditor({ bodyLabel, contentBlocks, disabled, documentBackground, helperText, onContentBlocksChange, onTitleChange, required, title, titleLabel, titleValidationMessage, transcriptionEnabled, transcriptionLanguage, }: RichTextEditorProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function getDeletionFocusTarget(blocks: RichTextBlock[], blockId: string): {
20
+ blockId: string;
21
+ position: "end";
22
+ } | {
23
+ blockId: string;
24
+ position: "start";
25
+ } | undefined;
26
+ export declare function normalizeSoftBreakHtml(html: string): string;
27
+ export declare function hasEditableTrailingWhitespace(value: string): boolean;
28
+ export declare function shouldDelayTypographyNormalization(value: string): boolean;
29
+ export declare function splitCheckboxMarkdownAtTextOffset(markdown: string, offset: number): {
30
+ after: string;
31
+ before: string;
32
+ };
33
+ export declare function getCheckboxEnterAction(event: {
34
+ shiftKey: boolean;
35
+ }): "line-break" | "next-checkbox";
36
+ export declare function shouldPadTrailingLineBreak(textAfterCursor: string): boolean;
37
+ export declare function resolveTranscriptionConfig(config?: {
38
+ enabled?: boolean;
39
+ language?: string;
40
+ }): {
41
+ enabled: boolean;
42
+ language: string | undefined;
43
+ };
44
+ export declare function appendTranscriptText(currentText: string, transcript: string): string;
45
+ export {};