TextEditor
TextEditor
is a powerful (rich-text) inline text editor that can be used on any SVG text element.
Learn more about the TextEditor plugin.
constructorβ
constructor(options?: TextEditor.Options);
The TextEditor constructor accepts several parameters for advanced use only:
textβ
[optional] SVG <text>
element. It is recommended to use the ui.TextEditor.getTextElement(el)
method to find the
container <text>
SVG element wrapping all the lines. You can use this method directly on the evt.target
element in
your event handlers, and just check if the returned value is truthy.
placeholderβ
[optional] Set to true
to show a default placeholder (Enter text...) when the text is empty. Set it to a string to
show a custom placeholder. The placeholder visual look can be styled in CSS as follows:
.text-editor .caret.placeholder:before { // The caret.
background-color: red;
}
.text-editor .caret.placeholder:after { // The placeholder text.
font-style: italic;
color: blue;
}
Configurationβ
The ui.TextEditor.edit()
method accepts several parameters. This is the recommended way to start
your text editor.
ui.TextEditor.edit(el?: SVGElement, opt?: TextEditor.Options): TextEditor;
elβ
[optional] SVG <text>
element or any of its descendants. Usually, you pass the event target object when the user
doubleclicks your JointJS element/link.
opt.cellViewβ
[optional] The view for your cell. Usually, you either have direct access to this view (in the paper triggered
'cell:pointerclick'
or 'cell:pointerdblclick'
events), but you can always find a view for any JointJS element or link
by using paper.findViewByModel(cell)
.
opt.textPropertyβ
[optional] The path to the text property that causes the element/link to re-render the text inside it.
(e.g. 'attrs/label/text'
).
opt.annotationsPropertyβ
[optional] The path to the property that holds the text annotations. (e.g. 'attrs/label/annotations'
). (Rich-text specific.)
opt.placeholderβ
[optional] Set to true
to show a placeholder when the text is empty. The placeholder text and visual look can be
styled in CSS as follows:
.text-editor .caret.placeholder:before { // The caret.
background-color: red;
}
.text-editor .caret.placeholder:after { // The placeholder text.
content: 'Enter text...';
font-style: italic;
color: blue;
}
opt.annotationsβ
[optional] Annotations that will be set on the ui.TextEditor
instance. (Rich-text specific.)
opt.annotateUrlsβ
[optional] Set to true
to enable the text editor to automatically detect URL hyperlinks, and annotate them using
the default urlAnnotation
.
opt.urlAnnotationβ
[optional] The default annotation for detected URL hyperlinks:
{
attrs: {
'class': 'url-annotation',
fill: 'lightblue',
'text-decoration': 'underline'
}
}
It could be defined as an object, or a function that returns an object.
function(url) {
return {
attrs: {
'data-url': url,
'fill': 'blue',
'text-decoration': 'underline'
}
}
}
opt.useNativeSelectionβ
[optional] ui.TextEditor
uses native selections for
selecting text. This can be disabled by setting useNativeSelection
to false
in which case ui.TextEditor
renders
its own selections. This is an advanced feature that is useful only in very rare occasions. For example, if you, for
any reason, need to select text in two different elements at the same time, you should set the useNativeSelection
to
false
. This is because if native selections are used, two or more text elements cannot have focus at the same time.
opt.textareaAttributesβ
[optional] Set attributes on the underlying (invisible) textarea that is used internally by the text editor to handle keyboard text input. This is an advanced option, and is useful in very rare situations. The default value is:
textareaAttributes: {
autocorrect: 'off',
autocomplete: 'off',
autocapitalize: 'off',
spellcheck: 'false',
tabindex: '0'
}
Some jQuery UI users reported an issue with the autocomplete
attribute being set on the text area, resulting in the
following error thrown in the jQuery UI:
Uncaught Error: cannot call methods on autocomplete prior to initialization; attempted to call method 'off'
. In this
case, you might want to set your own textareaAttributes
to overcome this issue (in this specific case, omitting
the autocomplete
attribute).
opt.onKeydownβ
[optional] The callback is triggered when a key is pressed. It is possible to stop the event from propagating to the
text editor by calling evt.stopPropagation()
.
onKeydown: (evt: KeyboardEvent) => {
if (evt.code === 'Enter') {
evt.stopPropagation();
TextEditor.close();
}
}
opt.onOutsidePointerdownβ
[optional] The callback is triggered when the user clicks/touches outside the text editor.
onOutsidePointerdown: (evt: PointerEvent) => {
TextEditor.close();
}
Methodsβ
render()β
textEditor.render(root?: HTMLElement): this;
Render the Text Editor. Call this method right after you have constructed the text editor instance, and you're ready to
display the inline text editor. If root
(HTML DOM element) is specified, the text editor HTML container will be
appended to that element instead of to the document.body
. This is useful if you want the text editor to scroll with
some other container.
remove()β
textEditor.remove(): this;
Remove the Text Editor. Call this when you're done with inline text editing.
selectAll()β
textEditor.selectAll(): this;
Programmatically select all the text inside the text editor.
select()β
textEditor.select(selectionStart: number, selectionEnd?: number): this
Programmatically select a portion of the text inside the text editor starting at selectionStart
, and ending at
selectionEnd
. This method automatically swaps selectionStart
and selectionEnd
if they are in a wrong order.
deselect()β
Programmatically deselect all the selected text inside the text editor.
textEditor.deselect(): this;
getSelectionStart()β
textEditor.getSelectionStart(): number | null;
Return the start character position of the current selection.
getSelectionEnd()β
textEditor.getSelectionEnd(): number | null;
Return the end character position of the current selection.
getSelectionRange()β
textEditor.getSelectionRange(): TextEditor.Selection;
Return an object of the form { start: Number, end: Number }
containing the start and end position of the current
selection. Note that the start and end positions are returned normalized. This means that the start index will always
be lower than the end index even though the user started selecting the text from the end back to the start.
getSelectionLength()β
textEditor.getSelectionLength(): number;
Return the number of characters in the current selection.
getSelection()β
textEditor.getSelection(): string;
Return the selected text.
startSelecting()β
textEditor.startSelecting(): void;
Starts the text selection (i.e. updates the selection range based on the mouse coordinates until the user releases the mouse).
findAnnotationsUnderCursor()β
textEditor.findAnnotationsUnderCursor(
annotations: Vectorizer.TextAnnotation,
selectionStart: number
): Array<Vectorizer.TextAnnotation>;
Find all the annotations in the annotations
array that the cursor at selectionStart
position falls into.
findAnnotationsInSelection()β
textEditor.findAnnotationsInSelection(
annotations: Vectorizer.TextAnnotation,
selectionStart: number,
selectionEnd: number
): Array<Vectorizer.TextAnnotation>;
Find all the annotations that fall into the selection range specified by selectionStart
and selectionEnd
. This
method assumes the selection range is normalized.
setCaret()β
textEditor.setCaret(charNum: number, opt?: { [key: string]: any }): this;
Programmatically set the caret position. If opt.silent
is true, the text editor will not trigger the 'caret:change'
event.
hideCaret()β
textEditor.hideCaret(): this;
Programmatically hide the caret.
updateCaret()β
textEditor.updateCaret(): void;
Manually update the visual attributes (e.g. size, color) of the caret. It's useful when the containing SVG was transformed.
getTextContent()β
textEditor.getTextContent(): string;
Return the text content (including new line characters) inside the text editor.
getWordBoundary()β
textEditor.getWordBoundary(charNum: number): [number, number] | undefined;
Return the start and end character positions for a word under charNum
character position.
getURLBoundary()β
textEditor.getURLBoundary(charNum: number): [number, number] | undefined;
Return the start and end character positions for a URL under charNum
character position. Return undefined
if there
was no URL recognized at the charNum
index.
getNumberOfChars()β
textEditor.getNumberOfChars(): number;
Return the number of characters in the text.
getCharNumFromEvent()β
textEditor.getCharNumFromEvent(evt: dia.Event | Event): number;
Return the character position the user clicked on. If there is no such a position found, return the last one.
setCurrentAnnotation()β
textEditor.setCurrentAnnotation(attrs: attributes.SVGAttributes): void;
This method stores annotation attributes that will be used for the very next insert operation. This is useful, for
example, when we have a toolbar and the user changes text to e.g. bold. At this point, we can just call
setCurrentAnnotation({ 'font-weight': 'bold' })
, and let the text editor know that once the user starts typing, the
text should be bold. Note that the current annotation will be removed right after the first text operation came. This
is because after that, the next inserted character will already inherit properties from the previous character which is
our 'bold' text. (Rich-text specific.)
setAnnotations()β
textEditor.setAnnotations(
annotations: Vectorizer.TextAnnotation | Array<Vectorizer.TextAnnotation>
): void;
Set annotations of the text inside the text editor. These annotations will be modified during the course of using the text editor. (Rich-text specific.)
getAnnotations()β
textEditor.getAnnotations(): Array<Vectorizer.TextAnnotation> | undefined;
Return the annotations of the text inside the text editor. (Rich-text specific.)
getCombinedAnnotationAttrsAtIndex()β
textEditor.getCombinedAnnotationAttrsAtIndex(
index: number,
annotations: Vectorizer.TextAnnotation | Array<Vectorizer.TextAnnotation>
): attributes.SVGAttributes;
Get the combined (merged) attributes for a character at the position index
taking into account all the annotations
that apply. (Rich-text specific.)
getSelectionAttrs()β
textEditor.getSelectionAttrs(
range: TextEditor.Selection,
annotations: Vectorizer.TextAnnotation
): attributes.SVGAttributes;
Find a common annotation among all the annotations that fall into the range
(an object with start
and end
properties - normalized). For characters that don't fall into any of the annotations
, assume defaultAnnotation
(default annotation does not need start
and end
properties). The common annotation denotes the attributes that all
the characters in the range
share. If any of the attributes for any character inside range
differ, undefined
is
returned. This is useful e.g. when your toolbar needs to reflect the text attributes of a selection. (Rich-text specific.)
Static Methodsβ
getTextElement()β
ui.TextEditor.getTextElement(el: SVGElement): SVGElement | undefined;
Return the containing SVG <text>
element closest to the SVG element el
. This is especially handy in event handlers
where you can just pass the evt.target
element to this function, and it will return the element that you can directly
use as a parameter to the ui.TextElement
constructor function. If no such element is exists, the function returns
undefined
.
edit()β
ui.TextEditor.edit(el?: SVGElement, opt?: TextEditor.Options): TextEditor;
See the Configuration section for details.
close()β
ui.TextEditor.close(): void;
Close the currently opened text editor (if there was one). Note that before actually removing the editor (and if the
annotateUrls
option is set to true
), the close()
method tries to find if there was a URL hyperlink at the current
cursor position and annotates it. The reason is that normally, URL hyperlinks are detected after the user types a
non-visible character such as SPACE or new line. In this case though, the user closed the editor by e.g. clicking
outside the element/link, so if there was a URL hyperlink at the end of the text content, it would not have been annotated.
applyAnnotations()β
ui.TextEditor.applyAnnotations(annotations: TextEditor.Annotation[]): void;
Apply annotations
to the current selection. This is useful if the user has something selected in the text editor,
and then changes e.g. the font size via a toolbar. In this case, you construct the annotations array from the toolbar
changes, and apply it using this function on the current selection.
setCurrentAnnotation()β
ui.TextEditor.setCurrentAnnotation(attrs: attributes.SVGAttributes): void;
See the instance setCurrentAnnotation()
method above.
getAnnotations()β
ui.TextEditor.getAnnotations(): TextEditor.Annotation[] | undefined;
See the instance getAnnotations()
method above.
setCaret()β
ui.TextEditor.setCaret(charNum: number, opt?: { [key: string]: any }): TextEditor;
See the instance setCaret()
method above.
deselect()β
ui.TextEditor.deselect(): TextEditor;
See the instance deselect()
method above.
selectAll()β
ui.TextEditor.selectAll(): TextEditor;
See the instance selectAll()
method above.
select()β
ui.TextEditor.select(selectionStart: number, selectionEnd?: number): TextEditor;
See the instance select()
method above.
getNumberOfChars()β
ui.TextEditor.getNumberOfChars(): number;
See the instance getNumberOfChars()
method above.
getCharNumFromEvent()β
ui.TextEditor.getCharNumFromEvent(evt: dia.Event | Event): number;
See the instance getCharNumFromEvent()
method above.
getWordBoundary()β
ui.TextEditor.getWordBoundary(charNum: number): [number, number] | undefined;
See the instance getWordBoundary()
method above.
findAnnotationsUnderCursor()β
ui.TextEditor.findAnnotationsUnderCursor(): Array<Vectorizer.TextAnnotation> | null;
See the instance findAnnotationsUnderCursor()
method above except the annotations
and index
arguments are automatically added so this method does not accept any arguments.
findAnnotationsInSelection()β
ui.TextEditor.findAnnotationsInSelection(): Array<Vectorizer.TextAnnotation> | null;
See the instance findAnnotationsInSelection()
method above except the annotations
and start and end index of the selection are automatically added so this method does not accept any arguments.
getSelectionAttrs()β
ui.TextEditor.getSelectionAttrs(
annotations: Vectorizer.TextAnnotation | Array<Vectorizer.TextAnnotation>
): attributes.SVGAttributes | null;
See the instance getSelectionAttrs()
method above except the range is automatically added.
getSelectionLength()β
ui.TextEditor.getSelectionLength(): number;
See the instance getSelectionLength()
method above.
getSelectionRange()β
ui.TextEditor.getSelectionRange(): TextEditor.Selection;
See the instance getSelectionRange()
method above.
isLineStart()β
ui.TextEditor.isLineStart(text: string, charNum: number): boolean;
Return true
if the character at the position index
is the first character of some line.
isLineEnding()β
ui.TextEditor.isLineEnding(text: string, charNum: number): boolean;
Return true
if the character at the position index
is a newline character, but does not denote an empty line. In
other words, the newline character under index
terminates a non-empty line.
isEmptyLine()β
ui.TextEditor.isEmptyLine(text: string, charNum: number): boolean;
Return true
if the character at the position index
is a newline character that denotes an empty line.
normalizeAnnotations()β
ui.TextEditor.normalizeAnnotations(
annotations: TextEditor.Annotation[],
options?: any
): TextEditor.Annotation[];
Returns an array of annotations that contains no duplicates, and is sorted by the start
index of each annotation. The
method generates annotations containing a maximum number of attributes rather than aiming for the maximum range.
getCombinedAnnotationAttrsAtIndex()β
ui.TextEditor.getCombinedAnnotationAttrsAtIndex(
annotations: TextEditor.Annotation[],
index: number,
options?: any
): attributes.SVGAttributes;
Get the combined (merged) attributes for a character at the position index
taking into account all the annotations
that apply.
getCombinedAnnotationAttrsBetweenIndexes()β
ui.TextEditor.getCombinedAnnotationAttrsBetweenIndexes(
annotations: TextEditor.Annotation[],
start: number,
end: number,
options?: any
): attributes.SVGAttributes;
Find a common annotation among all the annotations
that fall into the range between start
and end
. Common
annotation refers to attributes that all features in the range
have in common. If any of the attributes of any
character within the range
differ, it is omitted from the result.
Eventsβ
text:changeβ
Triggered when the text inside the text editor changes. This is the most important event, and the one you want to always
react on. The callback has the following signature: function(newText)
and is therefore called with the new text as a
parameter. You are assumed to set the new text to the property on your JointJS element/link that is used for storing
the associated text.
select:changeβ
Triggered when the text inside the text editor is being selected (or if the user double-clicked to select a word or
triple-clicked to select the entire text). The callback has the following signature:
function(selectionStart, selectionEnd)
. For example, you can see the selected text like this:
ed.on('select:change', function(selectionStart, selectionEnd) {
const text = ed.getTextContent().substring(selectionStart, selectionEnd);
});
select:changedβ
Triggered when the user is finished selecting text inside the text editor. The callback has the following signature:
function(selectionStart, selectionEnd)
. For example, you can see the selected text like this:
ed.on('select:changed', function(selectionStart, selectionEnd) {
const text = ed.getTextContent().substring(selectionStart, selectionEnd);
});
caret:changeβ
Triggered when the caret has changed position. The position is passed to the callback as the only argument.
caret:out-of-rangeβ
Triggered when the caret is outside the visible text area. This is a very special event that you don't usually deal with.
The only situation where this event can occur is when ui.TextEditor
is used on a text rendered along a path
(see Vectorizer#text(str, { textPath: '' })
). In this case, if the user moves their cursor outside the visible text area,
the caret:out-of-range
event is triggered so that the programmer has a chance to react (if they want to, because these
situations are handled seamlessly in ui.TextEditor
by hiding the caret).
openβ
Triggered when a new text editor instance is created.
TextEditor.on('open', function(textNode, cellView) {
// Editing of an SVGTextElement `textNode` (from the `cellView`) started.
});
closeβ
TextEditor.on('close', function(textNode, cellView) {
// Editing of an SVGTextElement `textNode` (from the `cellView`) finished.
});
Typesβ
Annotationβ
interface Annotation extends Vectorizer.TextAnnotation {
[key: string]: any;
}
Optionsβ
interface Options extends mvc.ViewOptions<undefined> {
text?: string, // The SVG text element on which we want to enable inline text editing.
newlineCharacterBBoxWidth?: number, // The width of the new line character. Used for selection of a newline.
placeholder?: boolean | string, // The placeholder in case the text gets emptied.
focus?: boolean, // Determines if the textarea should gain focus. In some cases, this is not intentional - e.g. if we use the ui.TextEditor for displaying remote cursor.
debug?: boolean,
useNativeSelection?: boolean,
annotateUrls?: boolean,
cellView?: dia.CellView,
textProperty?: dia.Path,
annotationsProperty?: dia.Path,
urlAnnotation?: Partial<URLAnnotation> | URLAnnotationCallback,
textareaAttributes?: {
autocorrect?: string,
autocomplete?: string,
autocapitalize?: string,
spellcheck?: string,
tabindex?: string
},
onKeydown?: (this: TextEditor, evt: KeyboardEvent, editor: TextEditor) => void;
onOutsidePointerdown?: (this: TextEditor, evt: PointerEvent, editor: TextEditor) => void;
}
URLAnnotationβ
interface URLAnnotation extends Annotation {
url: string;
}
URLAnnotationCallbackβ
type URLAnnotationCallback = (url: string) => Partial<URLAnnotation>;