foldkit-richtext-dom 0.4.1 → 0.6.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.
Files changed (43) hide show
  1. package/README.md +100 -29
  2. package/dist/{editor-DbYswARG.d.mts → editor-Cmo24yol.d.mts} +408 -11
  3. package/dist/editor-Cmo24yol.d.mts.map +1 -0
  4. package/dist/editor-bundle.d.mts +1659 -79
  5. package/dist/editor-bundle.d.mts.map +1 -1
  6. package/dist/editor-bundle.mjs +134 -14
  7. package/dist/editor-bundle.mjs.map +1 -1
  8. package/dist/editor.d.mts +2 -2
  9. package/dist/editor.mjs +184 -25
  10. package/dist/editor.mjs.map +1 -1
  11. package/dist/{events-Dasls6a_.d.mts → events-De4SvZil.d.mts} +25 -5
  12. package/dist/events-De4SvZil.d.mts.map +1 -0
  13. package/dist/events.d.mts +1 -1
  14. package/dist/events.mjs +431 -19
  15. package/dist/events.mjs.map +1 -1
  16. package/dist/filler-D2_wc_8Z.mjs +8 -0
  17. package/dist/filler-D2_wc_8Z.mjs.map +1 -0
  18. package/dist/{host-1dtxuKIA.d.mts → host-BKvhyPj-.d.mts} +27 -8
  19. package/dist/host-BKvhyPj-.d.mts.map +1 -0
  20. package/dist/host.d.mts +2 -2
  21. package/dist/host.mjs +25 -8
  22. package/dist/host.mjs.map +1 -1
  23. package/dist/html.d.mts.map +1 -1
  24. package/dist/html.mjs +2 -0
  25. package/dist/html.mjs.map +1 -1
  26. package/dist/index-DDV_JesZ.d.mts.map +1 -1
  27. package/dist/index.mjs +52 -19
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/input.d.mts +259 -3
  30. package/dist/input.d.mts.map +1 -1
  31. package/dist/input.mjs +2 -1
  32. package/dist/input.mjs.map +1 -1
  33. package/dist/toolbar.d.mts +7 -0
  34. package/dist/toolbar.d.mts.map +1 -1
  35. package/dist/toolbar.mjs +143 -14
  36. package/dist/toolbar.mjs.map +1 -1
  37. package/dist/view.d.mts.map +1 -1
  38. package/dist/view.mjs +26 -2
  39. package/dist/view.mjs.map +1 -1
  40. package/package.json +6 -6
  41. package/dist/editor-DbYswARG.d.mts.map +0 -1
  42. package/dist/events-Dasls6a_.d.mts.map +0 -1
  43. 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 (§121); the default renders the
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 (§125).
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 (§128), such as `foldkit-richtext-markdown`'s `markdownInputRules`. A rule the
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 (§129), code highlighting for one. It sees the document only. A highlight derived
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 (§29). A toolbar, a menu, a handle: each is the
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. It places it again when the editor's
246
- subtree changes (a patch can move a block without the page scrolling), on scroll, and on
247
- resize, and it finds a host drawn after it, or drawn again in the same parent.
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. The place is read again on a scroll and on release, where the blocks are
258
- then, and release sends `MovedBlock` there.
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 (§123):
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. `textBefore` is a read (`RichText.textBefore(document, position)`), not
287
- stored state, so there is no switch that opens or closes the menu.
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 (§124 §5): one transition and one undo step.
292
-
293
- `matchingEntries` filters, and it, `slashQuery`, and `slashMenu` work over any entries carrying
294
- a `message`, so an application's own catalogue reuses them.
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 (§121), so a
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 (§64, §126) is projected with `RichText.decorationsIn`: each covered piece of
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, Selected, Pasted, Undone,
357
- Redone, Patched
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. It refuses what the vocabulary cannot carry rather than
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. `attachment.sync(state, changeSet)` patches and restores the
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 (§31): it drops what the subtree
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 (§118):
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