@weasel-js/text-dom 1.9.4 → 1.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/index.d.ts +52 -74
- package/dist/index.js +446 -198
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@ writes styled runs.
|
|
|
5
5
|
|
|
6
6
|
Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
|
|
7
7
|
scene-graph canvas kit for React. See the
|
|
8
|
-
[API reference](https://orochi235.github.io/weasel/
|
|
8
|
+
[API reference](https://orochi235.github.io/weasel/#pkg/text-dom).
|
|
9
9
|
|
|
10
10
|
```sh
|
|
11
11
|
npm install @weasel-js/text-dom
|
|
@@ -22,6 +22,11 @@ While an edit is open the hook reports the selection's shared style
|
|
|
22
22
|
(`MIXED` where the selected runs disagree) and applies a style or a flag such
|
|
23
23
|
as bold to the selected range.
|
|
24
24
|
|
|
25
|
+
A paste or a drop goes in as runs, never as the clipboard's markup: its blocks
|
|
26
|
+
become lines, its table cells are set apart by tabs, and it keeps its bold,
|
|
27
|
+
italic, underline, strikethrough, overline, and super- and subscript. Face,
|
|
28
|
+
size, and color come from the text it lands in.
|
|
29
|
+
|
|
25
30
|
`runsToDom` and `domToRuns` are the conversion between runs and the overlay's
|
|
26
31
|
DOM, and `charOffsetToDomPosition` / `domPositionToCharOffset` map a character
|
|
27
32
|
offset in the text to a DOM position and back.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { FlagKey, StyledRun, TextStyle, TextVerticalAlign, TextPaint, RangeStyle, RunStylePatch, ResolvedTextStyle } from '@weasel-js/text';
|
|
2
2
|
|
|
3
|
-
/**
|
|
4
|
-
* In-place text editing via a contenteditable overlay positioned over the
|
|
5
|
-
* text node's screen-space pose. Enter / blur commit; Shift+Enter inserts a
|
|
6
|
-
* newline; Escape cancels. The hook owns the DOM lifecycle of the overlay
|
|
7
|
-
* element — caller just provides a container, the lookup hooks, and a
|
|
8
|
-
* `setText` callback (which is responsible for op-batching/undo).
|
|
9
|
-
*
|
|
10
|
-
* The text layer should hide the node it's currently editing
|
|
11
|
-
* (`isHidden: (n) => n.id === editingId`) so the overlay isn't drawn twice.
|
|
12
|
-
*/
|
|
3
|
+
/** The option, return, and pose types of `useTextEdit`. */
|
|
13
4
|
|
|
14
5
|
/**
|
|
15
6
|
* A styling a shortcut or a control toggles. The additive booleans, plus the
|
|
@@ -56,18 +47,19 @@ interface TextEditScreenPose {
|
|
|
56
47
|
* transform does the world→screen conversion.
|
|
57
48
|
*
|
|
58
49
|
* This is the only way run-level typography can be correct at a zoom other
|
|
59
|
-
* than 1.
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* scales node-level and run-level values by the same factor for free.
|
|
64
|
-
*
|
|
65
|
-
* Omit it (the default, `1`) and the pose is plain screen pixels — the hook
|
|
66
|
-
* then infers the world→screen factor from `fontSize / style.fontSize` to
|
|
67
|
-
* scale node-level `letterSpacing`, and run-level overrides are left
|
|
68
|
-
* unscaled. Correct only at zoom 1.
|
|
50
|
+
* than 1. Omit it (the default, `1`) and the pose is plain screen pixels —
|
|
51
|
+
* the hook then infers the world→screen factor from
|
|
52
|
+
* `fontSize / style.fontSize` to scale node-level `letterSpacing`, and
|
|
53
|
+
* run-level overrides are left unscaled.
|
|
69
54
|
*/
|
|
70
55
|
zoom?: number;
|
|
56
|
+
/**
|
|
57
|
+
* The frame the text is drawn in, when its box is turned or skewed: the
|
|
58
|
+
* linear part `[a, b, c, d]` (`x' = a·x + c·y`, `y' = b·x + d·y`) applied
|
|
59
|
+
* about the box's center. `x`, `y`, `width`, and `height` stay those of
|
|
60
|
+
* the box before it. Absent is the identity.
|
|
61
|
+
*/
|
|
62
|
+
frame?: readonly [a: number, b: number, c: number, d: number];
|
|
71
63
|
}
|
|
72
64
|
/** A box in CSS pixels relative to `useTextEdit`'s `container`. */
|
|
73
65
|
interface TextEditClipRect {
|
|
@@ -144,8 +136,7 @@ interface UseTextEditOptions {
|
|
|
144
136
|
* commit calls both `setText` (with `runsToPlainText(runs)`) and
|
|
145
137
|
* `setRuns(id, runs)` — but only when runs are actually in play: the node
|
|
146
138
|
* already had some, or the edit produced styling. A plain-text edit of a
|
|
147
|
-
* plain-text node still calls `setText` alone
|
|
148
|
-
* been styled doesn't grow a single-run `runs` array just for being edited.
|
|
139
|
+
* plain-text node still calls `setText` alone.
|
|
149
140
|
*/
|
|
150
141
|
setRuns?: (id: string, runs: StyledRun[]) => void;
|
|
151
142
|
/**
|
|
@@ -153,8 +144,7 @@ interface UseTextEditOptions {
|
|
|
153
144
|
* color popover, anything whose whole purpose is to style the text being
|
|
154
145
|
* edited? Focus moving into one does not end the edit.
|
|
155
146
|
*
|
|
156
|
-
* Without this,
|
|
157
|
-
* destroys it: clicking a size field blurs the overlay, blur commits, and
|
|
147
|
+
* Without this, clicking a size field blurs the overlay, blur commits, and
|
|
158
148
|
* the caret the control was about to act on is gone. Toggle buttons can
|
|
159
149
|
* dodge it by `preventDefault()`-ing their own mousedown, but a field the
|
|
160
150
|
* user has to type into cannot.
|
|
@@ -166,15 +156,21 @@ interface UseTextEditOptions {
|
|
|
166
156
|
/** What Escape does to the edit: `'cancel'` (the default) drops it,
|
|
167
157
|
* `'commit'` keeps the text as Enter would. */
|
|
168
158
|
escape?: 'cancel' | 'commit';
|
|
159
|
+
/** Called as an edit of `id` opens, from the call that opens it. */
|
|
160
|
+
onStart?: (id: string) => void;
|
|
161
|
+
/**
|
|
162
|
+
* Called as the edit of `id` closes, however it does: a commit, a cancel,
|
|
163
|
+
* a click away, or another edit opening over it. A commit has been written
|
|
164
|
+
* by then. Not called when the hook unmounts with an edit open.
|
|
165
|
+
*/
|
|
166
|
+
onEnd?: (id: string) => void;
|
|
169
167
|
/**
|
|
170
168
|
* How long, in ms, an edit opening waits for the overlay's face before
|
|
171
169
|
* showing the text in the fallback. Defaults to {@link TEXT_EDIT_FONT_HOLD}.
|
|
172
170
|
*
|
|
173
171
|
* The overlay stands in for canvas text the host has hidden, so the wait
|
|
174
172
|
* shows nothing at all; past it, the fallback shows and reflows to the real
|
|
175
|
-
* face when that lands. A face already loaded
|
|
176
|
-
* `@weasel-js/font` builds it as soon as its bytes arrive — shows at once.
|
|
177
|
-
* `0` never waits.
|
|
173
|
+
* face when that lands. A face already loaded shows at once. `0` never waits.
|
|
178
174
|
*/
|
|
179
175
|
fontHold?: number;
|
|
180
176
|
}
|
|
@@ -186,10 +182,8 @@ interface UseTextEditOptions {
|
|
|
186
182
|
declare const TEXT_EDIT_FONT_HOLD = 100;
|
|
187
183
|
/** Options for `useTextEdit().startEdit`. */
|
|
188
184
|
interface StartEditOptions {
|
|
189
|
-
/**
|
|
190
|
-
*
|
|
191
|
-
* `'all'` selects the whole text (default — preserves the prior behavior).
|
|
192
|
-
*/
|
|
185
|
+
/** Where to place the caret on edit start. Number = caret offset (0..text.length);
|
|
186
|
+
* `'all'` selects the whole text (default). */
|
|
193
187
|
caret?: number | 'all';
|
|
194
188
|
}
|
|
195
189
|
/** Return shape of `useTextEdit`. */
|
|
@@ -215,7 +209,7 @@ interface UseTextEditReturn {
|
|
|
215
209
|
* n" stay distinguishable — a styling written at a collapsed caret arms
|
|
216
210
|
* {@link UseTextEditReturn.pendingStyle} instead of restyling a range.
|
|
217
211
|
*
|
|
218
|
-
* Follows the DOM selection, which browsers
|
|
212
|
+
* Follows the DOM selection, which browsers report from a task
|
|
219
213
|
* rather than synchronously; anything this hook writes itself updates it
|
|
220
214
|
* synchronously. A DOM selection that leaves the overlay does **not** clear
|
|
221
215
|
* it: that is what happens when the user clicks a styling control, and
|
|
@@ -240,10 +234,15 @@ interface UseTextEditReturn {
|
|
|
240
234
|
* Styling armed at a collapsed caret, applied to the next character typed
|
|
241
235
|
* and then dropped. `{}` when nothing is armed — which is always the case
|
|
242
236
|
* while `selection` covers a real range, since a range is styled directly.
|
|
243
|
-
*
|
|
244
|
-
* Moving the caret abandons it, as in any other editor.
|
|
237
|
+
* Moving the caret abandons it.
|
|
245
238
|
*/
|
|
246
239
|
pendingStyle: RunStylePatch;
|
|
240
|
+
/**
|
|
241
|
+
* What a styling control shows during an edit: the node's own style and
|
|
242
|
+
* paint, `rangeStyle` over it, and `pendingStyle` last — what the user
|
|
243
|
+
* will see themselves type. `null` when nothing is being edited.
|
|
244
|
+
*/
|
|
245
|
+
effectiveStyle: RangeStyle | null;
|
|
247
246
|
/**
|
|
248
247
|
* Write `patch` over `selection`. A no-op with no active edit or an empty
|
|
249
248
|
* patch. Over a real range this restyles the runs under it; at a collapsed
|
|
@@ -259,34 +258,20 @@ interface UseTextEditReturn {
|
|
|
259
258
|
* re-selecting, and `rangeStyle` reflects the write before this returns.
|
|
260
259
|
*/
|
|
261
260
|
applyStyleToSelection: (patch: RunStylePatch) => void;
|
|
262
|
-
/**
|
|
263
|
-
*
|
|
264
|
-
* uniformly carry it, clear it if it does. The shape a B / I / x² control
|
|
265
|
-
* or a keyboard shortcut wants, and what this hook's own shortcuts call.
|
|
266
|
-
*/
|
|
261
|
+
/** Toggle one styling over `selection` — set it if the range doesn't
|
|
262
|
+
* uniformly carry it, clear it if it does. */
|
|
267
263
|
toggleStyle: (toggle: StyleToggle) => void;
|
|
268
264
|
}
|
|
269
|
-
|
|
270
|
-
declare function useTextEdit(opts: UseTextEditOptions): UseTextEditReturn;
|
|
271
|
-
/**
|
|
272
|
-
* The overlay's `top`, in container pixels, for content `contentHeight` tall
|
|
273
|
-
* (pre-scale, like the rest of `pose`): the pose's top, shifted by the same
|
|
274
|
-
* `verticalAlign` slack the canvas applies, plus `drop` — how far below the
|
|
275
|
-
* CSS line's baseline the canvas puts its own, from {@link baselineDrop}.
|
|
276
|
-
* @internal
|
|
277
|
-
*/
|
|
278
|
-
declare function overlayTop(pose: TextEditScreenPose, contentHeight: number, drop?: number): number;
|
|
265
|
+
|
|
279
266
|
/**
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
283
|
-
*
|
|
284
|
-
* is read from both sides rather than predicted: the canvas's from its own layout, the
|
|
285
|
-
* overlay's from a zero-height inline box on a hidden line set exactly as the
|
|
286
|
-
* overlay is. Pre-scale; `0` when either side has nothing to measure.
|
|
287
|
-
* @internal
|
|
267
|
+
* In-place text editing via a contenteditable overlay positioned over the
|
|
268
|
+
* text node's screen-space pose. Enter / blur commit; Shift+Enter inserts a
|
|
269
|
+
* newline. The text layer should hide the node it's currently editing
|
|
270
|
+
* (`isHidden: (n) => n.id === editingId`) so the overlay isn't drawn twice.
|
|
288
271
|
*/
|
|
289
|
-
|
|
272
|
+
|
|
273
|
+
/** In-place text editing via a contenteditable overlay positioned over the text node's screen-space pose. */
|
|
274
|
+
declare function useTextEdit(opts: UseTextEditOptions): UseTextEditReturn;
|
|
290
275
|
|
|
291
276
|
/**
|
|
292
277
|
* The DOM position of plain-text character position `offset`: a text node
|
|
@@ -313,20 +298,15 @@ declare function charOffsetToDomPosition(parent: HTMLElement, offset: number): {
|
|
|
313
298
|
declare function domPositionToCharOffset(parent: HTMLElement, node: Node, offset: number): number;
|
|
314
299
|
|
|
315
300
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
319
|
-
* styles. Newlines inside a run are literal `\n` characters; the overlay
|
|
320
|
-
* has `white-space: pre-wrap` so they render as line breaks. The browser
|
|
321
|
-
* breaks at no other UAX #14 hard break, so each of those is written as a
|
|
301
|
+
* DOM ↔ `StyledRun[]` serializers for the contenteditable overlay: a flat
|
|
302
|
+
* sequence of `<span data-run>` elements, one per run. The browser breaks at
|
|
303
|
+
* `\n` and at no other UAX #14 hard break, so each of those is written as a
|
|
322
304
|
* `<span data-break>` holding a `\n` in its place — one character for one,
|
|
323
305
|
* which keeps DOM offsets equal to source offsets.
|
|
324
306
|
*
|
|
325
|
-
* A small-caps run
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
* synthesis uses a fixed factor of its own. The pieces are presentation
|
|
329
|
-
* only — `domToRuns` reads straight through them.
|
|
307
|
+
* A small-caps run's lowercase letters sit in `<span data-small-caps>` pieces,
|
|
308
|
+
* uppercased and sized at the scale the canvas draws them with, since a
|
|
309
|
+
* browser's own synthesis uses a fixed factor of its own.
|
|
330
310
|
*/
|
|
331
311
|
|
|
332
312
|
/** Walk an overlay tree and emit a coalesced `StyledRun[]`. */
|
|
@@ -335,11 +315,9 @@ declare function domToRuns(parent: HTMLElement): StyledRun[];
|
|
|
335
315
|
* runs have it, and the face and transform that decide their pieces. */
|
|
336
316
|
type OverlayRunBase = Pick<ResolvedTextStyle, 'fontFamily' | 'fontWeight' | 'fontStyle' | 'textTransform' | 'fontVariantCaps'>;
|
|
337
317
|
/**
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
* inheriting small caps needs to be split by; without it the kit defaults
|
|
341
|
-
* stand in.
|
|
318
|
+
* Replace `parent`'s children with one `<span data-run>` per run. `base` is
|
|
319
|
+
* the node's style, which a run inheriting small caps is split by.
|
|
342
320
|
*/
|
|
343
321
|
declare function runsToDom(runs: readonly StyledRun[], parent: HTMLElement, base?: OverlayRunBase): void;
|
|
344
322
|
|
|
345
|
-
export { type StartEditOptions, type StyleToggle, TEXT_EDIT_FONT_HOLD, type TextEditClipRect, type TextEditCommit, type TextEditScreenPose, type TextEditSelection, type UseTextEditOptions, type UseTextEditReturn,
|
|
323
|
+
export { type OverlayRunBase, type StartEditOptions, type StyleToggle, TEXT_EDIT_FONT_HOLD, type TextEditClipRect, type TextEditCommit, type TextEditScreenPose, type TextEditSelection, type UseTextEditOptions, type UseTextEditReturn, charOffsetToDomPosition, domPositionToCharOffset, domToRuns, runsToDom, useTextEdit };
|