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#
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#
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));
| Option | Default | What it does |
|---|---|---|
multiline | true | A text area, or a single-line input |
width | none | Wraps a text area, clips and scrolls an input. Without it the box grows with the text |
placeholder | empty | Shown while the text is empty |
maxLength | none | Longest text in UTF-16 code units, like maxlength. Never splits an emoji |
readOnly | false | Selectable and copyable, not editable |
selectable | true | False turns pointer input off |
enter | newline, or commit for one line | newline, commit or blur. Shift + Enter breaks the line in a text area |
tab | next | next emits tab and leaves, insert types a tab |
history | its own | A HistoryLike to share the app’s undo stack |
ariaLabel | the placeholder | Name of the field for screen readers |
handles | true | Drag handles at the ends of a touch selection |
caretWidth | 1.5 | Caret width in screen pixels, whatever the zoom |
theme | blue | Caret, 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#
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