@react-x11/components 0.8.0 → 0.9.0
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 +95 -34
- package/dist/charts/node.d.ts +1 -1
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +6 -2
- package/dist/charts/node.js.map +1 -1
- package/dist/embed/client.d.ts +48 -3
- package/dist/embed/client.d.ts.map +1 -1
- package/dist/embed/client.js +79 -5
- package/dist/embed/client.js.map +1 -1
- package/dist/embed/index.d.ts +1 -1
- package/dist/embed/index.d.ts.map +1 -1
- package/dist/embed/index.js +1 -1
- package/dist/embed/index.js.map +1 -1
- package/dist/html/node.d.ts +3 -1
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +7 -5
- package/dist/html/node.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{markdown → internal/markdown}/ast.d.ts +12 -0
- package/dist/internal/markdown/ast.d.ts.map +1 -0
- package/dist/internal/markdown/ast.js +22 -0
- package/dist/internal/markdown/ast.js.map +1 -0
- package/dist/internal/markdown/parse.d.ts.map +1 -0
- package/dist/{markdown → internal/markdown}/parse.js +112 -22
- package/dist/internal/markdown/parse.js.map +1 -0
- package/dist/internal/markdown/stringify.d.ts +4 -0
- package/dist/internal/markdown/stringify.d.ts.map +1 -0
- package/dist/internal/markdown/stringify.js +684 -0
- package/dist/internal/markdown/stringify.js.map +1 -0
- package/dist/internal/markdown/tags.d.ts.map +1 -0
- package/dist/internal/markdown/tags.js.map +1 -0
- package/dist/internal/scroll.d.ts +3 -2
- package/dist/internal/scroll.d.ts.map +1 -1
- package/dist/internal/scroll.js +1 -0
- package/dist/internal/scroll.js.map +1 -1
- package/dist/maps/anchors.d.ts +98 -0
- package/dist/maps/anchors.d.ts.map +1 -0
- package/dist/maps/anchors.js +531 -0
- package/dist/maps/anchors.js.map +1 -0
- package/dist/maps/controller.d.ts +283 -0
- package/dist/maps/controller.d.ts.map +1 -0
- package/dist/maps/controller.js +743 -0
- package/dist/maps/controller.js.map +1 -0
- package/dist/maps/gl/buckets.d.ts +95 -0
- package/dist/maps/gl/buckets.d.ts.map +1 -0
- package/dist/maps/gl/buckets.js +362 -0
- package/dist/maps/gl/buckets.js.map +1 -0
- package/dist/maps/gl/build-worker.d.ts +13 -0
- package/dist/maps/gl/build-worker.d.ts.map +1 -0
- package/dist/maps/gl/build-worker.js +44 -0
- package/dist/maps/gl/build-worker.js.map +1 -0
- package/dist/maps/gl/color.d.ts +8 -0
- package/dist/maps/gl/color.d.ts.map +1 -0
- package/dist/maps/gl/color.js +57 -0
- package/dist/maps/gl/color.js.map +1 -0
- package/dist/maps/gl/cover.d.ts +51 -0
- package/dist/maps/gl/cover.d.ts.map +1 -0
- package/dist/maps/gl/cover.js +124 -0
- package/dist/maps/gl/cover.js.map +1 -0
- package/dist/maps/gl/index.d.ts +3 -0
- package/dist/maps/gl/index.d.ts.map +1 -0
- package/dist/maps/gl/index.js +20 -0
- package/dist/maps/gl/index.js.map +1 -0
- package/dist/maps/gl/markers.d.ts +28 -0
- package/dist/maps/gl/markers.d.ts.map +1 -0
- package/dist/maps/gl/markers.js +60 -0
- package/dist/maps/gl/markers.js.map +1 -0
- package/dist/maps/gl/overlays.d.ts +68 -0
- package/dist/maps/gl/overlays.d.ts.map +1 -0
- package/dist/maps/gl/overlays.js +300 -0
- package/dist/maps/gl/overlays.js.map +1 -0
- package/dist/maps/gl/pane.d.ts +12 -0
- package/dist/maps/gl/pane.d.ts.map +1 -0
- package/dist/maps/gl/pane.js +37 -0
- package/dist/maps/gl/pane.js.map +1 -0
- package/dist/maps/gl/placement.d.ts +111 -0
- package/dist/maps/gl/placement.d.ts.map +1 -0
- package/dist/maps/gl/placement.js +567 -0
- package/dist/maps/gl/placement.js.map +1 -0
- package/dist/maps/gl/renderer.d.ts +352 -0
- package/dist/maps/gl/renderer.d.ts.map +1 -0
- package/dist/maps/gl/renderer.js +1127 -0
- package/dist/maps/gl/renderer.js.map +1 -0
- package/dist/maps/gl/shaders.d.ts +120 -0
- package/dist/maps/gl/shaders.d.ts.map +1 -0
- package/dist/maps/gl/shaders.js +451 -0
- package/dist/maps/gl/shaders.js.map +1 -0
- package/dist/maps/gl/store.d.ts +115 -0
- package/dist/maps/gl/store.d.ts.map +1 -0
- package/dist/maps/gl/store.js +469 -0
- package/dist/maps/gl/store.js.map +1 -0
- package/dist/maps/gl/text.d.ts +154 -0
- package/dist/maps/gl/text.d.ts.map +1 -0
- package/dist/maps/gl/text.js +382 -0
- package/dist/maps/gl/text.js.map +1 -0
- package/dist/maps/gl/view.d.ts +54 -0
- package/dist/maps/gl/view.d.ts.map +1 -0
- package/dist/maps/gl/view.js +974 -0
- package/dist/maps/gl/view.js.map +1 -0
- package/dist/maps/index.d.ts +1 -1
- package/dist/maps/index.d.ts.map +1 -1
- package/dist/maps/index.js +216 -52
- package/dist/maps/index.js.map +1 -1
- package/dist/maps/labels.d.ts +37 -16
- package/dist/maps/labels.d.ts.map +1 -1
- package/dist/maps/labels.js +188 -119
- package/dist/maps/labels.js.map +1 -1
- package/dist/maps/node.d.ts +162 -56
- package/dist/maps/node.d.ts.map +1 -1
- package/dist/maps/node.js +607 -515
- package/dist/maps/node.js.map +1 -1
- package/dist/maps/overlay.d.ts +72 -9
- package/dist/maps/overlay.d.ts.map +1 -1
- package/dist/maps/overlay.js +108 -28
- package/dist/maps/overlay.js.map +1 -1
- package/dist/maps/paint.d.ts +3 -0
- package/dist/maps/paint.d.ts.map +1 -1
- package/dist/maps/paint.js.map +1 -1
- package/dist/maps/proj.d.ts +14 -0
- package/dist/maps/proj.d.ts.map +1 -1
- package/dist/maps/proj.js +28 -0
- package/dist/maps/proj.js.map +1 -1
- package/dist/maps/renderer.d.ts +69 -0
- package/dist/maps/renderer.d.ts.map +1 -0
- package/dist/maps/renderer.js +66 -0
- package/dist/maps/renderer.js.map +1 -0
- package/dist/maps/sources.d.ts +25 -3
- package/dist/maps/sources.d.ts.map +1 -1
- package/dist/maps/sources.js +17 -0
- package/dist/maps/sources.js.map +1 -1
- package/dist/maps/theme.d.ts +19 -0
- package/dist/maps/theme.d.ts.map +1 -0
- package/dist/maps/theme.js +44 -0
- package/dist/maps/theme.js.map +1 -0
- package/dist/maps/tiles.d.ts +117 -17
- package/dist/maps/tiles.d.ts.map +1 -1
- package/dist/maps/tiles.js +163 -30
- package/dist/maps/tiles.js.map +1 -1
- package/dist/maps/types.d.ts +188 -20
- package/dist/maps/types.d.ts.map +1 -1
- package/dist/markdown/expressions.d.ts +1 -1
- package/dist/markdown/expressions.d.ts.map +1 -1
- package/dist/markdown/expressions.js.map +1 -1
- package/dist/markdown/index.d.ts +7 -6
- package/dist/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +9 -7
- package/dist/markdown/index.js.map +1 -1
- package/dist/markdown/spans.d.ts +1 -1
- package/dist/markdown/spans.d.ts.map +1 -1
- package/dist/media-player/index.d.ts +11 -3
- package/dist/media-player/index.d.ts.map +1 -1
- package/dist/media-player/index.js +13 -2
- package/dist/media-player/index.js.map +1 -1
- package/dist/rich-text-editor/clipboard.d.ts +23 -0
- package/dist/rich-text-editor/clipboard.d.ts.map +1 -0
- package/dist/rich-text-editor/clipboard.js +307 -0
- package/dist/rich-text-editor/clipboard.js.map +1 -0
- package/dist/rich-text-editor/collab.d.ts +32 -0
- package/dist/rich-text-editor/collab.d.ts.map +1 -0
- package/dist/rich-text-editor/collab.js +55 -0
- package/dist/rich-text-editor/collab.js.map +1 -0
- package/dist/rich-text-editor/commands.d.ts +50 -0
- package/dist/rich-text-editor/commands.d.ts.map +1 -0
- package/dist/rich-text-editor/commands.js +376 -0
- package/dist/rich-text-editor/commands.js.map +1 -0
- package/dist/rich-text-editor/drag.d.ts +68 -0
- package/dist/rich-text-editor/drag.d.ts.map +1 -0
- package/dist/rich-text-editor/drag.js +148 -0
- package/dist/rich-text-editor/drag.js.map +1 -0
- package/dist/rich-text-editor/html.d.ts +145 -0
- package/dist/rich-text-editor/html.d.ts.map +1 -0
- package/dist/rich-text-editor/html.js +516 -0
- package/dist/rich-text-editor/html.js.map +1 -0
- package/dist/rich-text-editor/index.d.ts +263 -0
- package/dist/rich-text-editor/index.d.ts.map +1 -0
- package/dist/rich-text-editor/index.js +822 -0
- package/dist/rich-text-editor/index.js.map +1 -0
- package/dist/rich-text-editor/inline.d.ts +94 -0
- package/dist/rich-text-editor/inline.d.ts.map +1 -0
- package/dist/rich-text-editor/inline.js +252 -0
- package/dist/rich-text-editor/inline.js.map +1 -0
- package/dist/rich-text-editor/keymap.d.ts +71 -0
- package/dist/rich-text-editor/keymap.d.ts.map +1 -0
- package/dist/rich-text-editor/keymap.js +393 -0
- package/dist/rich-text-editor/keymap.js.map +1 -0
- package/dist/rich-text-editor/keys.d.ts +29 -0
- package/dist/rich-text-editor/keys.d.ts.map +1 -0
- package/dist/rich-text-editor/keys.js +93 -0
- package/dist/rich-text-editor/keys.js.map +1 -0
- package/dist/rich-text-editor/look.d.ts +71 -0
- package/dist/rich-text-editor/look.d.ts.map +1 -0
- package/dist/rich-text-editor/look.js +342 -0
- package/dist/rich-text-editor/look.js.map +1 -0
- package/dist/rich-text-editor/markdown.d.ts +18 -0
- package/dist/rich-text-editor/markdown.d.ts.map +1 -0
- package/dist/rich-text-editor/markdown.js +496 -0
- package/dist/rich-text-editor/markdown.js.map +1 -0
- package/dist/rich-text-editor/nodes.d.ts +135 -0
- package/dist/rich-text-editor/nodes.d.ts.map +1 -0
- package/dist/rich-text-editor/nodes.js +397 -0
- package/dist/rich-text-editor/nodes.js.map +1 -0
- package/dist/rich-text-editor/render.d.ts +68 -0
- package/dist/rich-text-editor/render.d.ts.map +1 -0
- package/dist/rich-text-editor/render.js +500 -0
- package/dist/rich-text-editor/render.js.map +1 -0
- package/dist/rich-text-editor/schema.d.ts +16 -0
- package/dist/rich-text-editor/schema.d.ts.map +1 -0
- package/dist/rich-text-editor/schema.js +386 -0
- package/dist/rich-text-editor/schema.js.map +1 -0
- package/dist/rich-text-editor/store.d.ts +11 -0
- package/dist/rich-text-editor/store.d.ts.map +1 -0
- package/dist/rich-text-editor/store.js +43 -0
- package/dist/rich-text-editor/store.js.map +1 -0
- package/dist/rich-text-editor/suggest.d.ts +127 -0
- package/dist/rich-text-editor/suggest.d.ts.map +1 -0
- package/dist/rich-text-editor/suggest.js +0 -0
- package/dist/rich-text-editor/suggest.js.map +1 -0
- package/dist/rich-text-editor/tables.d.ts +41 -0
- package/dist/rich-text-editor/tables.d.ts.map +1 -0
- package/dist/rich-text-editor/tables.js +204 -0
- package/dist/rich-text-editor/tables.js.map +1 -0
- package/dist/rich-text-editor/toolbar.d.ts +58 -0
- package/dist/rich-text-editor/toolbar.d.ts.map +1 -0
- package/dist/rich-text-editor/toolbar.js +548 -0
- package/dist/rich-text-editor/toolbar.js.map +1 -0
- package/dist/rich-text-editor/view.d.ts +368 -0
- package/dist/rich-text-editor/view.d.ts.map +1 -0
- package/dist/rich-text-editor/view.js +1968 -0
- package/dist/rich-text-editor/view.js.map +1 -0
- package/dist/rich-text-editor/virtual.d.ts +54 -0
- package/dist/rich-text-editor/virtual.d.ts.map +1 -0
- package/dist/rich-text-editor/virtual.js +262 -0
- package/dist/rich-text-editor/virtual.js.map +1 -0
- package/dist/richtext/node.d.ts +25 -2
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +46 -24
- package/dist/richtext/node.js.map +1 -1
- package/dist/terminal/backends.d.ts +4 -1
- package/dist/terminal/backends.d.ts.map +1 -1
- package/dist/terminal/backends.js.map +1 -1
- package/dist/terminal/index.d.ts +18 -7
- package/dist/terminal/index.d.ts.map +1 -1
- package/dist/terminal/index.js +28 -11
- package/dist/terminal/index.js.map +1 -1
- package/package.json +23 -4
- package/src/charts/node.ts +6 -2
- package/src/embed/client.ts +102 -8
- package/src/embed/index.ts +2 -0
- package/src/html/node.ts +7 -5
- package/src/index.ts +2 -0
- package/src/{markdown → internal/markdown}/ast.ts +24 -7
- package/src/{markdown → internal/markdown}/parse.ts +134 -24
- package/src/internal/markdown/stringify.ts +772 -0
- package/src/internal/scroll.ts +5 -3
- package/src/maps/anchors.ts +607 -0
- package/src/maps/controller.ts +999 -0
- package/src/maps/gl/buckets.ts +457 -0
- package/src/maps/gl/build-worker.ts +55 -0
- package/src/maps/gl/color.ts +62 -0
- package/src/maps/gl/cover.ts +170 -0
- package/src/maps/gl/index.ts +23 -0
- package/src/maps/gl/markers.ts +83 -0
- package/src/maps/gl/overlays.ts +379 -0
- package/src/maps/gl/pane.ts +42 -0
- package/src/maps/gl/placement.ts +816 -0
- package/src/maps/gl/renderer.ts +1576 -0
- package/src/maps/gl/shaders.ts +486 -0
- package/src/maps/gl/store.ts +566 -0
- package/src/maps/gl/text.ts +495 -0
- package/src/maps/gl/view.ts +1224 -0
- package/src/maps/index.ts +270 -81
- package/src/maps/labels.ts +214 -121
- package/src/maps/node.ts +763 -629
- package/src/maps/overlay.ts +148 -43
- package/src/maps/paint.ts +3 -0
- package/src/maps/proj.ts +31 -0
- package/src/maps/renderer.ts +155 -0
- package/src/maps/sources.ts +38 -3
- package/src/maps/theme.ts +51 -0
- package/src/maps/tiles.ts +191 -33
- package/src/maps/types.ts +188 -20
- package/src/markdown/expressions.ts +5 -1
- package/src/markdown/index.ts +14 -11
- package/src/markdown/spans.ts +1 -1
- package/src/media-player/index.ts +18 -4
- package/src/rich-text-editor/clipboard.ts +439 -0
- package/src/rich-text-editor/collab.ts +91 -0
- package/src/rich-text-editor/commands.ts +448 -0
- package/src/rich-text-editor/dom-globals.d.ts +23 -0
- package/src/rich-text-editor/drag.ts +211 -0
- package/src/rich-text-editor/html.ts +619 -0
- package/src/rich-text-editor/index.ts +1331 -0
- package/src/rich-text-editor/inline.ts +345 -0
- package/src/rich-text-editor/keymap.ts +510 -0
- package/src/rich-text-editor/keys.ts +119 -0
- package/src/rich-text-editor/look.ts +427 -0
- package/src/rich-text-editor/markdown.ts +653 -0
- package/src/rich-text-editor/nodes.ts +497 -0
- package/src/rich-text-editor/render.ts +800 -0
- package/src/rich-text-editor/schema.ts +431 -0
- package/src/rich-text-editor/store.ts +39 -0
- package/src/rich-text-editor/suggest.ts +597 -0
- package/src/rich-text-editor/tables.ts +272 -0
- package/src/rich-text-editor/toolbar.ts +724 -0
- package/src/rich-text-editor/view.ts +2287 -0
- package/src/rich-text-editor/virtual.ts +347 -0
- package/src/richtext/node.ts +59 -30
- package/src/terminal/backends.ts +4 -1
- package/src/terminal/index.ts +43 -15
- package/dist/markdown/ast.d.ts.map +0 -1
- package/dist/markdown/ast.js +0 -17
- package/dist/markdown/ast.js.map +0 -1
- package/dist/markdown/parse.d.ts.map +0 -1
- package/dist/markdown/parse.js.map +0 -1
- package/dist/markdown/tags.d.ts.map +0 -1
- package/dist/markdown/tags.js.map +0 -1
- /package/dist/{markdown → internal/markdown}/parse.d.ts +0 -0
- /package/dist/{markdown → internal/markdown}/tags.d.ts +0 -0
- /package/dist/{markdown → internal/markdown}/tags.js +0 -0
- /package/src/{markdown → internal/markdown}/tags.ts +0 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
// The two elements the editor registers.
|
|
2
|
+
//
|
|
3
|
+
// **`<richeditor>`** is the editor's root, and the reason it is an element
|
|
4
|
+
// rather than a `<box>` is the default-action seam (react-x11#266): keys,
|
|
5
|
+
// composition, focus and blur arrive at the focused node's `default*`
|
|
6
|
+
// methods *after* the application's handlers and not at all if one of them
|
|
7
|
+
// called `preventDefault()` — so `<RichTextEditor onKeyDown>` can veto or
|
|
8
|
+
// extend any editing key without knowing how the editor is built, exactly as
|
|
9
|
+
// `<textinput onKeyDown>` can. A `<box>` has no such methods to give. It is
|
|
10
|
+
// also what an assistive technology reads: the root answers
|
|
11
|
+
// `a11yTextState()` for the whole document, which is one text to a screen
|
|
12
|
+
// reader however many blocks draw it.
|
|
13
|
+
//
|
|
14
|
+
// Presses are the exception, and on purpose: a press lands on whatever is
|
|
15
|
+
// under the pointer — a paragraph, a list marker, the gap between two blocks —
|
|
16
|
+
// and core runs the default action of *that* node, not of the root. So the
|
|
17
|
+
// component takes presses in an ordinary bubbling `onMouseDown` on the root
|
|
18
|
+
// and `preventDefault()`s them, which also keeps core's drag-and-drop from
|
|
19
|
+
// arming on a press that is placing a caret — all but a press on the
|
|
20
|
+
// selection, which is left to core so that it can arm a drag of the
|
|
21
|
+
// selection from the root (./drag.ts).
|
|
22
|
+
//
|
|
23
|
+
// **`<richeditortext>`** is one textblock: `<richtext>` — its layout cache,
|
|
24
|
+
// its run decoration, its four text accessors — plus a selection band and a
|
|
25
|
+
// caret the editor owns and sets imperatively. Neither goes through React: a
|
|
26
|
+
// caret that blinks twice a second, or a drag that extends a selection sixty
|
|
27
|
+
// times a second, re-rendering anything would be the cost the retained node
|
|
28
|
+
// exists to avoid. The view says where they are; the node paints them and
|
|
29
|
+
// damages only what moved.
|
|
30
|
+
import { registerElement, registeredElements } from 'react-x11/host';
|
|
31
|
+
import { Node } from 'react-x11/node';
|
|
32
|
+
import type { A11yTextState } from 'react-x11/node';
|
|
33
|
+
import type { Rect } from 'react-x11';
|
|
34
|
+
|
|
35
|
+
import { RichTextNode } from '../richtext/node.js';
|
|
36
|
+
import type { NtkApp } from '../richtext/node.js';
|
|
37
|
+
import type { FillContext } from '../richtext/runs.js';
|
|
38
|
+
import { codeUnitOffsets, codePointAtOffset } from '../internal/text.js';
|
|
39
|
+
import type { InlineMap } from './inline.js';
|
|
40
|
+
|
|
41
|
+
/** The root element's name — registration key, `kind`, JSX tag. */
|
|
42
|
+
export const ROOT_ELEMENT = 'richeditor';
|
|
43
|
+
/** The textblock element's name. */
|
|
44
|
+
export const TEXT_ELEMENT = 'richeditortext';
|
|
45
|
+
|
|
46
|
+
type KeyEvent = Parameters<NonNullable<Node['defaultKeyDown']>>[0];
|
|
47
|
+
type ComposeEvent = Parameters<NonNullable<Node['defaultComposition']>>[0];
|
|
48
|
+
|
|
49
|
+
/** What a listener `view.dom.addEventListener` took is handed: a focus
|
|
50
|
+
* event, DOM-named, from the root element. */
|
|
51
|
+
export interface DomFocusEvent {
|
|
52
|
+
readonly type: 'focus' | 'blur' | 'focusin' | 'focusout';
|
|
53
|
+
readonly target: RichEditorNode;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* What the elements report to — the editor view (`./view.ts`). An interface
|
|
58
|
+
* so the nodes and the view can each be read, and tested, without the
|
|
59
|
+
* other.
|
|
60
|
+
*/
|
|
61
|
+
export interface EditorHost {
|
|
62
|
+
attachRoot(node: RichEditorNode): void;
|
|
63
|
+
detachRoot(node: RichEditorNode): void;
|
|
64
|
+
attachText(key: string, node: EditorTextNode): void;
|
|
65
|
+
detachText(key: string, node: EditorTextNode): void;
|
|
66
|
+
/** A mounted block drew something new — runs, a map, a node: whatever of
|
|
67
|
+
* the selection is in it has to be placed again. */
|
|
68
|
+
textUpdated(key: string, node: EditorTextNode): void;
|
|
69
|
+
keyDown(ev: KeyEvent): void;
|
|
70
|
+
composition(ev: ComposeEvent): void;
|
|
71
|
+
focusChanged(focused: boolean): void;
|
|
72
|
+
a11yText(): A11yTextState | null;
|
|
73
|
+
a11ySelect(start: number, end: number): boolean;
|
|
74
|
+
a11yReplace(start: number, end: number, text: string): boolean;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Register both elements. Called at `./index.ts`'s module scope — never
|
|
78
|
+
* here — so importing this module registers nothing. Idempotent, for the
|
|
79
|
+
* same lockfile-skew reason `registerRichText` is. */
|
|
80
|
+
export function registerEditorElements(): void {
|
|
81
|
+
if (!registeredElements().includes(ROOT_ELEMENT)) {
|
|
82
|
+
registerElement(ROOT_ELEMENT, {
|
|
83
|
+
create: (props, app) => new RichEditorNode(props, app),
|
|
84
|
+
semanticNames: ['host'],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (!registeredElements().includes(TEXT_ELEMENT)) {
|
|
88
|
+
registerElement(TEXT_ELEMENT, {
|
|
89
|
+
create: (props, app) => new EditorTextNode(props, app),
|
|
90
|
+
semanticNames: ['runs', 'wrap', 'map', 'blockKey', 'host', 'node'],
|
|
91
|
+
childrenAllowed: false,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function hostOf(props: Record<string, unknown> | undefined): EditorHost | null {
|
|
97
|
+
return (props?.host as EditorHost | undefined) ?? null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class RichEditorNode extends Node {
|
|
101
|
+
constructor(props: Record<string, unknown>, app: unknown) {
|
|
102
|
+
super(ROOT_ELEMENT, props, app as NtkApp);
|
|
103
|
+
// Nothing focuses an element that has not said it can be focused, and
|
|
104
|
+
// nothing types into one that is not focused. An app's `focusable`
|
|
105
|
+
// still overrides it, which is how `disabled` leaves the tab order.
|
|
106
|
+
this.focusableByDefault = true;
|
|
107
|
+
this.defaultCursor = 'text';
|
|
108
|
+
// The selection is the editor's own: a `selectable` document around it
|
|
109
|
+
// skips this subtree rather than lighting up half of what is being
|
|
110
|
+
// typed, and leaves its presses alone.
|
|
111
|
+
this.hasOwnSelection = true;
|
|
112
|
+
this.a11yRole = 'textbox';
|
|
113
|
+
hostOf(props)?.attachRoot(this);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get host(): EditorHost | null {
|
|
117
|
+
return hostOf(this.props);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
override applyProps(
|
|
121
|
+
nextProps: Record<string, unknown>,
|
|
122
|
+
prevProps: Record<string, unknown>,
|
|
123
|
+
): void {
|
|
124
|
+
const before = hostOf(prevProps ?? this.props);
|
|
125
|
+
super.applyProps(nextProps, prevProps);
|
|
126
|
+
const after = hostOf(nextProps);
|
|
127
|
+
if (after !== before) {
|
|
128
|
+
before?.detachRoot(this);
|
|
129
|
+
after?.attachRoot(this);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
override destroySubtree(): void {
|
|
134
|
+
this.host?.detachRoot(this);
|
|
135
|
+
super.destroySubtree();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Listeners a plugin written for a browser put on `view.dom` —
|
|
140
|
+
* y-prosemirror's cursor plugin listens for `focusin` and `focusout` to
|
|
141
|
+
* publish and withdraw the local caret. The focus family is all that is
|
|
142
|
+
* sent: `focus`, `blur`, `focusin`, `focusout`, after the view has heard
|
|
143
|
+
* the change, so `view.hasFocus()` already agrees with the event.
|
|
144
|
+
*/
|
|
145
|
+
private domEventListeners = new Map<
|
|
146
|
+
string,
|
|
147
|
+
Set<(ev: DomFocusEvent) => void>
|
|
148
|
+
>();
|
|
149
|
+
|
|
150
|
+
addEventListener(type: string, listener: (ev: DomFocusEvent) => void): void {
|
|
151
|
+
let set = this.domEventListeners.get(type);
|
|
152
|
+
if (!set) this.domEventListeners.set(type, (set = new Set()));
|
|
153
|
+
set.add(listener);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
removeEventListener(
|
|
157
|
+
type: string,
|
|
158
|
+
listener: (ev: DomFocusEvent) => void,
|
|
159
|
+
): void {
|
|
160
|
+
this.domEventListeners.get(type)?.delete(listener);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private sendDomEvent(type: DomFocusEvent['type']): void {
|
|
164
|
+
const set = this.domEventListeners.get(type);
|
|
165
|
+
if (!set) return;
|
|
166
|
+
for (const listener of [...set]) listener({ type, target: this });
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
override defaultKeyDown(ev: KeyEvent): void {
|
|
170
|
+
this.host?.keyDown(ev);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
override defaultComposition(ev: ComposeEvent): void {
|
|
174
|
+
this.host?.composition(ev);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
override defaultFocus(): void {
|
|
178
|
+
this.host?.focusChanged(true);
|
|
179
|
+
this.sendDomEvent('focus');
|
|
180
|
+
this.sendDomEvent('focusin');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
override defaultBlur(): void {
|
|
184
|
+
this.host?.focusChanged(false);
|
|
185
|
+
this.sendDomEvent('blur');
|
|
186
|
+
this.sendDomEvent('focusout');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
override a11yTextState(): A11yTextState | null {
|
|
190
|
+
return this.host?.a11yText() ?? null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
override a11ySetSelection(start: number, end: number): boolean {
|
|
194
|
+
return this.host?.a11ySelect(start, end) ?? false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
override a11yReplaceText(start: number, end: number, text: string): boolean {
|
|
198
|
+
return this.host?.a11yReplace(start, end, text) ?? false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function sameBand(
|
|
203
|
+
a: { start: number; end: number } | null,
|
|
204
|
+
b: { start: number; end: number } | null,
|
|
205
|
+
): boolean {
|
|
206
|
+
return a === b || (!!a && !!b && a.start === b.start && a.end === b.end);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export class EditorTextNode extends RichTextNode {
|
|
210
|
+
/** The selection band, in drawn code points, or null. */
|
|
211
|
+
private editorBand: { start: number; end: number } | null = null;
|
|
212
|
+
private editorBandColor = '';
|
|
213
|
+
/** The caret, as a drawn code point index, or null when it is elsewhere. */
|
|
214
|
+
private editorCaret: number | null = null;
|
|
215
|
+
private editorCaretColor = '';
|
|
216
|
+
private editorCaretOn = true;
|
|
217
|
+
/** Collaborators' carets in this block, in drawn code points. */
|
|
218
|
+
private remoteCarets: readonly { index: number; color: string }[] = [];
|
|
219
|
+
/** Where a drop would go in, in drawn code points, while a drag is over
|
|
220
|
+
* this block. */
|
|
221
|
+
private dropCaretAt: number | null = null;
|
|
222
|
+
private dropCaretColor = '';
|
|
223
|
+
|
|
224
|
+
constructor(props: Record<string, unknown>, app: unknown) {
|
|
225
|
+
super(props, app as NtkApp, TEXT_ELEMENT);
|
|
226
|
+
hostOf(props)?.attachText(String(props.blockKey ?? ''), this);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
get host(): EditorHost | null {
|
|
230
|
+
return hostOf(this.props);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** The block this node draws — the view's key for it. */
|
|
234
|
+
get blockKey(): string {
|
|
235
|
+
return String(this.props.blockKey ?? '');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/** How the drawn string maps back to the document. */
|
|
239
|
+
get map(): InlineMap | null {
|
|
240
|
+
return (this.props.map as InlineMap | undefined) ?? null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
override applyProps(
|
|
244
|
+
nextProps: Record<string, unknown>,
|
|
245
|
+
prevProps: Record<string, unknown>,
|
|
246
|
+
): void {
|
|
247
|
+
const before = prevProps ?? this.props;
|
|
248
|
+
const beforeHost = hostOf(before);
|
|
249
|
+
const beforeKey = String(before.blockKey ?? '');
|
|
250
|
+
super.applyProps(nextProps, prevProps);
|
|
251
|
+
const afterHost = hostOf(nextProps);
|
|
252
|
+
const afterKey = String(nextProps.blockKey ?? '');
|
|
253
|
+
if (afterHost !== beforeHost || afterKey !== beforeKey) {
|
|
254
|
+
beforeHost?.detachText(beforeKey, this);
|
|
255
|
+
afterHost?.attachText(afterKey, this);
|
|
256
|
+
} else if (
|
|
257
|
+
nextProps.map !== before.map ||
|
|
258
|
+
nextProps.runs !== before.runs ||
|
|
259
|
+
nextProps.node !== before.node
|
|
260
|
+
) {
|
|
261
|
+
afterHost?.textUpdated(afterKey, this);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
override destroySubtree(): void {
|
|
266
|
+
this.host?.detachText(this.blockKey, this);
|
|
267
|
+
super.destroySubtree();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// --- what the view sets ----------------------------------------------------
|
|
271
|
+
|
|
272
|
+
/** The selection band over this block, in drawn code points. */
|
|
273
|
+
setBand(band: { start: number; end: number } | null, color: string): void {
|
|
274
|
+
if (sameBand(band, this.editorBand) && color === this.editorBandColor)
|
|
275
|
+
return;
|
|
276
|
+
this.editorBand = band;
|
|
277
|
+
this.editorBandColor = color;
|
|
278
|
+
this.root?.invalidate(false, this, 'text');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/** Where the caret is in this block, or null to take it away. */
|
|
282
|
+
setCaret(index: number | null, color: string): void {
|
|
283
|
+
if (index === this.editorCaret && color === this.editorCaretColor) return;
|
|
284
|
+
this.damageCaret();
|
|
285
|
+
this.editorCaret = index;
|
|
286
|
+
this.editorCaretColor = color;
|
|
287
|
+
this.editorCaretOn = true;
|
|
288
|
+
this.damageCaret();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** One blink. Damages the caret's own rectangle and nothing else. */
|
|
292
|
+
blink(on: boolean): void {
|
|
293
|
+
if (this.editorCaret === null || on === this.editorCaretOn) return;
|
|
294
|
+
this.editorCaretOn = on;
|
|
295
|
+
this.damageCaret();
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private caretBox(): Rect | null {
|
|
299
|
+
if (this.editorCaret === null) return null;
|
|
300
|
+
const r = this.textCaretRect(this.editorCaret);
|
|
301
|
+
if (!r) return null;
|
|
302
|
+
const w = Math.max(1, Math.round(this.scale));
|
|
303
|
+
return {
|
|
304
|
+
x: Math.round(r.x),
|
|
305
|
+
y: Math.round(r.y),
|
|
306
|
+
width: w,
|
|
307
|
+
height: Math.ceil(r.height),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private damageCaret(): void {
|
|
312
|
+
const box = this.caretBox();
|
|
313
|
+
if (!box) return;
|
|
314
|
+
const pad = Math.ceil(this.scale);
|
|
315
|
+
this.root?.invalidate(
|
|
316
|
+
false,
|
|
317
|
+
{
|
|
318
|
+
x: box.x - pad,
|
|
319
|
+
y: box.y,
|
|
320
|
+
width: box.width + pad * 2,
|
|
321
|
+
height: box.height,
|
|
322
|
+
},
|
|
323
|
+
'caret',
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/** Collaborators' carets in this block — set by the view like the
|
|
328
|
+
* editor's own, and drawn beside it without blinking. */
|
|
329
|
+
setRemoteCarets(carets: readonly { index: number; color: string }[]): void {
|
|
330
|
+
const was = this.remoteCarets;
|
|
331
|
+
if (
|
|
332
|
+
carets.length === was.length &&
|
|
333
|
+
carets.every(
|
|
334
|
+
(c, i) => c.index === was[i].index && c.color === was[i].color,
|
|
335
|
+
)
|
|
336
|
+
)
|
|
337
|
+
return;
|
|
338
|
+
this.damageRemoteCarets();
|
|
339
|
+
this.remoteCarets = carets;
|
|
340
|
+
this.damageRemoteCarets();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** The collaborators' carets this block draws. */
|
|
344
|
+
get collaboratorCarets(): readonly { index: number; color: string }[] {
|
|
345
|
+
return this.remoteCarets;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/** A collaborator's caret: a bar two pixels wide, and a flag beside
|
|
349
|
+
* its top — inside the line, so nothing paints outside the block. */
|
|
350
|
+
private remoteCaretBoxes(index: number): { bar: Rect; flag: Rect } | null {
|
|
351
|
+
const r = this.textCaretRect(index);
|
|
352
|
+
if (!r) return null;
|
|
353
|
+
const s = Math.max(1, Math.round(this.scale));
|
|
354
|
+
const bar = {
|
|
355
|
+
x: Math.round(r.x) - s,
|
|
356
|
+
y: Math.round(r.y),
|
|
357
|
+
width: 2 * s,
|
|
358
|
+
height: Math.ceil(r.height),
|
|
359
|
+
};
|
|
360
|
+
const flag = {
|
|
361
|
+
x: bar.x + bar.width,
|
|
362
|
+
y: bar.y,
|
|
363
|
+
width: 3 * s,
|
|
364
|
+
height: 3 * s,
|
|
365
|
+
};
|
|
366
|
+
return { bar, flag };
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private damageRemoteCarets(): void {
|
|
370
|
+
for (const caret of this.remoteCarets) {
|
|
371
|
+
const boxes = this.remoteCaretBoxes(caret.index);
|
|
372
|
+
if (!boxes) continue;
|
|
373
|
+
const { bar, flag } = boxes;
|
|
374
|
+
this.root?.invalidate(
|
|
375
|
+
false,
|
|
376
|
+
{
|
|
377
|
+
x: bar.x - 1,
|
|
378
|
+
y: bar.y,
|
|
379
|
+
width: bar.width + flag.width + 2,
|
|
380
|
+
height: bar.height,
|
|
381
|
+
},
|
|
382
|
+
'caret',
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** The drop caret: where a drop would go in, or null to take it away. */
|
|
388
|
+
setDropCaret(index: number | null, color: string): void {
|
|
389
|
+
if (index === this.dropCaretAt && color === this.dropCaretColor) return;
|
|
390
|
+
this.damageDropCaret();
|
|
391
|
+
this.dropCaretAt = index;
|
|
392
|
+
this.dropCaretColor = color;
|
|
393
|
+
this.damageDropCaret();
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/** Where this block draws the drop caret, or null. */
|
|
397
|
+
get dropCaret(): number | null {
|
|
398
|
+
return this.dropCaretAt;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private dropCaretBox(): Rect | null {
|
|
402
|
+
if (this.dropCaretAt === null) return null;
|
|
403
|
+
const r = this.textCaretRect(this.dropCaretAt);
|
|
404
|
+
if (!r) return null;
|
|
405
|
+
const s = Math.max(1, Math.round(this.scale));
|
|
406
|
+
return {
|
|
407
|
+
x: Math.round(r.x) - s,
|
|
408
|
+
y: Math.round(r.y),
|
|
409
|
+
width: 2 * s,
|
|
410
|
+
height: Math.ceil(r.height),
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
private damageDropCaret(): void {
|
|
415
|
+
const box = this.dropCaretBox();
|
|
416
|
+
if (!box) return;
|
|
417
|
+
this.root?.invalidate(
|
|
418
|
+
false,
|
|
419
|
+
{ x: box.x - 1, y: box.y, width: box.width + 2, height: box.height },
|
|
420
|
+
'caret',
|
|
421
|
+
);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// --- painting ----------------------------------------------------------------
|
|
425
|
+
|
|
426
|
+
protected override paintSelection(ctx: FillContext): void {
|
|
427
|
+
const band = this.editorBand;
|
|
428
|
+
if (!band || band.end <= band.start || !this.editorBandColor) return;
|
|
429
|
+
ctx.fillStyle = this.editorBandColor;
|
|
430
|
+
for (const r of this.textRangeRects(band.start, band.end)) {
|
|
431
|
+
ctx.fillRect(
|
|
432
|
+
Math.round(r.x),
|
|
433
|
+
Math.round(r.y),
|
|
434
|
+
Math.ceil(r.width),
|
|
435
|
+
Math.ceil(r.height),
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
protected override paintOverlay(ctx: FillContext): void {
|
|
441
|
+
for (const caret of this.remoteCarets) {
|
|
442
|
+
const boxes = this.remoteCaretBoxes(caret.index);
|
|
443
|
+
if (!boxes) continue;
|
|
444
|
+
ctx.fillStyle = caret.color;
|
|
445
|
+
const { bar, flag } = boxes;
|
|
446
|
+
ctx.fillRect(bar.x, bar.y, bar.width, bar.height);
|
|
447
|
+
ctx.fillRect(flag.x, flag.y, flag.width, flag.height);
|
|
448
|
+
}
|
|
449
|
+
const drop = this.dropCaretBox();
|
|
450
|
+
if (drop && this.dropCaretColor) {
|
|
451
|
+
ctx.fillStyle = this.dropCaretColor;
|
|
452
|
+
ctx.fillRect(drop.x, drop.y, drop.width, drop.height);
|
|
453
|
+
}
|
|
454
|
+
if (!this.editorCaretOn) return;
|
|
455
|
+
const box = this.caretBox();
|
|
456
|
+
if (!box || !this.editorCaretColor) return;
|
|
457
|
+
ctx.fillStyle = this.editorCaretColor;
|
|
458
|
+
ctx.fillRect(box.x, box.y, box.width, box.height);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// --- lines -------------------------------------------------------------------
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* The drawn code points of the visual line holding `index`: where Home and
|
|
465
|
+
* End go. Read off the same layout the glyphs came from, so a wrapped line
|
|
466
|
+
* ends where it is seen to end. Null on the mock backend, which lays
|
|
467
|
+
* nothing out.
|
|
468
|
+
*/
|
|
469
|
+
lineRange(
|
|
470
|
+
index: number,
|
|
471
|
+
): { start: number; end: number; last: boolean } | null {
|
|
472
|
+
const layout = this.paintLayout();
|
|
473
|
+
if (!layout || layout.lines.length === 0) return null;
|
|
474
|
+
const text = this.textContent();
|
|
475
|
+
const offsets = codeUnitOffsets(text);
|
|
476
|
+
const unit = offsets[Math.max(0, Math.min(index, offsets.length - 1))];
|
|
477
|
+
const lines = layout.lines;
|
|
478
|
+
let at = lines.length - 1;
|
|
479
|
+
for (let i = 0; i < lines.length; i++) {
|
|
480
|
+
if (unit < lines[i].end || i === lines.length - 1) {
|
|
481
|
+
at = i;
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
const line = lines[at];
|
|
486
|
+
const last = at === lines.length - 1;
|
|
487
|
+
let end = line.end;
|
|
488
|
+
// a wrapped line's trailing space belongs to it but the caret after it
|
|
489
|
+
// is drawn on the next line — End stops before it
|
|
490
|
+
if (!last && end > line.start && /\s/.test(text[end - 1] ?? '')) end -= 1;
|
|
491
|
+
return {
|
|
492
|
+
start: codePointAtOffset(offsets, line.start),
|
|
493
|
+
end: codePointAtOffset(offsets, end),
|
|
494
|
+
last,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
}
|