foldkit-richtext-dom 0.4.1 → 0.5.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 +100 -29
- package/dist/editor-bundle.d.mts +1659 -79
- package/dist/editor-bundle.d.mts.map +1 -1
- package/dist/editor-bundle.mjs +114 -10
- package/dist/editor-bundle.mjs.map +1 -1
- package/dist/{editor-DbYswARG.d.mts → editor-gCZiXgG4.d.mts} +406 -11
- package/dist/editor-gCZiXgG4.d.mts.map +1 -0
- package/dist/editor.d.mts +2 -2
- package/dist/editor.mjs +179 -25
- package/dist/editor.mjs.map +1 -1
- package/dist/{events-Dasls6a_.d.mts → events-DLi6ecvD.d.mts} +20 -5
- package/dist/events-DLi6ecvD.d.mts.map +1 -0
- package/dist/events.d.mts +1 -1
- package/dist/events.mjs +334 -15
- package/dist/events.mjs.map +1 -1
- package/dist/filler-D2_wc_8Z.mjs +8 -0
- package/dist/filler-D2_wc_8Z.mjs.map +1 -0
- package/dist/{host-1dtxuKIA.d.mts → host-Cp2Zz-bO.d.mts} +27 -8
- package/dist/host-Cp2Zz-bO.d.mts.map +1 -0
- package/dist/host.d.mts +2 -2
- package/dist/host.mjs +25 -8
- package/dist/host.mjs.map +1 -1
- package/dist/html.d.mts.map +1 -1
- package/dist/html.mjs +2 -0
- package/dist/html.mjs.map +1 -1
- package/dist/index-DDV_JesZ.d.mts.map +1 -1
- package/dist/index.mjs +52 -19
- package/dist/index.mjs.map +1 -1
- package/dist/input.d.mts +259 -3
- package/dist/input.d.mts.map +1 -1
- package/dist/input.mjs +2 -1
- package/dist/input.mjs.map +1 -1
- package/dist/toolbar.d.mts +7 -0
- package/dist/toolbar.d.mts.map +1 -1
- package/dist/toolbar.mjs +135 -14
- package/dist/toolbar.mjs.map +1 -1
- package/dist/view.d.mts.map +1 -1
- package/dist/view.mjs +26 -2
- package/dist/view.mjs.map +1 -1
- package/package.json +4 -4
- package/dist/editor-DbYswARG.d.mts.map +0 -1
- package/dist/events-Dasls6a_.d.mts.map +0 -1
- package/dist/host-1dtxuKIA.d.mts.map +0 -1
package/README.md
CHANGED
|
@@ -98,20 +98,33 @@ What each part does, and does not do:
|
|
|
98
98
|
|
|
99
99
|
`editorAt(hostId, placement)` takes, each optional:
|
|
100
100
|
|
|
101
|
-
- **`rendering`**: how marks and node kinds become elements
|
|
101
|
+
- **`rendering`**: how marks and node kinds become elements; the default renders the
|
|
102
102
|
core's own names. A mark renders *inside* its run element, so the run stays outermost and a
|
|
103
103
|
selection still maps.
|
|
104
104
|
- **`vocabulary`**: `{ marks, nodes }` registries. The editor refuses an edit a declaration
|
|
105
|
-
forbids, rather than only letting `validate` report it later
|
|
105
|
+
forbids, rather than only letting `validate` report it later.
|
|
106
106
|
- **`inputRules`**: rules applied to what is typed, so a marker turns into a block change as it
|
|
107
|
-
is completed
|
|
107
|
+
is completed, such as `foldkit-richtext-markdown`'s `markdownInputRules`. A rule the
|
|
108
108
|
vocabulary refuses does not cost the keystroke: the text is inserted on its own.
|
|
109
109
|
- **`decorate`**: `document => DecorationSet`, drawn over the document on the mount and every
|
|
110
|
-
patch
|
|
110
|
+
patch, code highlighting for one. It sees the document only. A highlight derived
|
|
111
111
|
from application state, such as a search query or other people's carets, goes through
|
|
112
112
|
the `overlay(hostId, decorations)` Command instead. It draws them after the placement's
|
|
113
113
|
own and leaves the caret where it is. Its positions name the document shown, so set it
|
|
114
114
|
again when that changes; placing the host id again starts it with none.
|
|
115
|
+
`focus(hostId, selection)` is the Command that sends the reader into the text from a control
|
|
116
|
+
outside it (a table of contents, a find bar closing on its match): the editor takes the focus
|
|
117
|
+
with that selection, scrolled to the middle of the window when it is out of view. The browser
|
|
118
|
+
reports the selection back as `Selected`.
|
|
119
|
+
- **`keymap`**: chords beside the built-in ones, checked first, as `{ chord, command }`
|
|
120
|
+
(`Mod-u` toggling an application's `Underline`, say). `Mod` is Ctrl or Cmd.
|
|
121
|
+
- **`slashEntries`**: what its slash menu offers, which Enter in a query resolves against: an
|
|
122
|
+
application's own catalogue (an image, a table, a callout), beside or instead of the
|
|
123
|
+
editor's `slashEntries`. The application's menu view draws the same list.
|
|
124
|
+
- An empty line holds a `<br data-richtext-filler>` after its run. A browser can neither draw a
|
|
125
|
+
caret in an empty text node nor move one there with the arrow keys, so without it a new line
|
|
126
|
+
made with Enter had no height and could not be reached. The caret on an empty line stands
|
|
127
|
+
before it; it holds no text or position, server markup draws it too, and an import drops it.
|
|
115
128
|
- **`placeholder`**: what a blank document shows (`RichText.isBlank`). It is put on the block as
|
|
116
129
|
`data-placeholder`, and the root, a `role="textbox"`, carries it as `aria-placeholder`.
|
|
117
130
|
Drawing it is the stylesheet's, so the text never enters the content:
|
|
@@ -126,6 +139,10 @@ What each part does, and does not do:
|
|
|
126
139
|
}
|
|
127
140
|
```
|
|
128
141
|
|
|
142
|
+
- **`lineHint`**: what the empty paragraph a collapsed caret is in shows, such as "Type / for
|
|
143
|
+
blocks", put on it as `data-hint` and moved as the caret moves. A blank document's block shows
|
|
144
|
+
the placeholder instead. Draw it like the placeholder, under `:focus` so it shows only while
|
|
145
|
+
the editor is being written in: `[contenteditable]:focus [data-hint]::before`.
|
|
129
146
|
- **`serverRendered`**: whether a render is the server's; see "On a server-rendered page".
|
|
130
147
|
|
|
131
148
|
Each has a record at `foldkit-richtext-dom/host` (`placeRendering`/`renderingFor`,
|
|
@@ -192,12 +209,12 @@ submitted, and reported by `authoredChanged` like any other.
|
|
|
192
209
|
- **Filling:** `fill` shows a given document with a history of its own and no selection, since
|
|
193
210
|
the caret was in runs the given document does not have.
|
|
194
211
|
- **Resuming:** `settled`, for a stored draft shown again, keeps the caret, the stored marks,
|
|
195
|
-
and the history, clears only the slash menu's highlight, and takes this placement's host id
|
|
212
|
+
and the history, clears only the slash menu's highlight and dismissal, and takes this placement's host id
|
|
196
213
|
in case the draft was stored under another.
|
|
197
214
|
|
|
198
215
|
## Chrome around the editor
|
|
199
216
|
|
|
200
|
-
The editor renders its host and nothing else
|
|
217
|
+
The editor renders its host and nothing else. A toolbar, a menu, a handle: each is the
|
|
201
218
|
application's chrome, drawn beside the host from the Model, sending the editor's Messages.
|
|
202
219
|
This package ships the plain versions below; `foldkit-mixins-richtext` draws the same chrome
|
|
203
220
|
as slot views an application restyles.
|
|
@@ -237,14 +254,20 @@ a caret. The Mount only moves it. It places the element `gap` pixels above the p
|
|
|
237
254
|
selection, centred and kept inside the window's width, or below when there is no room above,
|
|
238
255
|
and again on `selectionchange`, any scroll, and resize; a selection outside the host is
|
|
239
256
|
ignored. It writes `position: fixed`, `top`, `left`, and `data-placement` (`top` or `bottom`)
|
|
240
|
-
on the element, so a style there must leave those alone.
|
|
257
|
+
on the element, so a style there must leave those alone. With `caret: true` it follows a caret
|
|
258
|
+
too, opening under it (above when there is no room below), which is where a slash menu goes; a
|
|
259
|
+
caret in an empty run is read from the element it is in.
|
|
241
260
|
|
|
242
261
|
### The block handle: anchor and drag
|
|
243
262
|
|
|
244
263
|
`blockAnchor({ hostId, node, gap })` places an element `gap` pixels left of block `node`, level
|
|
245
|
-
with its top edge and never past the window's left edge
|
|
246
|
-
|
|
247
|
-
|
|
264
|
+
with its top edge and never past the window's left edge; with `side: 'above'` it goes `gap`
|
|
265
|
+
pixels above the block, at its left edge, or just inside its top when there is no room above,
|
|
266
|
+
which is where a block's toolbar (a table's) goes; with `side: 'inside-end'` it goes inside the
|
|
267
|
+
block's top right corner, for a toolbar that should cover nothing around the block (a code
|
|
268
|
+
block's, over its own header). It places it again when the editor's subtree changes (a patch can
|
|
269
|
+
move a block without the page scrolling), on scroll, on resize, and when the element's own size
|
|
270
|
+
changes, and it finds a host drawn after it, or drawn again in the same parent.
|
|
248
271
|
|
|
249
272
|
`blockDrag({ hostId, node })` drags block `node` by the element it is mounted on, to any block
|
|
250
273
|
`RichText.moveTargets` allows under the placed vocabulary, so an item can go into another list
|
|
@@ -254,8 +277,14 @@ but not to the top level:
|
|
|
254
277
|
bottom. Where a container's edge meets its first or last block's, the block's is picked while
|
|
255
278
|
the pointer is inside it, and the container's once the pointer is past it.
|
|
256
279
|
- A line, `[data-richtext-drop]`, is fixed at that edge, outside the editable subtree, for a
|
|
257
|
-
stylesheet to draw
|
|
258
|
-
|
|
280
|
+
stylesheet to draw: it has no size or color of its own, so a page that styles nothing shows
|
|
281
|
+
no feedback at all. Once the pointer moves, the dragged block carries
|
|
282
|
+
`data-richtext-dragging` until the drag ends, and the page's root element
|
|
283
|
+
`data-richtext-drag`, for a `grabbing` cursor wherever the pointer goes. The place is read again on a scroll and on
|
|
284
|
+
release, where the blocks are then, and release sends `MovedBlock` there.
|
|
285
|
+
- Held within 48 pixels of the window's top or bottom, the pointer scrolls the page a frame at
|
|
286
|
+
a time, faster the nearer the edge (`edgeScroll`), so a block can be dragged past what the
|
|
287
|
+
window shows.
|
|
259
288
|
- Escape, a cancelled pointer, a move with the button up (a release the page never heard, over
|
|
260
289
|
a frame say), or a place where the block already is ends it with nothing sent.
|
|
261
290
|
- Only the pointer that pressed steers or ends it. The page is listened to only during a drag,
|
|
@@ -268,7 +297,7 @@ by the block (`h.Key(node)`) when the block it stands for can change.
|
|
|
268
297
|
### The slash menu
|
|
269
298
|
|
|
270
299
|
The editor owns the slash menu's vocabulary, because an entry is one of its own Messages and
|
|
271
|
-
the Bundle resolves Enter by handling the chosen entry as that Message
|
|
300
|
+
the Bundle resolves Enter by handling the chosen entry as that Message:
|
|
272
301
|
|
|
273
302
|
```ts
|
|
274
303
|
import { slashEntries, slashMenu, slashQuery } from 'foldkit-richtext-dom/editor'
|
|
@@ -280,18 +309,29 @@ slashMenu(slashEntries, 'see /head', 0)?.highlighted?.label // 'Heading 1'
|
|
|
280
309
|
- **`slashEntries`** leads with the text blocks a caret can become (`Paragraph`,
|
|
281
310
|
`Heading 1`–`3`), then the standard containers (`Quote`, `Bulleted list`, `Numbered list`,
|
|
282
311
|
`Code block`, whose id is `code-block` because `code` is the Code mark's), then the marks.
|
|
283
|
-
Each has a stable `id`, a `label`, the words a query may also match, and its Message
|
|
312
|
+
Each has a stable `id`, a `label`, the words a query may also match, and its Message, and
|
|
313
|
+
for a menu to draw, a `description`, the `group` it is listed under (Text, Lists, Blocks,
|
|
314
|
+
Formatting), and an `icon` by name.
|
|
284
315
|
- **`slashMenu(entries, textBefore, index)`** is the one value a view and `update` share:
|
|
285
316
|
whether a query is open, what matches, and what Enter would send. A stale index falls back to
|
|
286
|
-
the first match.
|
|
287
|
-
|
|
317
|
+
the first match. The text a query is read from is the document's, so typing a slash opens
|
|
318
|
+
the menu and leaving the query closes it, with no switch to keep in step.
|
|
288
319
|
- **`EditorState.menuIndex`** is the one thing a menu owns. The application moves it (with
|
|
289
320
|
`slashMove` from `foldkit-mixins-richtext`), and on `Entered` the Bundle treats the
|
|
290
321
|
highlighted entry as the Message a click would send. Choosing an entry also removes the
|
|
291
|
-
query typed for it, as one action
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
a
|
|
322
|
+
query typed for it, as one action: one transition and one undo step.
|
|
323
|
+
- **`Message.DismissedSlash`** is Escape over the menu. `EditorState.slashDismissed` keeps the
|
|
324
|
+
slash of the query it closed, so the menu stays shut while that query is typed on and Enter
|
|
325
|
+
splits the line, and it ends when the query does: a slash typed later opens a menu again.
|
|
326
|
+
`menuTextOf(view)` from `foldkit-richtext-dom/editor-bundle` is the text a menu reads, empty
|
|
327
|
+
while dismissed, and the Bundle's Enter reads it too, so a view built on it agrees with Enter.
|
|
328
|
+
|
|
329
|
+
`matchingEntries` decides the order. With no query it lists every entry with each group's
|
|
330
|
+
entries together, in the order the group first appears; with one, the best matches first (a
|
|
331
|
+
label the query starts, then a word of a label, then a keyword, then any containing it). The
|
|
332
|
+
keys move through that order and a menu draws it, so they cannot disagree. It, `slashQuery`, and
|
|
333
|
+
`slashMenu` work over any entries carrying a `message`, so an application's own catalogue reuses
|
|
334
|
+
them.
|
|
295
335
|
|
|
296
336
|
## Showing a document: the read-only renderer
|
|
297
337
|
|
|
@@ -306,14 +346,14 @@ renderDocument(document, renderer) // a declared Link as a real <a href>
|
|
|
306
346
|
renderDocument(document, renderer, decorations) // a decoration overlaid on the runs it covers
|
|
307
347
|
```
|
|
308
348
|
|
|
309
|
-
It takes the same `rendering(...)` registry as the HTML serializer and the editor
|
|
349
|
+
It takes the same `rendering(...)` registry as the HTML serializer and the editor, so a
|
|
310
350
|
declared mark or kind becomes the same element everywhere, custom elements included. Without
|
|
311
351
|
one, blocks become `p`/`h1`–`h6`, marks nest as `strong`/`em`/`code` in the serializer's order,
|
|
312
352
|
an undeclared mark rides on a `span` with `data-marks`, and an unknown block renders as an
|
|
313
353
|
inert `div data-unknown="Type"` placeholder. A tag Foldkit cannot build is reported, never
|
|
314
354
|
swapped for another element.
|
|
315
355
|
|
|
316
|
-
A decoration set
|
|
356
|
+
A decoration set is projected with `RichText.decorationsIn`: each covered piece of
|
|
317
357
|
a run becomes a `span[data-decoration=<kind>]` with the run's marks inside it, which is also how
|
|
318
358
|
the editor draws one, so one stylesheet serves both. Each string field of the decoration's
|
|
319
359
|
`data` whose name is lowercase letters, digits, and hyphens is drawn as
|
|
@@ -353,12 +393,16 @@ cases (each built as `Message.Typed({ text })` and so on, not exported one by on
|
|
|
353
393
|
|
|
354
394
|
```text
|
|
355
395
|
Typed, Backspace, DeletedForward, Entered, ToggledMark, AppliedMark, ClearedMark,
|
|
356
|
-
RetypedBlock, WrappedBlock, ConvertedBlock, LiftedBlock, MovedBlock,
|
|
357
|
-
|
|
396
|
+
RetypedBlock, WrappedBlock, ConvertedBlock, LiftedBlock, MovedBlock, InsertedBlock,
|
|
397
|
+
DeletedBlock, InsertedRow, InsertedColumn, DeletedRow, DeletedColumn, Indented, Outdented,
|
|
398
|
+
ReplacedText, InsertedFootnote, RenamedFootnote, ActivatedAdornment, DismissedSlash,
|
|
399
|
+
ChangedProps, Selected, Pasted, Undone, Redone, Patched
|
|
358
400
|
```
|
|
359
401
|
|
|
360
402
|
The adapter reports what the browser did as commands, a caret, and history chords, and
|
|
361
|
-
`toMessage` turns each into a Message.
|
|
403
|
+
`toMessage` turns each into a Message. Its chords are Mod+B, Mod+I and Mod+E for Bold, Italic
|
|
404
|
+
and Code, Mod+Shift+S for Strikethrough (Mod+S alone is left to the browser), and Mod+Z,
|
|
405
|
+
Mod+Shift+Z and Mod+Y for undo and redo; a placement's `keymap` adds its own, checked first. It refuses what the vocabulary cannot carry rather than
|
|
362
406
|
dropping a detail: an insertion carrying marks, or a mark value with props, comes back
|
|
363
407
|
`undefined`. The rest are the application's to send, because no browser event means them:
|
|
364
408
|
|
|
@@ -366,6 +410,21 @@ dropping a detail: an insertion carrying marks, or a mark value with props, come
|
|
|
366
410
|
`LiftedBlock` for the core's block commands;
|
|
367
411
|
- `MovedBlock` (`{ node, to }`, `to` a `RichText.Beside`), which a block handle sends and which
|
|
368
412
|
needs no selection;
|
|
413
|
+
- `InsertedBlock` (`{ block, to? }`) and `DeletedBlock` (`{ node }`) for `InsertBlock` and
|
|
414
|
+
`DeleteBlock`: an insert menu's image or table, a handle's Duplicate and Delete;
|
|
415
|
+
- `ChangedProps` (`{ node, props }`) for `SetProps`. The adapter sends it too, for a click on a
|
|
416
|
+
block's adornment that toggles a prop (a task's checkbox, from `ElementRendering.adornments`);
|
|
417
|
+
- `InsertedRow` and `InsertedColumn` (`{ where }`), `DeletedRow` and `DeletedColumn` for the
|
|
418
|
+
table commands, which act on the caret's cell: what a table toolbar sends;
|
|
419
|
+
- `Indented` and `Outdented` for `Indent` and `Outdent`. The adapter sends them for Tab and
|
|
420
|
+
Shift+Tab, but only where `RichText.takesTab` says Tab means something (code, a table cell,
|
|
421
|
+
under the placement's vocabulary); anywhere else Tab is left to move focus;
|
|
422
|
+
- `ReplacedText` (`{ query, replacement, caseSensitive?, at? }`) for `Replace`: one match, or
|
|
423
|
+
every match, as one undo step, which a find bar sends;
|
|
424
|
+
- `InsertedFootnote` (`{ text? }`) and `RenamedFootnote` (`{ from, to }`) for `InsertFootnote` and
|
|
425
|
+
`RenameFootnote`, which need `FootnoteRef` in the placement's vocabulary;
|
|
426
|
+
- `ActivatedAdornment` (`{ node, action }`): an adornment with an `action` was clicked, a code
|
|
427
|
+
block's Copy, say. It is no edit, and the editor changes nothing; the application acts on it;
|
|
369
428
|
- `AppliedMark` (`{ mark }`, a name or a `{ name, props }` value) and `ClearedMark` (`{ mark }`)
|
|
370
429
|
for `SetMark` and `ClearMark`: what a link editor sends, which at a caret changes or removes
|
|
371
430
|
the whole link the caret is in.
|
|
@@ -418,7 +477,19 @@ that transition committed.
|
|
|
418
477
|
focus. A keystroke, a composition start, a cut, or a paste reads the live selection first and
|
|
419
478
|
reports it before its intent, because `selectionchange` is asynchronous and a click just
|
|
420
479
|
before would otherwise be missed. `keymap` adds or overrides chord bindings, checked
|
|
421
|
-
before the built-in ones.
|
|
480
|
+
before the built-in ones. A press on an atom (an image, a divider) selects it as a node
|
|
481
|
+
selection; the browser is given the range around its element, which reads back as that
|
|
482
|
+
selection, and the block carries `data-selected`, the only sign of it a stylesheet can
|
|
483
|
+
reach. An arrow that leaves a block for an atom beside it (Right at its end, Down from its
|
|
484
|
+
last line) selects the atom rather than stepping over it, and from a selected atom an arrow
|
|
485
|
+
goes to the text beside it. A press on an adornment (a task's checkbox, a code block's Copy)
|
|
486
|
+
keeps the caret where it was, so writing goes on there. A third press selects its block's
|
|
487
|
+
text, where the browser's selection runs on to the next block and typing over it would join
|
|
488
|
+
that block too. In code (a kind declared `lineBreaks`), the first Mod+A selects the code and
|
|
489
|
+
a second the document. A press in the space below
|
|
490
|
+
the last block puts the caret at the end of a last paragraph or heading, and after anything
|
|
491
|
+
else (a table, code, an image) opens a paragraph there, which no caret could reach.
|
|
492
|
+
`attachment.sync(state, changeSet)` patches and restores the
|
|
422
493
|
selection in one call, but only while the browser's selection is inside the editor: a sync
|
|
423
494
|
never takes the caret from another editor or an input. While an IME is composing, the latest
|
|
424
495
|
state waits and is drawn when composition ends, so another person's edit never rewrites the
|
|
@@ -429,7 +500,7 @@ that transition committed.
|
|
|
429
500
|
`decorations` is the next render's whole set, so a run whose share of it changed is redrawn,
|
|
430
501
|
and a patch without one clears what was drawn. An element that is no longer what its block
|
|
431
502
|
renders as (a heading re-leveled to `h3`, say) is re-rendered rather than kept.
|
|
432
|
-
- **`repair(dom, content)`** is recovery, not domain state
|
|
503
|
+
- **`repair(dom, content)`** is recovery, not domain state: it drops what the subtree
|
|
433
504
|
holds that the document does not, re-renders blocks whose rendering drifted, and returns the
|
|
434
505
|
same `EditorDom` when nothing was wrong. Call it after a cancelled IME.
|
|
435
506
|
- **`positionToRange` and `rangeToPosition`** translate between a semantic `Position` and a DOM
|
|
@@ -440,7 +511,7 @@ that transition committed.
|
|
|
440
511
|
|
|
441
512
|
A Foldkit view owns the host element; the interpreter owns what goes inside it. `mountInto`
|
|
442
513
|
renders into the host and records the attachment there, so a patch Command that has only the
|
|
443
|
-
element can find it
|
|
514
|
+
element can find it:
|
|
444
515
|
|
|
445
516
|
```ts
|
|
446
517
|
import { attachmentIn, mountInto, releaseMount } from 'foldkit-richtext-dom/host'
|
|
@@ -463,7 +534,7 @@ Anything else in the host is replaced, so it ends with one subtree either way.
|
|
|
463
534
|
`renderEditable(document, renderer?, decorations?)` is the same subtree as `Html`, which is
|
|
464
535
|
what a server sends.
|
|
465
536
|
|
|
466
|
-
`attachEditor(host, content, emit, { rendering?, decorate?, placeholder? })` attaches the
|
|
537
|
+
`attachEditor(host, content, emit, { rendering?, decorate?, placeholder?, lineHint? })` attaches the
|
|
467
538
|
translation to Messages to a host element; `events({ content })` wraps it as the Mount the
|
|
468
539
|
editor's view uses; `patchEditor(hostId, state, changeSet)` is what the patch Command runs,
|
|
469
540
|
reporting whether it patched. A missing host is an editor that went away while the transition
|