@bayonai/rich-text-editor 0.1.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BEHAVIOR.md +396 -0
- package/CHANGELOG.md +22 -0
- package/README.md +25 -6
- package/dist/core/blockTree.d.ts +14 -0
- package/dist/core/blockTree.js +126 -0
- package/dist/core/blockTypes.d.ts +6 -0
- package/dist/core/blockTypes.js +5 -0
- package/dist/core/exportImport.d.ts +59 -0
- package/dist/core/exportImport.js +51 -0
- package/dist/core/features.d.ts +59 -0
- package/dist/core/features.js +57 -0
- package/dist/core/imageBlockDiagnostics.d.ts +4 -0
- package/dist/core/imageBlockDiagnostics.js +19 -0
- package/dist/core/proFeatures.d.ts +60 -0
- package/dist/core/proFeatures.js +64 -0
- package/dist/{richText.d.ts → core/richText.d.ts} +2 -0
- package/dist/core/richText.js +566 -0
- package/dist/core/types.d.ts +78 -0
- package/dist/index.d.ts +14 -8
- package/dist/index.js +8 -5
- package/dist/react/editor/RichTextBody.d.ts +28 -0
- package/dist/react/editor/RichTextBody.js +131 -0
- package/dist/react/editor/RichTextEditor.d.ts +138 -0
- package/dist/react/editor/RichTextEditor.js +2925 -0
- package/dist/react/editor/RichTextRenderedBlock.d.ts +20 -0
- package/dist/react/editor/RichTextRenderedBlock.js +162 -0
- package/dist/react/editor/RichTextRenderer.d.ts +13 -0
- package/dist/react/editor/RichTextRenderer.js +16 -0
- package/dist/react/{RichTextTitleInput.d.ts → editor/RichTextTitleInput.d.ts} +11 -1
- package/dist/react/{RichTextTitleInput.js → editor/RichTextTitleInput.js} +17 -2
- package/dist/react/editor/blockActions.d.ts +48 -0
- package/dist/react/editor/blockActions.js +495 -0
- package/dist/react/editor/editorHistory.d.ts +55 -0
- package/dist/react/editor/editorHistory.js +111 -0
- package/dist/react/{editorNavigation.d.ts → editor/editorNavigation.d.ts} +2 -0
- package/dist/react/{editorNavigation.js → editor/editorNavigation.js} +16 -0
- package/dist/react/editor/editorOperations.d.ts +10 -0
- package/dist/react/editor/editorOperations.js +3 -0
- package/dist/react/editor/editorSelection.d.ts +3 -0
- package/dist/react/editor/editorSelection.js +215 -0
- package/dist/react/{editorShortcuts.d.ts → editor/editorShortcuts.d.ts} +10 -0
- package/dist/react/{editorShortcuts.js → editor/editorShortcuts.js} +17 -1
- package/dist/react/{RichTextIcons.d.ts → icons/RichTextIcons.d.ts} +3 -0
- package/dist/react/{RichTextIcons.js → icons/RichTextIcons.js} +9 -0
- package/dist/react/index.d.ts +12 -9
- package/dist/react/index.js +7 -6
- package/dist/react/{EditorSessionProvider.d.ts → session/EditorSessionProvider.d.ts} +2 -2
- package/dist/react/{EditorSessionProvider.js → session/EditorSessionProvider.js} +3 -3
- package/dist/react/{UnsavedChangesDialog.js → session/UnsavedChangesDialog.js} +1 -1
- package/dist/react/styles/RichTextStyles.js +1362 -0
- package/dist/react/{BlockActionTool.d.ts → tools/BlockActionTool.d.ts} +1 -1
- package/dist/react/{BlockActionTool.js → tools/BlockActionTool.js} +6 -2
- package/dist/react/tools/LinkCreationInput.d.ts +9 -0
- package/dist/react/tools/LinkCreationInput.js +38 -0
- package/dist/react/{SelectionFormatToolbar.d.ts → tools/SelectionFormatToolbar.d.ts} +3 -2
- package/dist/react/{SelectionFormatToolbar.js → tools/SelectionFormatToolbar.js} +3 -3
- package/dist/react/tools/SpecialBlockOption.d.ts +9 -0
- package/dist/react/tools/SpecialBlockOption.js +8 -0
- package/dist/react/tools/SpecialBlockTool.d.ts +91 -0
- package/dist/react/tools/SpecialBlockTool.js +125 -0
- package/dist/react/{TranscriptionControl.d.ts → tools/TranscriptionControl.d.ts} +9 -0
- package/dist/react/{TranscriptionControl.js → tools/TranscriptionControl.js} +70 -9
- package/dist/react/tools/blockActionToolState.d.ts +41 -0
- package/dist/react/tools/blockActionToolState.js +177 -0
- package/dist/react/tools/imageBlockDiagnostics.d.ts +2 -0
- package/dist/react/tools/imageBlockDiagnostics.js +12 -0
- package/dist/{session.d.ts → session/session.d.ts} +1 -1
- package/dist-cjs/core/blockTree.js +137 -0
- package/dist-cjs/core/blockTypes.js +9 -0
- package/dist-cjs/core/exportImport.js +56 -0
- package/dist-cjs/core/features.js +62 -0
- package/dist-cjs/core/proFeatures.js +70 -0
- package/dist-cjs/core/richText.js +578 -0
- package/dist-cjs/index.js +22 -6
- package/dist-cjs/react/editor/RichTextBody.js +134 -0
- package/dist-cjs/react/editor/RichTextEditor.js +2956 -0
- package/dist-cjs/react/editor/RichTextRenderedBlock.js +166 -0
- package/dist-cjs/react/editor/RichTextRenderer.js +20 -0
- package/dist-cjs/react/{RichTextTitleInput.js → editor/RichTextTitleInput.js} +18 -2
- package/dist-cjs/react/editor/blockActions.js +518 -0
- package/dist-cjs/react/editor/editorHistory.js +120 -0
- package/dist-cjs/react/{editorNavigation.js → editor/editorNavigation.js} +17 -0
- package/dist-cjs/react/editor/editorOperations.js +6 -0
- package/dist-cjs/react/editor/editorSelection.js +219 -0
- package/dist-cjs/react/{editorShortcuts.js → editor/editorShortcuts.js} +17 -1
- package/dist-cjs/react/{RichTextIcons.js → icons/RichTextIcons.js} +12 -0
- package/dist-cjs/react/index.js +9 -7
- package/dist-cjs/react/{EditorSessionProvider.js → session/EditorSessionProvider.js} +3 -3
- package/dist-cjs/react/{UnsavedChangesDialog.js → session/UnsavedChangesDialog.js} +1 -1
- package/dist-cjs/react/styles/RichTextStyles.js +1365 -0
- package/dist-cjs/react/{BlockActionTool.js → tools/BlockActionTool.js} +6 -2
- package/dist-cjs/react/tools/LinkCreationInput.js +41 -0
- package/dist-cjs/react/{SelectionFormatToolbar.js → tools/SelectionFormatToolbar.js} +3 -3
- package/dist-cjs/react/tools/SpecialBlockOption.js +11 -0
- package/dist-cjs/react/tools/SpecialBlockTool.js +129 -0
- package/dist-cjs/react/{TranscriptionControl.js → tools/TranscriptionControl.js} +71 -9
- package/dist-cjs/react/tools/blockActionToolState.js +186 -0
- package/package.json +3 -2
- package/dist/react/RichTextBody.d.ts +0 -18
- package/dist/react/RichTextBody.js +0 -66
- package/dist/react/RichTextEditor.d.ts +0 -45
- package/dist/react/RichTextEditor.js +0 -1096
- package/dist/react/RichTextRenderedBlock.d.ts +0 -4
- package/dist/react/RichTextRenderedBlock.js +0 -36
- package/dist/react/RichTextRenderer.d.ts +0 -4
- package/dist/react/RichTextRenderer.js +0 -8
- package/dist/react/RichTextStyles.js +0 -719
- package/dist/react/SpecialBlockOption.d.ts +0 -7
- package/dist/react/SpecialBlockOption.js +0 -7
- package/dist/react/SpecialBlockTool.d.ts +0 -42
- package/dist/react/SpecialBlockTool.js +0 -50
- package/dist/react/blockActionToolState.d.ts +0 -18
- package/dist/react/blockActionToolState.js +0 -53
- package/dist/react/blockActions.d.ts +0 -8
- package/dist/react/blockActions.js +0 -111
- package/dist/richText.js +0 -297
- package/dist/types.d.ts +0 -34
- package/dist-cjs/react/RichTextBody.js +0 -69
- package/dist-cjs/react/RichTextEditor.js +0 -1108
- package/dist-cjs/react/RichTextRenderedBlock.js +0 -39
- package/dist-cjs/react/RichTextRenderer.js +0 -11
- package/dist-cjs/react/RichTextStyles.js +0 -722
- package/dist-cjs/react/SpecialBlockOption.js +0 -10
- package/dist-cjs/react/SpecialBlockTool.js +0 -54
- package/dist-cjs/react/blockActionToolState.js +0 -58
- package/dist-cjs/react/blockActions.js +0 -119
- package/dist-cjs/richText.js +0 -307
- /package/dist/{types.js → core/types.js} +0 -0
- /package/dist/{writingStats.d.ts → core/writingStats.d.ts} +0 -0
- /package/dist/{writingStats.js → core/writingStats.js} +0 -0
- /package/dist/react/{RichTextDocumentSurface.d.ts → editor/RichTextDocumentSurface.d.ts} +0 -0
- /package/dist/react/{RichTextDocumentSurface.js → editor/RichTextDocumentSurface.js} +0 -0
- /package/dist/react/{UnsavedChangesDialog.d.ts → session/UnsavedChangesDialog.d.ts} +0 -0
- /package/dist/react/{RichTextStyles.d.ts → styles/RichTextStyles.d.ts} +0 -0
- /package/dist/react/{richTextBlockStyles.d.ts → styles/richTextBlockStyles.d.ts} +0 -0
- /package/dist/react/{richTextBlockStyles.js → styles/richTextBlockStyles.js} +0 -0
- /package/dist/react/{specialBlockStyles.d.ts → styles/specialBlockStyles.d.ts} +0 -0
- /package/dist/react/{specialBlockStyles.js → styles/specialBlockStyles.js} +0 -0
- /package/dist/{saveControl.d.ts → session/saveControl.d.ts} +0 -0
- /package/dist/{saveControl.js → session/saveControl.js} +0 -0
- /package/dist/{session.js → session/session.js} +0 -0
- /package/dist/{sessionRegistry.d.ts → session/sessionRegistry.d.ts} +0 -0
- /package/dist/{sessionRegistry.js → session/sessionRegistry.js} +0 -0
- /package/dist-cjs/{types.js → core/types.js} +0 -0
- /package/dist-cjs/{writingStats.js → core/writingStats.js} +0 -0
- /package/dist-cjs/react/{RichTextDocumentSurface.js → editor/RichTextDocumentSurface.js} +0 -0
- /package/dist-cjs/react/{richTextBlockStyles.js → styles/richTextBlockStyles.js} +0 -0
- /package/dist-cjs/react/{specialBlockStyles.js → styles/specialBlockStyles.js} +0 -0
- /package/dist-cjs/{saveControl.js → session/saveControl.js} +0 -0
- /package/dist-cjs/{session.js → session/session.js} +0 -0
- /package/dist-cjs/{sessionRegistry.js → session/sessionRegistry.js} +0 -0
package/BEHAVIOR.md
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
# Rich Text Editor Behavior
|
|
2
|
+
|
|
3
|
+
This document describes the visible behavior and internal structure contracts for
|
|
4
|
+
`@bayonai/rich-text-editor`. It is intended for maintainers changing editor
|
|
5
|
+
behavior. Public package APIs and the persisted `RichTextBlock` shape should
|
|
6
|
+
remain stable unless a migration is explicitly planned.
|
|
7
|
+
|
|
8
|
+
## Core Model
|
|
9
|
+
|
|
10
|
+
The saved document model is `RichTextBlock[]`.
|
|
11
|
+
|
|
12
|
+
Top-level blocks can be paragraphs, headings, quotes, code, dividers, images,
|
|
13
|
+
bullets, ordered rows, checkboxes, or toggles. Only tree rows are nestable:
|
|
14
|
+
|
|
15
|
+
- `bullet`
|
|
16
|
+
- `ordered`
|
|
17
|
+
- `checkbox`
|
|
18
|
+
- `toggle`
|
|
19
|
+
|
|
20
|
+
Tree rows carry child blocks in `children`. Toggles also carry `collapsed`.
|
|
21
|
+
Checkboxes also carry `checked`.
|
|
22
|
+
|
|
23
|
+
Paragraphs, headings, and quotes currently stay flat. Do not introduce generic
|
|
24
|
+
block nesting unless the editor model and persistence boundaries are planned
|
|
25
|
+
for it.
|
|
26
|
+
|
|
27
|
+
## Source Responsibilities
|
|
28
|
+
|
|
29
|
+
- `src/core/types.ts` defines the persisted block shape.
|
|
30
|
+
- `src/core/richText.ts` owns sanitization, Markdown conversion, clipboard
|
|
31
|
+
encode/decode, and public non-React helpers.
|
|
32
|
+
- `src/react/editor/RichTextEditor.tsx` owns the editable DOM, semantic edit
|
|
33
|
+
handlers, DOM-to-block parsing, editor HTML serialization, selection
|
|
34
|
+
restoration, and keyboard behavior.
|
|
35
|
+
- `src/react/editor/RichTextBody.tsx` is the `contentEditable` shell and should
|
|
36
|
+
stay thin.
|
|
37
|
+
- `src/react/editor/blockActions.ts` owns pure block operations such as split,
|
|
38
|
+
delete, indent, outdent, reorder, and clipboard text for block actions.
|
|
39
|
+
- `src/react/editor/RichTextRenderedBlock.tsx` owns read-only rendering and must
|
|
40
|
+
stay visually aligned with editor rendering.
|
|
41
|
+
- `src/react/styles/RichTextStyles.tsx` owns package styles for both editable
|
|
42
|
+
and read-only surfaces.
|
|
43
|
+
|
|
44
|
+
When possible, add behavior in pure helpers first, cover it with focused tests,
|
|
45
|
+
then connect it to the editable DOM.
|
|
46
|
+
|
|
47
|
+
## Component Behavior Inventory
|
|
48
|
+
|
|
49
|
+
The React package surface is intentionally small. Keep these responsibilities
|
|
50
|
+
stable so host apps can compose the editor without depending on private DOM
|
|
51
|
+
details.
|
|
52
|
+
|
|
53
|
+
- `RichTextEditor` is the full controlled editing experience. It composes the
|
|
54
|
+
title, document surface, editable body, selection toolbar, special block
|
|
55
|
+
insertion tool, block action handles, drag/drop state, stats, and optional
|
|
56
|
+
transcription. It must emit sanitized `RichTextBlock[]` updates through
|
|
57
|
+
`onContentBlocksChange` and title updates through `onTitleChange`.
|
|
58
|
+
- `RichTextDocumentSurface` provides the shared editor/read-only document
|
|
59
|
+
frame. It owns only the transparent vs panel background shell and should not
|
|
60
|
+
learn editor state or block semantics.
|
|
61
|
+
- `RichTextTitleInput` renders the editable auto-growing title textarea.
|
|
62
|
+
`Enter` and `ArrowDown` move focus to the start of the body. Validation text
|
|
63
|
+
is announced with `role="alert"` and the input exposes invalid/required ARIA
|
|
64
|
+
state.
|
|
65
|
+
- `RichTextReadTitle` renders the read-only title with the same textarea
|
|
66
|
+
sizing and package title styles as the editable title. Use it for view/edit
|
|
67
|
+
parity instead of duplicating title chrome in host apps.
|
|
68
|
+
- `RichTextBody` is the only `contentEditable` surface. It delegates semantic
|
|
69
|
+
Enter, Backspace, Delete, Tab, paste, toggle-collapse, and formatting shortcut
|
|
70
|
+
behavior to `RichTextEditor`; it should stay a thin event boundary.
|
|
71
|
+
- `RichTextRenderer` renders sanitized blocks for read-only views and injects
|
|
72
|
+
the shared package styles. When `onContentBlocksChange` is supplied, rendered
|
|
73
|
+
toggles are interactive and persist collapsed-state changes through the same
|
|
74
|
+
block shape.
|
|
75
|
+
- `RichTextRenderedBlocks` and `RichTextRenderedBlock` own read-only block
|
|
76
|
+
markup. They group adjacent tree rows into semantic `ul`/`ol` lists, render
|
|
77
|
+
nested children recursively, preserve checkbox checked state, and keep toggle
|
|
78
|
+
title/content structure aligned with the editor DOM.
|
|
79
|
+
- `RichTextStyleScope` injects the package CSS needed by editor, renderer,
|
|
80
|
+
title, transcription, block tools, and unsaved-change dialog surfaces. New
|
|
81
|
+
visible package components should reuse these classes rather than shipping
|
|
82
|
+
app-specific styles.
|
|
83
|
+
- `SelectionFormatToolbar` is the floating selection toolbar. It uses icon
|
|
84
|
+
buttons with labels/tooltips for bold, italic, link, title, quote, and code,
|
|
85
|
+
and it must keep mouse-down from stealing the active text selection.
|
|
86
|
+
- `SpecialBlockTool` and `SpecialBlockOption` render the floating add-block
|
|
87
|
+
control for empty insertion targets. The action catalog is the single source
|
|
88
|
+
of truth for special block labels, icons, inserted HTML, and initial focus
|
|
89
|
+
behavior.
|
|
90
|
+
- `BlockActionTool` renders each visible block handle and its copy, cut, delete,
|
|
91
|
+
and close icon controls. It owns pointer capture/suppressed-click behavior for
|
|
92
|
+
drag handles, but block movement and clipboard semantics stay in pure helpers.
|
|
93
|
+
- `TranscriptionControl` owns browser `SpeechRecognition` integration. It must
|
|
94
|
+
remain browser-local, expose clear ready/recording/success/error/unavailable
|
|
95
|
+
states, stop recording on unmount, and report final transcript text through
|
|
96
|
+
`onTranscript` only. It must treat mobile Safari as unavailable even when
|
|
97
|
+
`webkitSpeechRecognition` exists, because those sessions can get stuck.
|
|
98
|
+
- `EditorSessionProvider` owns the shared unsaved-change registry, beforeunload
|
|
99
|
+
guard, and pending-exit dialog. Use `useEditorSession` for a controlled
|
|
100
|
+
editor's dirty/save/reset state and `useEditorExitGuard` for route/navigation
|
|
101
|
+
guards.
|
|
102
|
+
- `UnsavedChangesDialog` is the default pending-exit UI. It lets users stay,
|
|
103
|
+
discard and leave, or save and leave; backdrop clicks stay on the page unless
|
|
104
|
+
a save is in progress, and save errors are announced.
|
|
105
|
+
- `RichTextIcons` are package-local presentational icons. They should stay
|
|
106
|
+
side-effect free and accessible labels should live on the buttons that render
|
|
107
|
+
them.
|
|
108
|
+
|
|
109
|
+
## Baseline Patterns Intention
|
|
110
|
+
|
|
111
|
+
- Treat every visible row as a block. Paragraphs, headings, quotes, code,
|
|
112
|
+
media placeholders, dividers, list rows, checkboxes, and toggles should all
|
|
113
|
+
feel independently selectable, movable, copyable, and deletable when that
|
|
114
|
+
action is supported.
|
|
115
|
+
- Keep creation shortcuts lightweight and Markdown-like. A user should be able
|
|
116
|
+
to create common structure from the keyboard without opening menus: list
|
|
117
|
+
markers, checkbox syntax, numbered rows, headings, quotes, toggles, dividers,
|
|
118
|
+
and code blocks should remain discoverable through text shortcuts.
|
|
119
|
+
- Prefer contextual chrome over persistent controls. Block handles, insertion
|
|
120
|
+
tools, drag affordances, and selection formatting should appear near the
|
|
121
|
+
active or hovered content and stay quiet when the user is simply reading or
|
|
122
|
+
typing.
|
|
123
|
+
- Preserve keyboard-first editing. Enter creates the next natural row,
|
|
124
|
+
Shift+Enter creates an intentional soft break, Tab and Shift+Tab change list
|
|
125
|
+
hierarchy, Backspace exits or removes structure at the beginning of a block,
|
|
126
|
+
and common formatting shortcuts should not require the mouse.
|
|
127
|
+
- Keep block transforms conceptually reversible. When a row changes from one
|
|
128
|
+
type to another, preserve its visible text and caret position where practical
|
|
129
|
+
instead of treating the action as delete plus recreate.
|
|
130
|
+
- Make toggles manual and predictable. Toggle state changes only when the user
|
|
131
|
+
activates the toggle control or an explicit shortcut. Collapsing hides child
|
|
132
|
+
content visually but must not mutate, reorder, or discard those children.
|
|
133
|
+
- Keep drag/drop scoped to obvious block movement. Moving a block should move
|
|
134
|
+
its subtree, show a precise destination indicator, and avoid accidental
|
|
135
|
+
structural changes when the pointer is released on an invalid or ambiguous
|
|
136
|
+
target.
|
|
137
|
+
- Keep read-only output visually faithful to editing output. Users should not
|
|
138
|
+
need to learn a different document shape when switching between edit and
|
|
139
|
+
view modes.
|
|
140
|
+
- Keep mobile editing uncluttered. Favor the document content and the current
|
|
141
|
+
action target; avoid always-visible heavy toolbars, wide controls, or
|
|
142
|
+
side-scrolling layouts.
|
|
143
|
+
- Use icon buttons with accessible labels and tooltips for compact editor
|
|
144
|
+
controls. Text labels belong in menus, dialogs, validation, and ambiguous
|
|
145
|
+
destructive flows where the extra clarity is worth the space.
|
|
146
|
+
|
|
147
|
+
## Current Baseline Discrepancies
|
|
148
|
+
|
|
149
|
+
These are known places where the current editor does not fully meet the
|
|
150
|
+
baseline patterns above. Treat this section as implementation guidance for
|
|
151
|
+
future editor polish, not as permission to broaden unrelated bug fixes.
|
|
152
|
+
|
|
153
|
+
- Block transforms are only partial. Text shortcuts create structure, Backspace
|
|
154
|
+
can convert supported tree rows back to paragraphs, and the selection toolbar
|
|
155
|
+
can format selected text as heading, quote, or code. There is not yet a
|
|
156
|
+
general row-level transform menu for changing an existing block between
|
|
157
|
+
paragraph, heading, quote, list row, checkbox, toggle, code, divider, or image
|
|
158
|
+
while preserving text, children, and caret placement.
|
|
159
|
+
- The shortcut set is intentionally smaller than a full Notion-style shortcut
|
|
160
|
+
map. The editor supports common patterns such as `# `, `> `, `>> `, `- `,
|
|
161
|
+
`* `, `1. `, `[]`, `[x]`, divider markers, and fenced-code starts, but it
|
|
162
|
+
does not expose multiple heading levels, slash commands, or Notion-exact
|
|
163
|
+
toggle/list shortcuts.
|
|
164
|
+
- The floating special-block insertion tool does not expose every basic block
|
|
165
|
+
type. It currently includes image, quote, title, bullet list, toggle, code
|
|
166
|
+
block, embedded HTML, and divider. Ordered rows and checkboxes are keyboard
|
|
167
|
+
shortcuts only unless a host builds additional chrome.
|
|
168
|
+
- Keyboard behavior covers core editing but not full block navigation. There is
|
|
169
|
+
no dedicated block-selection mode, no keyboard command for opening the block
|
|
170
|
+
action menu, no shortcut to toggle a checkbox or collapse a toggle from
|
|
171
|
+
anywhere in the row, and no expand/collapse-all toggle command.
|
|
172
|
+
- Read-only rendering is close to edit-mode structure, but it is not a perfect
|
|
173
|
+
mirror. Empty toggle content is omitted in read-only output, URL-backed image
|
|
174
|
+
blocks render their image, upload-progress placeholders stay editor-only, and
|
|
175
|
+
read-only block actions are not available.
|
|
176
|
+
- Mobile simplicity is an intention rather than a fully verified contract. The
|
|
177
|
+
editor uses contextual controls, but viewport-level checks should verify that
|
|
178
|
+
block handles, selection toolbar, special-block tool, transcription control,
|
|
179
|
+
and nested toggle/list rows do not create horizontal scrolling or obscure the
|
|
180
|
+
active text target on small screens.
|
|
181
|
+
|
|
182
|
+
## Tree Row Structure
|
|
183
|
+
|
|
184
|
+
Bullets, ordered rows, checkboxes, and toggles are all tree rows. Shared tree
|
|
185
|
+
row behavior should use shared helpers rather than type-specific branches.
|
|
186
|
+
|
|
187
|
+
Shared concepts:
|
|
188
|
+
|
|
189
|
+
- row detection: `getTreeRowEditorParts(...)`
|
|
190
|
+
- label focus: `focusTreeRowLabelAtTextOffset(...)`
|
|
191
|
+
- Enter splitting: `splitTreeRowMarkdownAtTextOffset(...)` and
|
|
192
|
+
`splitTreeRowBlock(...)`
|
|
193
|
+
- Backspace paragraph exit:
|
|
194
|
+
`getTreeRowBackspaceParagraphMarkdown(...)` and
|
|
195
|
+
`getTreeRowBackspaceFocusPositionForParagraph(...)`
|
|
196
|
+
- nesting: `indentBlock(...)`, `outdentBlock(...)`, `reorderBlock(...)`
|
|
197
|
+
|
|
198
|
+
Type-specific behavior should be limited to actual type differences:
|
|
199
|
+
|
|
200
|
+
- bullet marker vs ordered counter vs checkbox input
|
|
201
|
+
- checkbox `checked` state
|
|
202
|
+
- toggle `collapsed` state and content box
|
|
203
|
+
- clipboard prefixes such as `- `, `1. `, `[ ] `, and `[x] `
|
|
204
|
+
|
|
205
|
+
## Cursor Behavior Guidelines
|
|
206
|
+
|
|
207
|
+
Prefer visually consistent behavior over a technically pure model
|
|
208
|
+
interpretation.
|
|
209
|
+
|
|
210
|
+
Selection and toolbar behavior:
|
|
211
|
+
|
|
212
|
+
- Text selection shows the formatting toolbar above block handles and hover
|
|
213
|
+
zones.
|
|
214
|
+
- While a selection toolbar is active, formatting controls take priority over
|
|
215
|
+
block drag controls.
|
|
216
|
+
|
|
217
|
+
Backspace at the beginning of a tree-row label:
|
|
218
|
+
|
|
219
|
+
- Empty bullet exits to a paragraph containing `*`, with the caret after `*`.
|
|
220
|
+
- Empty ordered row exits to a paragraph containing `1.`, with the caret after
|
|
221
|
+
`1.`.
|
|
222
|
+
- Empty checkbox exits to an empty paragraph, with the caret at the start.
|
|
223
|
+
- Non-empty bullet, ordered row, or checkbox exits to a paragraph containing the
|
|
224
|
+
same text, with the caret at the start of that text. This keeps the caret
|
|
225
|
+
visually where it was before the marker/input was removed.
|
|
226
|
+
- Empty toggle title removes the toggle only when its content is empty.
|
|
227
|
+
- Toggle content must not backspace into the title. The title and content box
|
|
228
|
+
are a strict pair.
|
|
229
|
+
- Backspace in an empty text block inside toggle content deletes that block
|
|
230
|
+
when there is a previous content sibling to focus. It does nothing at the top
|
|
231
|
+
of an otherwise empty content box.
|
|
232
|
+
- Backspace immediately below a toggle must not pull the block into the toggle.
|
|
233
|
+
Toggle title and content are not allowed to merge with adjacent blocks.
|
|
234
|
+
|
|
235
|
+
Delete in an empty editable block:
|
|
236
|
+
|
|
237
|
+
- Forward Delete removes the empty current block and moves focus to the next
|
|
238
|
+
visible block when one exists.
|
|
239
|
+
- If no next focus target exists, Delete does not remove the final editable
|
|
240
|
+
fallback block.
|
|
241
|
+
|
|
242
|
+
Enter inside a tree-row label:
|
|
243
|
+
|
|
244
|
+
- Splits at the browser-visible cursor offset.
|
|
245
|
+
- Preserves intentional trailing spaces on the previous row.
|
|
246
|
+
- Moves children to the new visual sibling when a parent row is split.
|
|
247
|
+
- Empty tree rows become a default paragraph.
|
|
248
|
+
- Paragraph-like blocks inside toggle content split inside the same toggle
|
|
249
|
+
content wrapper. Enter should not break them out of the toggle.
|
|
250
|
+
- Shift+Enter stays inside the current row and inserts a visible soft break.
|
|
251
|
+
|
|
252
|
+
Tab and Shift+Tab:
|
|
253
|
+
|
|
254
|
+
- Use the shared block tree operations.
|
|
255
|
+
- Preserve following child sequences when outdenting.
|
|
256
|
+
- Keep focus at the same visible label offset when practical.
|
|
257
|
+
|
|
258
|
+
Placeholders and empty hit targets:
|
|
259
|
+
|
|
260
|
+
- Empty checkbox labels are visually empty and must not use a `Task`
|
|
261
|
+
placeholder.
|
|
262
|
+
- Empty checkbox labels still need a stable same-line editable hit area beside
|
|
263
|
+
the checkbox input.
|
|
264
|
+
- Empty list and toggle labels may show calm placeholders without selecting
|
|
265
|
+
placeholder text.
|
|
266
|
+
- Empty toggle content shows the `Toggle content` placeholder inside its content
|
|
267
|
+
wrapper.
|
|
268
|
+
|
|
269
|
+
## Toggle Guidelines
|
|
270
|
+
|
|
271
|
+
A toggle is one strict structure:
|
|
272
|
+
|
|
273
|
+
1. title row
|
|
274
|
+
2. content wrapper
|
|
275
|
+
|
|
276
|
+
The content wrapper should always exist while editing, including when empty.
|
|
277
|
+
It is an independent nested document area and a drop target for dragged blocks.
|
|
278
|
+
Collapsed toggles hide content visually but keep child blocks in document data.
|
|
279
|
+
|
|
280
|
+
Backspace should not allow content rows to become title rows or adjacent root
|
|
281
|
+
blocks to be absorbed into a toggle. Structural changes must preserve the
|
|
282
|
+
title/content pairing.
|
|
283
|
+
|
|
284
|
+
All direct toggle content blocks render inside one shared content wrapper. Do
|
|
285
|
+
not render a separate bordered content box per child block.
|
|
286
|
+
|
|
287
|
+
Creating a toggle focuses the beginning of the title for both the special block
|
|
288
|
+
action and `>>` shortcut-created toggles.
|
|
289
|
+
|
|
290
|
+
Drag and drop inside toggle content should use the same block movement helpers
|
|
291
|
+
as the rest of the editor. Dropping onto the content wrapper places the dragged
|
|
292
|
+
block inside the toggle; dropping among rows inside the wrapper targets those
|
|
293
|
+
rows first.
|
|
294
|
+
|
|
295
|
+
Toggling collapsed state should preserve scroll position and should never drop
|
|
296
|
+
children.
|
|
297
|
+
|
|
298
|
+
## Block Actions and Drag
|
|
299
|
+
|
|
300
|
+
Every visible block row should have its own block handle when hovered or active,
|
|
301
|
+
including paragraphs inside toggle content and individual checkbox items.
|
|
302
|
+
Empty paragraphs are still visible block rows: they should expose a drag handle
|
|
303
|
+
and, when they are also valid insertion targets, the special block plus control.
|
|
304
|
+
Those controls should sit in separate gutter slots so neither hides the other.
|
|
305
|
+
|
|
306
|
+
Handle visibility and placement:
|
|
307
|
+
|
|
308
|
+
- Deeply nested rows keep their hover lane aligned with their visual row.
|
|
309
|
+
- Checkbox handles use a small raised anchor to align with the checkbox input.
|
|
310
|
+
- Toggle handles use the normal row anchor.
|
|
311
|
+
- Block action menus include copy, cut, delete, and close icon controls.
|
|
312
|
+
- Deleting from the block action menu preserves scroll position.
|
|
313
|
+
|
|
314
|
+
Drag behavior:
|
|
315
|
+
|
|
316
|
+
- Dragging a block moves the whole block subtree.
|
|
317
|
+
- Before and after drops match the target sibling's visual indentation level.
|
|
318
|
+
- Moving the pointer into a compatible indentation lane drops inside the target
|
|
319
|
+
row.
|
|
320
|
+
- Moving the pointer to a shallower indentation lane drops beside the visual
|
|
321
|
+
ancestor.
|
|
322
|
+
- The drop indicator should draw at the indentation level that the drop will
|
|
323
|
+
produce.
|
|
324
|
+
- Toggle content boxes are valid drop targets, but row targets inside them take
|
|
325
|
+
priority.
|
|
326
|
+
- Invalid drag targets show the drop indicator in red. Releasing onto the
|
|
327
|
+
dragged block itself, one of its descendants, or no meaningful target must not
|
|
328
|
+
mutate the document or trigger a content change.
|
|
329
|
+
|
|
330
|
+
## Clipboard and Persistence
|
|
331
|
+
|
|
332
|
+
Clipboard round trips should prefer the structured rich-text encoding when
|
|
333
|
+
available. Plain-text clipboard export should remain readable and compatible
|
|
334
|
+
with common Markdown-like input:
|
|
335
|
+
|
|
336
|
+
- bullet: `- text`
|
|
337
|
+
- ordered: `1. text`
|
|
338
|
+
- unchecked checkbox: `[ ] text`
|
|
339
|
+
- checked checkbox: `[x] text`
|
|
340
|
+
- toggle: `> title`
|
|
341
|
+
|
|
342
|
+
Persistence boundaries must preserve nested `children`, checkbox `checked`, and
|
|
343
|
+
toggle `collapsed`. If the saved block shape changes, update package tests,
|
|
344
|
+
application mappers, and Functions mappers together.
|
|
345
|
+
|
|
346
|
+
Pasting behavior:
|
|
347
|
+
|
|
348
|
+
- Rich-text clipboard payloads should decode back into the same block structure
|
|
349
|
+
when possible.
|
|
350
|
+
- Pasting into an empty editable block replaces that block instead of creating
|
|
351
|
+
an extra blank row.
|
|
352
|
+
- Pasting into an empty toggle content placeholder inserts the pasted blocks as
|
|
353
|
+
toggle children.
|
|
354
|
+
- Plain-text shortcuts such as `[]`, `* `, `1. `, and `>>` should continue to
|
|
355
|
+
work inside toggle content.
|
|
356
|
+
|
|
357
|
+
Typography and whitespace:
|
|
358
|
+
|
|
359
|
+
- Interior repeated spaces and intentional trailing spaces should be preserved
|
|
360
|
+
through editor storage and rendering.
|
|
361
|
+
- Spacer entities inserted by the browser should be dropped when they are only
|
|
362
|
+
editor artifacts.
|
|
363
|
+
- Enter splitting should use browser-visible text offsets, including soft
|
|
364
|
+
breaks and emoji.
|
|
365
|
+
- List-like rows should remain compact vertically.
|
|
366
|
+
|
|
367
|
+
## Transcription
|
|
368
|
+
|
|
369
|
+
Hosts may enable browser-native transcription with `transcriptionEnabled`.
|
|
370
|
+
The editor inserts final transcript text into the current body selection with
|
|
371
|
+
readable spacing. Mobile Safari should show the unavailable state instead of
|
|
372
|
+
starting `webkitSpeechRecognition`, which can leave the browser in a stuck
|
|
373
|
+
recording session.
|
|
374
|
+
|
|
375
|
+
If a host needs the transcription control in external chrome, it should pass
|
|
376
|
+
`transcriptionPortalContainer`. The control must stay React-owned through a
|
|
377
|
+
portal; host components must not move `.bayon-rte-transcription` with direct DOM
|
|
378
|
+
operations such as `appendChild`.
|
|
379
|
+
|
|
380
|
+
## Testing and Build Expectations
|
|
381
|
+
|
|
382
|
+
For behavior changes, add focused tests near the helper or component being
|
|
383
|
+
changed. Prefer pure helper tests for tree operations before DOM integration.
|
|
384
|
+
|
|
385
|
+
Common verification commands:
|
|
386
|
+
|
|
387
|
+
```powershell
|
|
388
|
+
pnpm --filter @bayonai/rich-text-editor test -- --run
|
|
389
|
+
pnpm --filter @bayonai/rich-text-editor lint
|
|
390
|
+
pnpm --filter @bayonai/rich-text-editor build
|
|
391
|
+
pnpm exec tsc --noEmit -p tsconfig.json
|
|
392
|
+
git diff --check
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
The repository tracks package build output. After changing rich-text package
|
|
396
|
+
source, rebuild the package so tracked output stays aligned.
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- Added nested bullet, ordered, checkbox, and toggle block support with
|
|
6
|
+
recursive sanitization and editable/read-only rendering built on recursive
|
|
7
|
+
`ul`/`ol` plus `li` list DOM.
|
|
8
|
+
- Added Tab and Shift+Tab handling for indenting and outdenting nestable rows
|
|
9
|
+
while leaving paragraph, heading, quote, code, divider, and image blocks flat.
|
|
10
|
+
- Added `* ` and `1. ` shortcuts for unordered and ordered rows.
|
|
11
|
+
- Unified Enter behavior across bullet, ordered, checkbox, and toggle rows so
|
|
12
|
+
row splitting uses the caret position consistently.
|
|
13
|
+
- Fixed Enter at the end of title blocks so the following row starts as default
|
|
14
|
+
paragraph text.
|
|
15
|
+
- Fixed ArrowUp in body text so the browser moves to the previous visual line
|
|
16
|
+
instead of jumping focus to the title.
|
|
17
|
+
- Removed top-level list indentation and nested list borders while keeping
|
|
18
|
+
indentation on child list containers.
|
|
19
|
+
- Kept empty special-created list and toggle rows as placeholder rows instead
|
|
20
|
+
of selected placeholder text.
|
|
21
|
+
- Added editable toggle collapse and expand controls that preserve child
|
|
22
|
+
content while storing collapsed state on the toggle block.
|
|
23
|
+
- Aligned block action handles with indented nested rows.
|
|
24
|
+
- Added an editor operation boundary with document and selection snapshots to
|
|
25
|
+
prepare semantic editor actions for future undo/redo support.
|
|
26
|
+
|
|
5
27
|
## 0.1.2 - 2026-06-24
|
|
6
28
|
|
|
7
29
|
- Added block-level action handles for non-empty editor blocks.
|
package/README.md
CHANGED
|
@@ -14,10 +14,17 @@ The package has no Next.js, Firebase, or application-domain dependencies.
|
|
|
14
14
|
Hosts provide controlled document values, optional metadata, persistence
|
|
15
15
|
callbacks, and navigation adapters.
|
|
16
16
|
|
|
17
|
+
## Maintainer Notes
|
|
18
|
+
|
|
19
|
+
Internal structure and behavior contracts are documented in
|
|
20
|
+
[`BEHAVIOR.md`](./BEHAVIOR.md). Read that file before changing tree-row,
|
|
21
|
+
toggle, cursor, clipboard, or persistence behavior.
|
|
22
|
+
|
|
17
23
|
## Editor Capabilities
|
|
18
24
|
|
|
19
25
|
- Native rich-text body editing with headings, quotes, code, dividers,
|
|
20
|
-
checkboxes, image placeholders, and
|
|
26
|
+
bullets, ordered bullets, checkboxes, toggles, image placeholders, and
|
|
27
|
+
Markdown-compatible text storage.
|
|
21
28
|
- Empty, undesignated blocks expose the special block insertion tool.
|
|
22
29
|
- Non-empty top-level blocks expose a compact gutter handle for block actions.
|
|
23
30
|
- Dragging a block handle reorders blocks with a visible drop indicator.
|
|
@@ -26,12 +33,24 @@ callbacks, and navigation adapters.
|
|
|
26
33
|
block only after the clipboard write succeeds.
|
|
27
34
|
- Deleting the last block preserves one empty paragraph so the editor remains
|
|
28
35
|
writable.
|
|
29
|
-
- Checkbox rows can be
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
36
|
+
- Checkbox rows can be created with the `[]` shortcut, reordered individually,
|
|
37
|
+
copied, cut, and deleted. Pressing Enter from a checkbox creates the next
|
|
38
|
+
checkbox, and Backspace at the start of a checkbox row converts that row back
|
|
39
|
+
to a paragraph.
|
|
40
|
+
- Bullet rows can be created with the `* ` shortcut, and ordered rows can be
|
|
41
|
+
created with the `1. ` shortcut. Empty list-like rows show placeholders
|
|
42
|
+
instead of selecting placeholder text.
|
|
43
|
+
- Bullet, ordered, checkbox, and toggle rows can hold nested children. Pressing
|
|
44
|
+
Tab indents a nestable row under the previous compatible row, and Shift+Tab
|
|
45
|
+
promotes it one level. Paragraph, heading, quote, code, divider, and image
|
|
46
|
+
blocks remain flat. Editable and read-only nested rows render as recursive
|
|
47
|
+
`ul`/`ol` plus `li` trees so list-like content keeps semantic list structure.
|
|
48
|
+
- Toggle rows render as collapsible sections in read-only output and preserve
|
|
49
|
+
their collapsed state in the stored block. Editable toggles can also be
|
|
50
|
+
collapsed and expanded without dropping child content.
|
|
33
51
|
- Browser-native transcription can be enabled by hosts with the editor's
|
|
34
52
|
`transcriptionEnabled` option. It defaults to enabled, uses the browser
|
|
35
53
|
`SpeechRecognition` or `webkitSpeechRecognition` API when available, inserts
|
|
36
54
|
final transcript text into the current editor body, and never uploads or
|
|
37
|
-
stores audio.
|
|
55
|
+
stores audio. Mobile Safari is treated as unavailable because its prefixed
|
|
56
|
+
recognition sessions can get stuck and leave the editor unusable.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type NestableRichTextBlock } from "./blockTypes";
|
|
2
|
+
import type { RichTextBlock, RichTextBlockPath } from "./types";
|
|
3
|
+
export declare function findBlockPath(blocks: RichTextBlock[], blockId: string): RichTextBlockPath | null;
|
|
4
|
+
export declare function getBlockAtPath(blocks: RichTextBlock[], path: RichTextBlockPath): RichTextBlock | null;
|
|
5
|
+
export declare function getBlocksAtPath(blocks: RichTextBlock[], path: RichTextBlockPath): RichTextBlock[] | null;
|
|
6
|
+
export declare function removeBlockAtPath(blocks: RichTextBlock[], path: RichTextBlockPath): {
|
|
7
|
+
block: RichTextBlock;
|
|
8
|
+
blocks: RichTextBlock[];
|
|
9
|
+
} | null;
|
|
10
|
+
export declare function updateBlockAtPath(blocks: RichTextBlock[], path: RichTextBlockPath, updater: (block: RichTextBlock) => RichTextBlock): RichTextBlock[];
|
|
11
|
+
export declare function insertBlockAtPath(blocks: RichTextBlock[], parentPath: RichTextBlockPath, index: number, insertedBlocks: RichTextBlock[]): RichTextBlock[];
|
|
12
|
+
export declare function pathContains(parentPath: RichTextBlockPath, childPath: RichTextBlockPath): boolean;
|
|
13
|
+
export declare function withNestableBlockChildren(block: NestableRichTextBlock, children: RichTextBlock[]): NestableRichTextBlock;
|
|
14
|
+
export declare function withBlockChildren(block: RichTextBlock, children: RichTextBlock[]): RichTextBlock;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { isNestableRichTextBlock } from "./blockTypes.js";
|
|
2
|
+
export function findBlockPath(blocks, blockId) {
|
|
3
|
+
for (let index = 0; index < blocks.length; index += 1) {
|
|
4
|
+
const block = blocks[index];
|
|
5
|
+
if (!block) {
|
|
6
|
+
continue;
|
|
7
|
+
}
|
|
8
|
+
if (block.id === blockId) {
|
|
9
|
+
return [index];
|
|
10
|
+
}
|
|
11
|
+
if (isNestableRichTextBlock(block) && block.children?.length) {
|
|
12
|
+
const childPath = findBlockPath(block.children, blockId);
|
|
13
|
+
if (childPath) {
|
|
14
|
+
return [index, ...childPath];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
export function getBlockAtPath(blocks, path) {
|
|
21
|
+
let currentBlocks = blocks;
|
|
22
|
+
let currentBlock;
|
|
23
|
+
for (const index of path) {
|
|
24
|
+
currentBlock = currentBlocks[index];
|
|
25
|
+
if (!currentBlock) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
currentBlocks = isNestableRichTextBlock(currentBlock)
|
|
29
|
+
? currentBlock.children ?? []
|
|
30
|
+
: [];
|
|
31
|
+
}
|
|
32
|
+
return currentBlock ?? null;
|
|
33
|
+
}
|
|
34
|
+
export function getBlocksAtPath(blocks, path) {
|
|
35
|
+
if (path.length === 0) {
|
|
36
|
+
return blocks;
|
|
37
|
+
}
|
|
38
|
+
const parent = getBlockAtPath(blocks, path);
|
|
39
|
+
return isNestableRichTextBlock(parent) ? parent.children ?? [] : null;
|
|
40
|
+
}
|
|
41
|
+
export function removeBlockAtPath(blocks, path) {
|
|
42
|
+
const [index, ...rest] = path;
|
|
43
|
+
if (index === undefined) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (rest.length === 0) {
|
|
47
|
+
const block = blocks[index];
|
|
48
|
+
if (!block) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
block,
|
|
53
|
+
blocks: [...blocks.slice(0, index), ...blocks.slice(index + 1)],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const parent = blocks[index];
|
|
57
|
+
if (!isNestableRichTextBlock(parent)) {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const removed = removeBlockAtPath(parent.children ?? [], rest);
|
|
61
|
+
if (!removed) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const updatedParent = withNestableBlockChildren(parent, removed.blocks);
|
|
65
|
+
return {
|
|
66
|
+
block: removed.block,
|
|
67
|
+
blocks: [
|
|
68
|
+
...blocks.slice(0, index),
|
|
69
|
+
updatedParent,
|
|
70
|
+
...blocks.slice(index + 1),
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
export function updateBlockAtPath(blocks, path, updater) {
|
|
75
|
+
const [index, ...rest] = path;
|
|
76
|
+
if (index === undefined) {
|
|
77
|
+
return blocks;
|
|
78
|
+
}
|
|
79
|
+
const block = blocks[index];
|
|
80
|
+
if (!block) {
|
|
81
|
+
return blocks;
|
|
82
|
+
}
|
|
83
|
+
const updatedBlock = rest.length === 0
|
|
84
|
+
? updater(block)
|
|
85
|
+
: isNestableRichTextBlock(block)
|
|
86
|
+
? withNestableBlockChildren(block, updateBlockAtPath(block.children ?? [], rest, updater))
|
|
87
|
+
: block;
|
|
88
|
+
return [...blocks.slice(0, index), updatedBlock, ...blocks.slice(index + 1)];
|
|
89
|
+
}
|
|
90
|
+
export function insertBlockAtPath(blocks, parentPath, index, insertedBlocks) {
|
|
91
|
+
if (parentPath.length === 0) {
|
|
92
|
+
return [
|
|
93
|
+
...blocks.slice(0, index),
|
|
94
|
+
...insertedBlocks,
|
|
95
|
+
...blocks.slice(index),
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
return updateBlockAtPath(blocks, parentPath, (parent) => {
|
|
99
|
+
if (!isNestableRichTextBlock(parent)) {
|
|
100
|
+
return parent;
|
|
101
|
+
}
|
|
102
|
+
return withNestableBlockChildren(parent, [
|
|
103
|
+
...(parent.children ?? []).slice(0, index),
|
|
104
|
+
...insertedBlocks,
|
|
105
|
+
...(parent.children ?? []).slice(index),
|
|
106
|
+
]);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
export function pathContains(parentPath, childPath) {
|
|
110
|
+
return parentPath.every((index, pathIndex) => childPath[pathIndex] === index);
|
|
111
|
+
}
|
|
112
|
+
export function withNestableBlockChildren(block, children) {
|
|
113
|
+
if (block.type === "toggle") {
|
|
114
|
+
return { ...block, children };
|
|
115
|
+
}
|
|
116
|
+
if (children.length > 0) {
|
|
117
|
+
return { ...block, children };
|
|
118
|
+
}
|
|
119
|
+
const { children: _children, ...rest } = block;
|
|
120
|
+
return rest;
|
|
121
|
+
}
|
|
122
|
+
export function withBlockChildren(block, children) {
|
|
123
|
+
return isNestableRichTextBlock(block)
|
|
124
|
+
? withNestableBlockChildren(block, children)
|
|
125
|
+
: block;
|
|
126
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { RichTextBlock } from "./types";
|
|
2
|
+
export type NestableRichTextBlock = Extract<RichTextBlock, {
|
|
3
|
+
type: "bullet" | "ordered" | "checkbox" | "toggle";
|
|
4
|
+
}>;
|
|
5
|
+
export declare const NESTABLE_RICH_TEXT_BLOCK_TYPES: Set<"toggle" | "checkbox" | "ordered" | "bullet">;
|
|
6
|
+
export declare function isNestableRichTextBlock(block: RichTextBlock | null | undefined): block is NestableRichTextBlock;
|