Skip to main content

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>;