Skip to content
pixi-silk
GitHub repository
Text

Editing text

TextEditor turns a SilkText into a text layer people click and type into: caret, selection, undo, clipboard, forms and input methods, on the canvas.

Updated pixi-silk 1.0.1View as Markdown

TextEditor from pixi-silk/ui/textedit wraps a SilkText with a caret, a selection and keyboard input. It is a display object, so it pans and zooms with a camera like the rest of the scene.

Make a text layer editable#

Click a text and type. Pinch or ctrl + scroll to zoom in and keep typing.Open in Studio
import { Container } from 'pixi.js';
import { TextEditor } from 'pixi-silk/ui/textedit';

declare const world: Container;
declare const canvas: HTMLCanvasElement;

const note = new TextEditor({
    text: 'Edit me',
    fontSize: 15,
    width: 320, // wraps at 320, grows in height
    placeholder: 'Write something',
    element: canvas,
});

note.on('change', (text: string) => console.log('saved', text));
world.addChild(note);

The note wraps at 320 units and grows downwards as you type. change fires when the edit is committed with a new value. Every SilkText style option works too: font, size, weight, fill, letter spacing, line height and alignment.

Words for double click and word jumps come from Intl.Segmenter, so they follow the language. Arrow keys move by grapheme, so an emoji or an accented letter is one step.

How typing reaches the canvas#

The keys do not go to the canvas. On focus, the editor puts a hidden <textarea> on the page at the caret and keeps its text and selection in step. The browser types into it, and the editor applies each change.

That is why the platform’s own input works. Input methods for Japanese, Chinese and Korean show the composed text inline with an underline, and it becomes one undo step when confirmed. Dead keys, mobile keyboards, autocorrect, the emoji picker and the system clipboard work too.

The hidden field follows the caret as you type, pan or zoom, so input method popups open next to the text. Pass element (usually app.canvas) to place it. Without it, the editor uses the element of the first press.

Build a form#

Tab between the fields. Press Enter in the last one to send the form.Open in Studio

With multiline: false the editor is an input. Enter commits instead of breaking the line, and a width clips the text and scrolls it to keep the caret in view. TextEditor.chain() links fields in Tab order.

import { TextEditor } from 'pixi-silk/ui/textedit';

const email = new TextEditor({ multiline: false, width: 260, placeholder: 'ada@example.com' });
const code = new TextEditor({ multiline: false, width: 260, maxLength: 8 });

TextEditor.chain([email, code]);
code.on('submit', () => console.log(email.value, code.value));
OptionDefaultWhat it does
multilinetrueA text area, or a single-line input
widthnoneWraps a text area, clips and scrolls an input. Without it the box grows with the text
placeholderemptyShown while the text is empty
maxLengthnoneLongest text in UTF-16 code units, like maxlength. Never splits an emoji
readOnlyfalseSelectable and copyable, not editable
selectabletrueFalse turns pointer input off
enternewline, or commit for one linenewline, commit or blur. Shift + Enter breaks the line in a text area
tabnextnext emits tab and leaves, insert types a tab
historyits ownA HistoryLike to share the app’s undo stack
ariaLabelthe placeholderName of the field for screen readers
handlestrueDrag handles at the ends of a touch selection
caretWidth1.5Caret width in screen pixels, whatever the zoom
themeblueCaret, selection and composition colours

In a multi-line field, cmd or ctrl + Enter commits and leaves. Escape leaves any field.

Show text people can copy#

Select with the mouse or a long press, then copy.Open in Studio

readOnly: true keeps clicking, selecting and copying, and drops the caret and editing. Use it for release notes, logs or error messages.

Events, signals and undo#

The editor emits focus, blur, input on every edit, change on a commit with a new value, submit on Enter in a single-line field, and tab. The same state is on editor.state as signals: text, committed, focused, selection and composing.

Undo is built in. Consecutive typing folds into one step, and a click or an arrow key starts the next. To keep text edits in the same stack as the rest of an editor, pass its history:

import { bindHistoryKeys, History } from 'pixi-silk/history';
import { TextEditor } from 'pixi-silk/ui/textedit';

const history = new History();
const title = new TextEditor({ text: 'Frame 1', multiline: false, history });

bindHistoryKeys(history); // cmd + Z outside the field undoes text edits too

Find a caret from a point#

The caret math lives in the text layout, so other tools can use it. caretAt(point) returns the caret nearest a local point. caretRect(index) gives where a caret sits and rangeRects(from, to) the highlight boxes of a range. They follow wrapped lines, alignment, letter spacing and trailing spaces.

import { SilkText } from 'pixi-silk/text';

const label = new SilkText({ text: 'Hello world', wordWrap: true, wordWrapWidth: 60 });
const hit = label.caretAt({ x: 30, y: 8 });
const caret = label.caretRect(hit.index, hit.upstream);

console.log(hit.index, caret.x, label.rangeRects(0, 5));

pixi-silk/text also exports these as functions on a TextLayout, with graphemeStops, wordAt and the word jumps.

Where it stops#

Right-to-left and mixed-direction text is not supported. Arabic and Hebrew lay out left to right, and the caret moves in source order. Emoji count as one caret step but draw as one-colour silhouettes, and there are no ligatures, as in SilkText.

Spell check underlines do not show, because the hidden field is invisible.

API reference: TextEditor