Replies: 5 comments 5 replies
-
My use case (in case someone knows what the right pattern would be here): I want to make an editable transcript. I have word-level timestamps for words in a transcript, so I want to create one text node per word with a I want the user to be able to edit the text for a WordNode. If the user tries to delete a WordNode, the node is either updated or replaced with a placeholder element. If the user clicks the placeholder, the original word is restored. So far, I've:
|
Beta Was this translation helpful? Give feedback.
-
I think this is a very common use-case. A lot of people want "sticky" nodes that don't get deleted but turn into placeholders when their contents is removed. Workaround 🚀Here is the workaround I came up with, which prevents a node from being removed or replaced if it is the first child of the root node (I wanted the the first h1 to turn into a title placeholder when it was deleted). This works by subclassing the node class you'd like to make a template (in my case import { HeadingNode } from "@lexical/rich-text";
import { $getRoot, LexicalNode } from "lexical";
export class SectionHeadingNode extends HeadingNode {
static getType() {
return "sticky-heading";
}
static clone(node: any) {
return new SectionHeadingNode(node.getTag());
}
// do not remove node if it is the first child of root
remove(preserveEmptyParent?: boolean | undefined): void {
if ($getRoot().getFirstChild()?.getKey() !== this.getKey()) {
super.remove(preserveEmptyParent);
}
}
// do not replace node if it is the first child of root
replace<N extends LexicalNode>(
replaceWith: N,
includeChildren?: boolean | undefined
): N {
if ($getRoot().getFirstChild()?.getKey() !== this.getKey()) {
return super.replace(replaceWith, includeChildren);
}
return this as unknown as N;
}
} and then you need to replace the ... {
namespace: "rewrite",
editorState: isCollab ? null : loadContent,
theme: {},
onError(error: any) throw error,
nodes: [
...,
SectionHeadingNode,
{
replace: HeadingNode,
with: (node: HeadingNode) => new SectionHeadingNode(node.getTag()),
},
]
}; |
Beta Was this translation helpful? Give feedback.
-
@notjustinshaw how do you detect empty text and display separate placeholder for the header and non-header? |
Beta Was this translation helpful? Give feedback.
-
The lexical internals take care of that. You just create a new class and override the methods that delete and remove the element from the dom tree to just have them no-op instead. This gets you an element that can’t be removed when empty. The placeholder is just what happens when it’s empty and we inherit that from the old header.
…On Sep 12, 2023 at 16:39 -0700, Viet Nguyen ***@***.***>, wrote:
@notjustinshaw how do you detect empty text and display separate placeholder for the header and non-header?
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
If it's just to display empty text placeholder, you can achieve this via CSS with the 'empty': https://developer.mozilla.org/en-US/docs/Web/CSS/:empty |
Beta Was this translation helpful? Give feedback.
-
I have a CustomTextNode. How do I prevent it from being destroyed?
Options I've considered:
KEY_DELETE_COMMAND
and skipping ifCustomTextNode
is within the range. Attempted this but it's fragile as there doesn't seem to be a single way to cover all "delete" events (ex. someone can select a region and type a letter which triggersCONTROLLED_TEXT_INSERTION_COMMAND
)created
and then recreating them upon deletion, but this also feels fragile & inefficient.remove
function is overridden`)registerLexicalTextEntity
to perform a replacement of empty text with a placeholder node. The matcher doesn't seem to be called when the node is being removed.Beta Was this translation helpful? Give feedback.
All reactions