@blue-patterns/richtext 0.0.0-stage → 0.7.18
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 +275 -0
- package/LICENSE +22 -0
- package/README.md +88 -2
- package/astryx.integration.mjs +11 -0
- package/blocks/RichTextEditorShowcase.template.mjs +13 -0
- package/blocks/RichTextEditorShowcase.tsx +19 -0
- package/dist/RichTextEditor.d.ts +282 -0
- package/dist/RichTextEditor.d.ts.map +1 -0
- package/dist/RichTextEditor.js +657 -0
- package/dist/RichTextEditorAutoLinkPlugin.d.ts +83 -0
- package/dist/RichTextEditorAutoLinkPlugin.d.ts.map +1 -0
- package/dist/RichTextEditorAutoLinkPlugin.js +99 -0
- package/dist/RichTextEditorToolbar.d.ts +186 -0
- package/dist/RichTextEditorToolbar.d.ts.map +1 -0
- package/dist/RichTextEditorToolbar.js +688 -0
- package/dist/RichTextView.d.ts +92 -0
- package/dist/RichTextView.d.ts.map +1 -0
- package/dist/RichTextView.js +267 -0
- package/dist/codeLanguages.d.ts +14 -0
- package/dist/codeLanguages.d.ts.map +1 -0
- package/dist/codeLanguages.js +34 -0
- package/dist/codeLineTokens.d.ts +53 -0
- package/dist/codeLineTokens.d.ts.map +1 -0
- package/dist/codeLineTokens.js +188 -0
- package/dist/editorNodes.d.ts +8 -0
- package/dist/editorNodes.d.ts.map +1 -0
- package/dist/editorNodes.js +30 -0
- package/dist/editorTheme.d.ts +7 -0
- package/dist/editorTheme.d.ts.map +1 -0
- package/dist/editorTheme.js +328 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/internal/CodeBackspacePlugin.d.ts +2 -0
- package/dist/internal/CodeBackspacePlugin.d.ts.map +1 -0
- package/dist/internal/CodeBackspacePlugin.js +70 -0
- package/dist/internal/CodeBlockHeaderPlugin.d.ts +11 -0
- package/dist/internal/CodeBlockHeaderPlugin.d.ts.map +1 -0
- package/dist/internal/CodeBlockHeaderPlugin.js +204 -0
- package/dist/internal/CodePastePlugin.d.ts +5 -0
- package/dist/internal/CodePastePlugin.d.ts.map +1 -0
- package/dist/internal/CodePastePlugin.js +104 -0
- package/dist/internal/CodeSyntaxPlugin.d.ts +8 -0
- package/dist/internal/CodeSyntaxPlugin.d.ts.map +1 -0
- package/dist/internal/CodeSyntaxPlugin.js +397 -0
- package/dist/internal/CodeTabPlugin.d.ts +2 -0
- package/dist/internal/CodeTabPlugin.d.ts.map +1 -0
- package/dist/internal/CodeTabPlugin.js +256 -0
- package/dist/internal/DisabledLinkGuardPlugin.d.ts +4 -0
- package/dist/internal/DisabledLinkGuardPlugin.d.ts.map +1 -0
- package/dist/internal/DisabledLinkGuardPlugin.js +93 -0
- package/dist/internal/ImageSourceCaretPlugin.d.ts +3 -0
- package/dist/internal/ImageSourceCaretPlugin.d.ts.map +1 -0
- package/dist/internal/ImageSourceCaretPlugin.js +105 -0
- package/dist/internal/ListItemShortcutsPlugin.d.ts +6 -0
- package/dist/internal/ListItemShortcutsPlugin.d.ts.map +1 -0
- package/dist/internal/ListItemShortcutsPlugin.js +314 -0
- package/dist/internal/MarkdownExtensionsPlugin.d.ts +17 -0
- package/dist/internal/MarkdownExtensionsPlugin.d.ts.map +1 -0
- package/dist/internal/MarkdownExtensionsPlugin.js +185 -0
- package/dist/internal/SelectAllScopePlugin.d.ts +2 -0
- package/dist/internal/SelectAllScopePlugin.d.ts.map +1 -0
- package/dist/internal/SelectAllScopePlugin.js +67 -0
- package/dist/internal/TableColumnFloorPlugin.d.ts +12 -0
- package/dist/internal/TableColumnFloorPlugin.d.ts.map +1 -0
- package/dist/internal/TableColumnFloorPlugin.js +84 -0
- package/dist/internal/TablePastePlugin.d.ts +9 -0
- package/dist/internal/TablePastePlugin.d.ts.map +1 -0
- package/dist/internal/TablePastePlugin.js +183 -0
- package/dist/internal/TableScrollRegionPlugin.d.ts +9 -0
- package/dist/internal/TableScrollRegionPlugin.d.ts.map +1 -0
- package/dist/internal/TableScrollRegionPlugin.js +95 -0
- package/dist/internal/TaskCheckboxPlugin.d.ts +7 -0
- package/dist/internal/TaskCheckboxPlugin.d.ts.map +1 -0
- package/dist/internal/TaskCheckboxPlugin.js +193 -0
- package/dist/internal/ToolbarLinkPopover.d.ts +74 -0
- package/dist/internal/ToolbarLinkPopover.d.ts.map +1 -0
- package/dist/internal/ToolbarLinkPopover.js +292 -0
- package/dist/linkEditing.d.ts +6 -0
- package/dist/linkEditing.d.ts.map +1 -0
- package/dist/linkEditing.js +20 -0
- package/dist/linkForm.d.ts +28 -0
- package/dist/linkForm.d.ts.map +1 -0
- package/dist/linkForm.js +137 -0
- package/dist/linkUtils.d.ts +27 -0
- package/dist/linkUtils.d.ts.map +1 -0
- package/dist/linkUtils.js +101 -0
- package/dist/markdown.d.ts +17 -0
- package/dist/markdown.d.ts.map +1 -0
- package/dist/markdown.js +17 -0
- package/dist/markdownCharacterReferences.d.ts +135 -0
- package/dist/markdownCharacterReferences.d.ts.map +1 -0
- package/dist/markdownCharacterReferences.js +1127 -0
- package/dist/markdownCodeFence.d.ts +4 -0
- package/dist/markdownCodeFence.d.ts.map +1 -0
- package/dist/markdownCodeFence.js +167 -0
- package/dist/markdownExtensionNode.d.ts +88 -0
- package/dist/markdownExtensionNode.d.ts.map +1 -0
- package/dist/markdownExtensionNode.js +248 -0
- package/dist/markdownExtensions.d.ts +115 -0
- package/dist/markdownExtensions.d.ts.map +1 -0
- package/dist/markdownExtensions.js +392 -0
- package/dist/markdownHardLineBreak.d.ts +25 -0
- package/dist/markdownHardLineBreak.d.ts.map +1 -0
- package/dist/markdownHardLineBreak.js +59 -0
- package/dist/markdownHeading.d.ts +20 -0
- package/dist/markdownHeading.d.ts.map +1 -0
- package/dist/markdownHeading.js +30 -0
- package/dist/markdownLink.d.ts +5 -0
- package/dist/markdownLink.d.ts.map +1 -0
- package/dist/markdownLink.js +80 -0
- package/dist/markdownListExport.d.ts +3 -0
- package/dist/markdownListExport.d.ts.map +1 -0
- package/dist/markdownListExport.js +68 -0
- package/dist/markdownListIndentation.d.ts +8 -0
- package/dist/markdownListIndentation.d.ts.map +1 -0
- package/dist/markdownListIndentation.js +101 -0
- package/dist/markdownOrderedList.d.ts +3 -0
- package/dist/markdownOrderedList.d.ts.map +1 -0
- package/dist/markdownOrderedList.js +51 -0
- package/dist/markdownQuote.d.ts +22 -0
- package/dist/markdownQuote.d.ts.map +1 -0
- package/dist/markdownQuote.js +32 -0
- package/dist/markdownSerializers.d.ts +49 -0
- package/dist/markdownSerializers.d.ts.map +1 -0
- package/dist/markdownSerializers.js +83 -0
- package/dist/markdownSource.d.ts +71 -0
- package/dist/markdownSource.d.ts.map +1 -0
- package/dist/markdownSource.js +804 -0
- package/dist/markdownSpaceRuns.d.ts +24 -0
- package/dist/markdownSpaceRuns.d.ts.map +1 -0
- package/dist/markdownSpaceRuns.js +147 -0
- package/dist/markdownTable.d.ts +35 -0
- package/dist/markdownTable.d.ts.map +1 -0
- package/dist/markdownTable.js +227 -0
- package/dist/markdownTaskList.d.ts +48 -0
- package/dist/markdownTaskList.d.ts.map +1 -0
- package/dist/markdownTaskList.js +166 -0
- package/dist/markdownThematicBreak.d.ts +3 -0
- package/dist/markdownThematicBreak.d.ts.map +1 -0
- package/dist/markdownThematicBreak.js +55 -0
- package/dist/privateAttributes/layerPortalBoundary.stylex.d.ts +12 -0
- package/dist/privateAttributes/layerPortalBoundary.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/layerPortalBoundary.stylex.js +16 -0
- package/dist/richtext.css +213 -0
- package/dist/textSemantics.d.ts +3 -0
- package/dist/textSemantics.d.ts.map +1 -0
- package/dist/textSemantics.js +87 -0
- package/package.json +158 -3
- package/src/RichTextEditor.doc.mjs +308 -0
- package/src/RichTextEditor.tsx +1109 -0
- package/src/RichTextEditorAutoLinkPlugin.doc.mjs +43 -0
- package/src/RichTextEditorAutoLinkPlugin.tsx +126 -0
- package/src/RichTextEditorToolbar.doc.mjs +92 -0
- package/src/RichTextEditorToolbar.tsx +871 -0
- package/src/RichTextView.doc.mjs +101 -0
- package/src/RichTextView.tsx +351 -0
- package/src/codeLanguages.ts +42 -0
- package/src/codeLineTokens.ts +241 -0
- package/src/doc-mjs.d.ts +23 -0
- package/src/editorNodes.ts +44 -0
- package/src/editorTheme.ts +550 -0
- package/src/index.ts +59 -0
- package/src/internal/CodeBackspacePlugin.tsx +96 -0
- package/src/internal/CodeBlockHeaderPlugin.tsx +282 -0
- package/src/internal/CodePastePlugin.tsx +144 -0
- package/src/internal/CodeSyntaxPlugin.tsx +493 -0
- package/src/internal/CodeTabPlugin.tsx +298 -0
- package/src/internal/DisabledLinkGuardPlugin.tsx +93 -0
- package/src/internal/ImageSourceCaretPlugin.tsx +159 -0
- package/src/internal/ListItemShortcutsPlugin.tsx +404 -0
- package/src/internal/MarkdownExtensionsPlugin.tsx +289 -0
- package/src/internal/SelectAllScopePlugin.tsx +86 -0
- package/src/internal/TableColumnFloorPlugin.tsx +99 -0
- package/src/internal/TablePastePlugin.tsx +223 -0
- package/src/internal/TableScrollRegionPlugin.tsx +107 -0
- package/src/internal/TaskCheckboxPlugin.tsx +246 -0
- package/src/internal/ToolbarLinkPopover.tsx +392 -0
- package/src/linkEditing.ts +20 -0
- package/src/linkForm.tsx +156 -0
- package/src/linkUtils.ts +106 -0
- package/src/markdown.ts +25 -0
- package/src/markdownCharacterReferences.ts +1137 -0
- package/src/markdownCodeFence.ts +216 -0
- package/src/markdownExtensionNode.ts +339 -0
- package/src/markdownExtensions.ts +496 -0
- package/src/markdownHardLineBreak.ts +73 -0
- package/src/markdownHeading.ts +33 -0
- package/src/markdownLink.ts +86 -0
- package/src/markdownListExport.ts +85 -0
- package/src/markdownListIndentation.ts +105 -0
- package/src/markdownOrderedList.ts +60 -0
- package/src/markdownQuote.ts +35 -0
- package/src/markdownSerializers.ts +108 -0
- package/src/markdownSource.ts +1045 -0
- package/src/markdownSpaceRuns.ts +164 -0
- package/src/markdownTable.ts +309 -0
- package/src/markdownTaskList.ts +222 -0
- package/src/markdownThematicBreak.ts +66 -0
- package/src/privateAttributes/layerPortalBoundary.stylex.ts +18 -0
- package/src/textSemantics.ts +104 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@blue-patterns/cli/authoring').ComponentDoc} */
|
|
4
|
+
|
|
5
|
+
export const docs = {
|
|
6
|
+
name: 'RichTextEditor',
|
|
7
|
+
displayName: 'Rich Text Editor',
|
|
8
|
+
category: 'Form Controls',
|
|
9
|
+
keywords: [
|
|
10
|
+
'richtext',
|
|
11
|
+
'rich text',
|
|
12
|
+
'wysiwyg',
|
|
13
|
+
'editor',
|
|
14
|
+
'lexical',
|
|
15
|
+
'formatting',
|
|
16
|
+
'contenteditable',
|
|
17
|
+
'prose',
|
|
18
|
+
],
|
|
19
|
+
props: [
|
|
20
|
+
{
|
|
21
|
+
name: 'label',
|
|
22
|
+
type: 'string',
|
|
23
|
+
description:
|
|
24
|
+
'Label text for the editor. Always rendered for accessibility.',
|
|
25
|
+
required: true,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'isLabelHidden',
|
|
29
|
+
type: 'boolean',
|
|
30
|
+
description:
|
|
31
|
+
'Visually hide the label (still accessible to screen readers).',
|
|
32
|
+
default: 'false',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'description',
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'Description text displayed between the label and editor.',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
name: 'isOptional',
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
description:
|
|
43
|
+
'Displays an "Optional" indicator next to the label. Mutually exclusive with isRequired.',
|
|
44
|
+
default: 'false',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'isRequired',
|
|
48
|
+
type: 'boolean',
|
|
49
|
+
description:
|
|
50
|
+
'Displays a "Required" indicator next to the label and sets aria-required on the editable surface. Mutually exclusive with isOptional.',
|
|
51
|
+
default: 'false',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
name: 'labelTooltip',
|
|
55
|
+
type: 'string',
|
|
56
|
+
description:
|
|
57
|
+
'Tooltip text displayed in an info icon at the end of the label.',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: 'defaultValue',
|
|
61
|
+
type: 'string',
|
|
62
|
+
description:
|
|
63
|
+
'Initial serialized editor state (JSON string from editorState.toJSON()). Read once on mount; the editor is uncontrolled.',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'onChange',
|
|
67
|
+
type: '(editorState: EditorState, editor: LexicalEditor) => void',
|
|
68
|
+
description:
|
|
69
|
+
'Fired when content changes. Serialize with editorState.toJSON() for persistence.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'placeholder',
|
|
73
|
+
type: 'string',
|
|
74
|
+
description:
|
|
75
|
+
'Placeholder text shown when the editor is empty. Uses the same responsive body typography and text inset as the editable content.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'isReadOnly',
|
|
79
|
+
type: 'boolean',
|
|
80
|
+
description:
|
|
81
|
+
'Whether the editor is read-only (non-editable). Content stays at full opacity, in the tab order, and announced as read-only. Takes effect when changed after mount.',
|
|
82
|
+
default: 'false',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'isDisabled',
|
|
86
|
+
type: 'boolean',
|
|
87
|
+
description:
|
|
88
|
+
'Whether the editor is disabled: non-editable, dimmed, out of the tab order, and announced as disabled. Its links do not navigate and are not tab stops. Wins when isReadOnly is also set. Takes effect when changed after mount.',
|
|
89
|
+
default: 'false',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'status',
|
|
93
|
+
type: '{ type: "warning" | "error" | "success"; message?: string }',
|
|
94
|
+
description:
|
|
95
|
+
'Validation status. Shows a colored border and status icon; an optional message follows the statusVariant placement. Error also sets aria-invalid.',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'statusVariant',
|
|
99
|
+
type: "'attached' | 'detached' | 'tooltip'",
|
|
100
|
+
description:
|
|
101
|
+
'How the status is presented: attached keeps the icon in the editor and overlaps the message below; detached floats the message below with its own icon; tooltip hides the message box and reveals it from the focusable in-editor status icon.',
|
|
102
|
+
default: "'attached'",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'size',
|
|
106
|
+
type: "'sm' | 'md' | 'lg'",
|
|
107
|
+
description: 'The size of the editor, affecting internal padding.',
|
|
108
|
+
default: "'md'",
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'nodes',
|
|
112
|
+
type: 'ReadonlyArray<Klass<LexicalNode>>',
|
|
113
|
+
description:
|
|
114
|
+
'Additional Lexical nodes to register beyond the default OSS set (Heading, Quote, List, Link, Code). Extension point for custom nodes (mentions, images) without forking.',
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'toolbar',
|
|
118
|
+
type: 'ReactNode',
|
|
119
|
+
description:
|
|
120
|
+
'Toolbar content rendered edge-to-edge at the top of the field, before the padded editing surface. Pass RichTextEditorToolbar here for correct visual and keyboard order.',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'plugins',
|
|
124
|
+
type: 'ReactNode',
|
|
125
|
+
description:
|
|
126
|
+
'Additional Lexical plugins rendered inside the composer. Compose mentions, autolink, and other editor behavior on top of the base editor.',
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'hasMarkdownShortcuts',
|
|
130
|
+
type: 'boolean',
|
|
131
|
+
description:
|
|
132
|
+
'Enable Markdown shortcut typing (e.g. "# " for a heading). Uses the transformers prop (defaults to thematic breaks, the standard @lexical/markdown transformers, hard line breaks, and GFM tables). The same markers work at the start of a list item: "- ", "1. ", or "[ ] " switches its list between bullets, numbers, and tasks (so "- [ ] " typed in a paragraph makes a task item), and "# " or "> " takes a top-level item out of its list as a heading or a quote, the rest of the list continuing after it. Each change is one undo step, back to the marker as typed.',
|
|
133
|
+
default: 'true',
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: 'markdownExtensions',
|
|
137
|
+
type: 'ReadonlyArray<RichTextMarkdownExtension>',
|
|
138
|
+
description:
|
|
139
|
+
'Markdown plugins whose nodes the editor draws, each adopted with createRichTextExtension. A plugin node renders exactly as core Markdown renders it and edits as one unit; a node whose plugin is not given here shows its source. Pass the extensions the content was converted with, and create them in a client module. RichTextView takes the same prop.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'codeLanguages',
|
|
143
|
+
type: 'ReadonlyArray<CodeLanguage>',
|
|
144
|
+
description:
|
|
145
|
+
'CodeBlock language values a fence may name, each imported on its own (e.g. graphql from @blue-patterns/core/CodeBlock/languages/graphql) or made with createCodeLanguage. Pass the same list as Markdown codeLanguages so a matched fence paints and labels the same while reading and editing. RichTextView takes the same prop.',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: 'transformers',
|
|
149
|
+
type: 'ReadonlyArray<Transformer>',
|
|
150
|
+
description:
|
|
151
|
+
'Markdown transformers: the single source of truth for markdown behaviour. Defaults to thematic breaks, the standard @lexical/markdown TRANSFORMERS (with each nested list written at the content column of the item above it, so it reads back nested), hard line breaks (a line break typed with Shift+Enter exports as a backslash before the line ending, so it reads back as a line break), and GFM tables; a custom array replaces the default, thematic breaks and tables included. In Lexical the same array drives all three markdown operations (shortcut typing, markdown->state import, state->markdown export); this prop wires shortcut typing today and is the intended input for the serialization APIs added in later phases. Pass a custom array to support additional node types (e.g. transformers layered in via the nodes extension point) consistently across all three. Shortcut typing is only applied when hasMarkdownShortcuts is true.',
|
|
152
|
+
default: 'TRANSFORMERS',
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'hasAutoFocus',
|
|
156
|
+
type: 'boolean',
|
|
157
|
+
description: 'Automatically focus the editor on mount.',
|
|
158
|
+
default: 'false',
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'tabEscapeHint',
|
|
162
|
+
type: 'string',
|
|
163
|
+
description:
|
|
164
|
+
'Screen-reader hint describing how to move focus out of the editor, since Tab is bound to indentation (press Escape, then Tab). Visually hidden, wired via aria-describedby. Translated for the active locale; override to change the text, or pass "" to omit.',
|
|
165
|
+
default:
|
|
166
|
+
"'Press Escape then Tab to move focus out of the editor.' (translated)",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'maxLength',
|
|
170
|
+
type: 'number',
|
|
171
|
+
description:
|
|
172
|
+
'Maximum number of characters. When set, a character counter (current/max) is displayed below the editor. Like TextArea, does not enforce the limit natively; the counter shows error styling when the plain-text length exceeds the limit.',
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'width',
|
|
176
|
+
type: 'number | string',
|
|
177
|
+
description:
|
|
178
|
+
'Width of the field. Numbers are pixels, strings used as-is (e.g. "100%").',
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'minHeight',
|
|
182
|
+
type: 'SizeValue',
|
|
183
|
+
description:
|
|
184
|
+
'Minimum height of the editable content surface. Numbers are pixels; strings are used as CSS lengths. Content continues growing beyond this height.',
|
|
185
|
+
default: "'4.5rem'",
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: 'namespace',
|
|
189
|
+
type: 'string',
|
|
190
|
+
description: 'The Lexical composer namespace, used for editor identity.',
|
|
191
|
+
default: "'astryx-editor'",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'xstyle',
|
|
195
|
+
type: 'StyleXStyles',
|
|
196
|
+
description:
|
|
197
|
+
'StyleX styles for layout customization. Must be a stylex.create() value, not an inline style object.',
|
|
198
|
+
},
|
|
199
|
+
],
|
|
200
|
+
theming: {
|
|
201
|
+
targets: [
|
|
202
|
+
{
|
|
203
|
+
className: 'astryx-rich-text-editor',
|
|
204
|
+
// RichTextEditor.tsx reflects both through
|
|
205
|
+
// themeProps('rich-text-editor', {size, status}) as `data-size` and
|
|
206
|
+
// `data-status`, the same axes TextArea declares for the input visuals
|
|
207
|
+
// this field container shares.
|
|
208
|
+
visualProps: ['size', 'status'],
|
|
209
|
+
},
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
usage: {
|
|
213
|
+
description:
|
|
214
|
+
'A WYSIWYG rich-text editor built on Lexical, styled with Astryx design tokens. Its field container shares TextArea input visuals for the resting border, hover ring, focus-within ring, disabled state, and status colors. Ships from @blue-patterns/richtext; lexical and @lexical/* are optional peer dependencies. The editor is deliberately minimal and extensible: pass toolbar, nodes, and plugins to layer richer behaviour (formatting, mentions, hover cards) on top without forking. Use RichTextView to render serialized content read-only.',
|
|
215
|
+
bestPractices: [
|
|
216
|
+
{
|
|
217
|
+
guidance: true,
|
|
218
|
+
description:
|
|
219
|
+
'RichText is built on Lexical and exposes it: nodes and transformers take Lexical types, plugins takes Lexical React plugins, and onChange and getEditorState() report Lexical types. Install the lexical and @lexical/* peers (optional peers) at the Lexical minor named in the package peer ranges before importing from @blue-patterns/richtext.',
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
guidance: true,
|
|
223
|
+
description:
|
|
224
|
+
'Persist content by serializing editorState.toJSON() in onChange; rehydrate via defaultValue / RichTextView value.',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
guidance: true,
|
|
228
|
+
description:
|
|
229
|
+
'Register custom node types via the nodes prop on BOTH the editor and the RichTextView so serialized content round-trips.',
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
guidance: true,
|
|
233
|
+
description:
|
|
234
|
+
'Use a ref (RichTextEditorRef) to imperatively focus(), clear(), read the state via getEditorState(), serialize to Markdown via getMarkdown() or HTML via getHTML(), or reach the LexicalEditor via getEditor(). The handle is available after mount. getMarkdown() uses the same transformers prop the editor is configured with, and returns imported content as it was written: blocks nobody changed come back byte for byte, and only blocks that were edited or added are written in canonical Markdown. focus() and clear() are no-ops when the editor is read-only or disabled, and clear() resets to a single empty paragraph.',
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
guidance: true,
|
|
238
|
+
description:
|
|
239
|
+
'GFM pipe tables import, edit, and export by default. The first row renders as header cells, delimiter-row colons set each column alignment, rows shorter than the widest row gain empty cells, and a wide table scrolls inside its own wrapper. Arrow keys move between cells; Tab keeps its editor meaning, so Escape then Tab still leaves the editor. Pasting a table into a table fills its cells one by one from the cell holding the caret, or within the selected cells, adding rows and columns as needed; a cell the paste does not fill keeps its text, and a table pasted with other content goes into the cell as text. Tables export in the canonical form: outer pipes and three-dash delimiters. RichTextView renders the same tables. Install @lexical/table with the other lexical peers.',
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
guidance: true,
|
|
243
|
+
description:
|
|
244
|
+
'In a code block, Tab and Shift+Tab indent and outdent every line the selection touches, by the indent the block already uses: a tab when one of its lines starts with a tab, otherwise the fewest spaces an indented line starts with (2 to 8), otherwise 2 spaces. The selection stays on the same characters, and each press is one undo step. Escape then Tab still moves focus out of the editor.',
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
guidance: true,
|
|
248
|
+
description:
|
|
249
|
+
'Backspace at the very start of a code block turns it into a paragraph holding its lines, each on its own line, with the caret at its start. The code never merges into the block above, so a heading above keeps its own text. Backspace anywhere else in a code block deletes as usual.',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
guidance: true,
|
|
253
|
+
description:
|
|
254
|
+
'Pasting into a code block inserts plain code. Rich content, such as a web page, a word processor document, or blocks copied from another editor, goes in as the plain text on the clipboard; when the clipboard has no plain text, each pasted block goes in as one line of its text. Headings, lists, tables, and formatting never land inside a code block.',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
guidance: true,
|
|
258
|
+
description:
|
|
259
|
+
'Cmd+A (Ctrl+A on other platforms) in a code block or a table cell selects the code or the cell text first; pressing it again selects the whole document. Anywhere else it selects the whole document at once.',
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
guidance: true,
|
|
263
|
+
description:
|
|
264
|
+
'Thematic breaks (---, ***, ___, with or without spaces between) import as horizontal rules and export as ---. A dash line directly under a paragraph line stays with that paragraph, as in Markdown. Click a rule or arrow onto it to select it; Backspace removes it. RichTextView draws the same rules. Install @lexical/extension with the other lexical peers.',
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
guidance: true,
|
|
268
|
+
description:
|
|
269
|
+
'To produce a defaultValue from Markdown without mounting an editor (e.g. on the server), use markdownToEditorStateJSON(markdown). Convert the other way with editorStateJSONToMarkdown(json). Both run headless via @lexical/headless and accept the same transformers/nodes options as the editor. The round trip keeps the Markdown as written: exporting unchanged content returns the input exactly, including indentation, escapes, character references, fence metadata, and constructs the editor shows as plain text, and an edit regenerates only the blocks it changed. On the server or in Node, import them from @blue-patterns/richtext/markdown, which loads no React or client code.',
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
guidance: true,
|
|
273
|
+
description:
|
|
274
|
+
"To recognize a Markdown plugin made with createMarkdownPlugin, adopt it with createRichTextExtension(plugin) and pass the result in the extensions option of markdownToEditorStateJSON. Core's parser finds the plugin's syntax, and each node keeps its exact source, which is what it exports; base Markdown inside plugin source stays part of the node. A plugin that declares a transform is refused with a RichTextExtensionError, and so is a configuration that adopts one plugin twice. Without the extension, plugin syntax stays text. createRichTextExtension and the serializers are also exported from @blue-patterns/richtext/markdown for server code. Pass the same extensions to the markdownExtensions prop of RichTextEditor and RichTextView to draw the nodes: each renders as core Markdown renders it, edits as one unit (the caret steps over it; Backspace, cut, copy, and paste take it whole), and exports its exact source.",
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
guidance: true,
|
|
278
|
+
description:
|
|
279
|
+
'Add a formatting toolbar with toolbar={<RichTextEditorToolbar />}. The dedicated slot places it edge-to-edge at the top of the field, before the padded editing surface, with a RichText-owned 4px control gutter and correct keyboard order. It uses small Astryx Toolbar controls: undo/redo, a divided block-format Selector for paragraphs/headings/lists/quotes, then divided inline ToggleButtons for bold/italic/underline/strikethrough/code and links. The complete action row scrolls horizontally when space is tight, keeping every control directly available without a More menu. The Selector keeps its default adaptive placement. Compose extra controls via endContent.',
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
guidance: true,
|
|
283
|
+
description:
|
|
284
|
+
'Links: the toolbar Link button (on by default; disable with hasLink={false}) and Cmd/Ctrl+K open a non-modal Astryx Popover anchored to the button. It holds one URL field, applied with Enter, and Apply, Remove (when the selection is in a link), and Cancel icon buttons with tooltips. It keeps the Lexical selection while focus is in the field: Escape or Cancel closes it and returns focus and the selection to the editor, and a URL that cannot be linked shows an error under the field, which keeps focus. Pass promptForUrl only when integrating an existing synchronous URL flow; it replaces the popover. Entered URLs are sanitized (only http/https/mailto/tel are written; javascript:/data: are rejected). Links open in a new tab by default: target=_blank and rel=noopener noreferrer are written into the link node data; set hasNewTabLinks={false} for same-tab links. The toolbar takes base props and passes them to the Astryx Toolbar it renders: ref, className, style and xstyle on its root; data-*, other aria-* attributes and DOM handlers on the toolbar element (name it with label, not aria-label).',
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
guidance: true,
|
|
288
|
+
description:
|
|
289
|
+
'Auto-linking: render RichTextEditorAutoLinkPlugin in the plugins slot to turn typed/pasted URLs and emails into links automatically. Created links open in a new tab by default (target=_blank, rel=noopener noreferrer, baked into the node). Pass matchers to recognize additional patterns (build them with createLinkMatcherWithRegExp).',
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
guidance: true,
|
|
293
|
+
description:
|
|
294
|
+
"The toolbar's glyphs are themeable. Each control resolves its icon from the core icon registry under a stable richtext:* key (see RICHTEXT_ICON_KEYS), falling back to a bundled inline SVG. A theme restyles any glyph without forking the toolbar by naming its key: defineTheme({icons: {'richtext:bold': <MyBoldIcon />}}). Undo and redo mirror under right-to-left direction, whichever glyph draws them.",
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
guidance: true,
|
|
298
|
+
description:
|
|
299
|
+
"To switch between reading (Markdown or RichTextView) and editing the same document in place, give the editor's field its inset and border outside the reading width, so every paragraph keeps its line count across the switch. Define the frame with stylex.create, for example const inPlace = stylex.create({editorFrame: {marginInline: `calc(-1 * (${spacingVars['--spacing-2']} + ${borderVars['--border-width']}))`}}), with spacingVars and borderVars from @blue-patterns/core/theme/tokens.stylex, and pass xstyle={inPlace.editorFrame} to the RichTextEditor. The recipe assumes no status: a status icon reserves another spacing-6 at the inline end. The field then extends past the column on both sides; leave room for it in the surrounding padding. A standalone editor in a form keeps its default width, so its border lines up with the fields around it.",
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
guidance: false,
|
|
303
|
+
description:
|
|
304
|
+
'Use for single-line input or plain text; use TextInput or TextArea for those.',
|
|
305
|
+
},
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
};
|