@voithos-labs/aragonite 0.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/LICENSE +661 -0
- package/README.md +365 -0
- package/THIRD-PARTY-NOTICES.md +102 -0
- package/dist/a11y-strings.d.ts +46 -0
- package/dist/a11y-strings.js +62 -0
- package/dist/action-contracts.d.ts +301 -0
- package/dist/action-contracts.js +5 -0
- package/dist/active-editor.d.ts +16 -0
- package/dist/active-editor.js +67 -0
- package/dist/ambient/ambient-cursor.d.ts +41 -0
- package/dist/ambient/ambient-cursor.js +120 -0
- package/dist/ambient/ambient-dom.d.ts +9 -0
- package/dist/ambient/ambient-dom.js +102 -0
- package/dist/assert.d.ts +6 -0
- package/dist/assert.js +17 -0
- package/dist/block-component.d.ts +274 -0
- package/dist/block-component.js +43 -0
- package/dist/block-id.d.ts +14 -0
- package/dist/block-id.js +34 -0
- package/dist/bounded-memo.d.ts +14 -0
- package/dist/bounded-memo.js +32 -0
- package/dist/components/BlockDragHandle.svelte +66 -0
- package/dist/components/BlockDragHandle.svelte.d.ts +18 -0
- package/dist/components/BlockHost.svelte +328 -0
- package/dist/components/BlockHost.svelte.d.ts +15 -0
- package/dist/components/BlockList.svelte +114 -0
- package/dist/components/BlockList.svelte.d.ts +16 -0
- package/dist/components/DecorationOverlay.svelte +156 -0
- package/dist/components/DecorationOverlay.svelte.d.ts +14 -0
- package/dist/components/Editor.svelte +1849 -0
- package/dist/components/Editor.svelte.d.ts +49 -0
- package/dist/components/GapCaret.svelte +226 -0
- package/dist/components/GapCaret.svelte.d.ts +9 -0
- package/dist/components/SearchBar.svelte +243 -0
- package/dist/components/SearchBar.svelte.d.ts +7 -0
- package/dist/components/SelectionOverlay.svelte +171 -0
- package/dist/components/SelectionOverlay.svelte.d.ts +14 -0
- package/dist/components/block-content-selector.d.ts +12 -0
- package/dist/components/block-content-selector.js +12 -0
- package/dist/components/blocks/BlockquoteBlock.svelte +32 -0
- package/dist/components/blocks/BlockquoteBlock.svelte.d.ts +11 -0
- package/dist/components/blocks/ThematicBreakBlock.svelte +156 -0
- package/dist/components/blocks/ThematicBreakBlock.svelte.d.ts +17 -0
- package/dist/components/blocks/code/CodeBlock.svelte +726 -0
- package/dist/components/blocks/code/CodeBlock.svelte.d.ts +23 -0
- package/dist/components/blocks/code/CodeLanguageChip.svelte +127 -0
- package/dist/components/blocks/code/CodeLanguageChip.svelte.d.ts +14 -0
- package/dist/components/blocks/code/code-beforeinput.d.ts +30 -0
- package/dist/components/blocks/code/code-beforeinput.js +40 -0
- package/dist/components/blocks/code/code-bootstrap.d.ts +8 -0
- package/dist/components/blocks/code/code-bootstrap.js +54 -0
- package/dist/components/blocks/code/code-editing.d.ts +28 -0
- package/dist/components/blocks/code/code-editing.js +79 -0
- package/dist/components/blocks/code/code-enter.d.ts +22 -0
- package/dist/components/blocks/code/code-enter.js +19 -0
- package/dist/components/blocks/code/code-fence-boundary.d.ts +77 -0
- package/dist/components/blocks/code/code-fence-boundary.js +169 -0
- package/dist/components/blocks/code/code-fence-exit.d.ts +24 -0
- package/dist/components/blocks/code/code-fence-exit.js +36 -0
- package/dist/components/blocks/code/code-indent.d.ts +14 -0
- package/dist/components/blocks/code/code-indent.js +87 -0
- package/dist/components/blocks/code/code-languages.d.ts +15 -0
- package/dist/components/blocks/code/code-languages.js +30 -0
- package/dist/components/blocks/code/code-paste-surface.d.ts +6 -0
- package/dist/components/blocks/code/code-paste-surface.js +29 -0
- package/dist/components/blocks/code/code-paste.d.ts +24 -0
- package/dist/components/blocks/code/code-paste.js +21 -0
- package/dist/components/blocks/code/code-renderer.d.ts +18 -0
- package/dist/components/blocks/code/code-renderer.js +249 -0
- package/dist/components/blocks/directive/DirectiveContainerBlock.svelte +51 -0
- package/dist/components/blocks/directive/DirectiveContainerBlock.svelte.d.ts +11 -0
- package/dist/components/blocks/directive/activate-directives.d.ts +7 -0
- package/dist/components/blocks/directive/activate-directives.js +23 -0
- package/dist/components/blocks/editable-leaf.d.ts +126 -0
- package/dist/components/blocks/editable-leaf.js +410 -0
- package/dist/components/blocks/editable-surface.d.ts +209 -0
- package/dist/components/blocks/editable-surface.js +331 -0
- package/dist/components/blocks/list/ListBlock.svelte +162 -0
- package/dist/components/blocks/list/ListBlock.svelte.d.ts +11 -0
- package/dist/components/blocks/list/ListItemBlock.svelte +324 -0
- package/dist/components/blocks/list/ListItemBlock.svelte.d.ts +14 -0
- package/dist/components/blocks/list/task-checkbox.d.ts +3 -0
- package/dist/components/blocks/list/task-checkbox.js +33 -0
- package/dist/components/blocks/plain-text-backend.d.ts +21 -0
- package/dist/components/blocks/plain-text-backend.js +47 -0
- package/dist/components/blocks/surface-wiring.svelte.d.ts +18 -0
- package/dist/components/blocks/surface-wiring.svelte.js +68 -0
- package/dist/components/blocks/table/TableActionMenu.svelte +267 -0
- package/dist/components/blocks/table/TableActionMenu.svelte.d.ts +15 -0
- package/dist/components/blocks/table/TableBlock.svelte +765 -0
- package/dist/components/blocks/table/TableBlock.svelte.d.ts +30 -0
- package/dist/components/blocks/table/TableCellBlock.svelte +1075 -0
- package/dist/components/blocks/table/TableCellBlock.svelte.d.ts +30 -0
- package/dist/components/blocks/table/TableGrip.svelte +91 -0
- package/dist/components/blocks/table/TableGrip.svelte.d.ts +8 -0
- package/dist/components/blocks/table/TableRowBlock.svelte +233 -0
- package/dist/components/blocks/table/TableRowBlock.svelte.d.ts +34 -0
- package/dist/components/blocks/table/cell-clipboard.d.ts +23 -0
- package/dist/components/blocks/table/cell-clipboard.js +34 -0
- package/dist/components/blocks/table/cell-keydown-plan.d.ts +37 -0
- package/dist/components/blocks/table/cell-keydown-plan.js +66 -0
- package/dist/components/blocks/table/cell-pointer.d.ts +58 -0
- package/dist/components/blocks/table/cell-pointer.js +153 -0
- package/dist/components/blocks/table/cell-render.d.ts +51 -0
- package/dist/components/blocks/table/cell-render.js +98 -0
- package/dist/components/blocks/table/cell-table-commands.d.ts +12 -0
- package/dist/components/blocks/table/cell-table-commands.js +21 -0
- package/dist/components/blocks/table/cell-x-mapping.d.ts +6 -0
- package/dist/components/blocks/table/cell-x-mapping.js +16 -0
- package/dist/components/blocks/table/selected-cells.d.ts +19 -0
- package/dist/components/blocks/table/selected-cells.js +35 -0
- package/dist/components/blocks/table/table-caret-at-point.d.ts +8 -0
- package/dist/components/blocks/table/table-caret-at-point.js +35 -0
- package/dist/components/blocks/table/table-cell-paste.d.ts +16 -0
- package/dist/components/blocks/table/table-cell-paste.js +75 -0
- package/dist/components/blocks/table/table-drag-hit-test.d.ts +5 -0
- package/dist/components/blocks/table/table-drag-hit-test.js +20 -0
- package/dist/components/blocks/table/table-drop-target.d.ts +1 -0
- package/dist/components/blocks/table/table-drop-target.js +16 -0
- package/dist/components/blocks/table/table-menu-model.d.ts +52 -0
- package/dist/components/blocks/table/table-menu-model.js +78 -0
- package/dist/components/blocks/table/table-navigation.d.ts +8 -0
- package/dist/components/blocks/table/table-navigation.js +22 -0
- package/dist/components/blocks/table/table-reorder-drag.d.ts +78 -0
- package/dist/components/blocks/table/table-reorder-drag.js +97 -0
- package/dist/components/blocks/text/TextEditableBlock.svelte +1012 -0
- package/dist/components/blocks/text/TextEditableBlock.svelte.d.ts +33 -0
- package/dist/components/blocks/text/click-snap-guard.d.ts +8 -0
- package/dist/components/blocks/text/click-snap-guard.js +19 -0
- package/dist/components/blocks/text/composition-seat.d.ts +50 -0
- package/dist/components/blocks/text/composition-seat.js +65 -0
- package/dist/components/blocks/text/construct-edge-delete.d.ts +47 -0
- package/dist/components/blocks/text/construct-edge-delete.js +164 -0
- package/dist/components/blocks/text/construct-reveal.d.ts +39 -0
- package/dist/components/blocks/text/construct-reveal.js +164 -0
- package/dist/components/blocks/text/edge-policy-dispatch.d.ts +91 -0
- package/dist/components/blocks/text/edge-policy-dispatch.js +457 -0
- package/dist/components/blocks/text/edge-seat.d.ts +42 -0
- package/dist/components/blocks/text/edge-seat.js +197 -0
- package/dist/components/blocks/text/hidden-suffix.d.ts +10 -0
- package/dist/components/blocks/text/hidden-suffix.js +15 -0
- package/dist/components/blocks/text/link-at-point.d.ts +33 -0
- package/dist/components/blocks/text/link-at-point.js +47 -0
- package/dist/components/blocks/text/link-source-bytes.d.ts +37 -0
- package/dist/components/blocks/text/link-source-bytes.js +161 -0
- package/dist/components/blocks/text/live-join-seam.d.ts +15 -0
- package/dist/components/blocks/text/live-join-seam.js +327 -0
- package/dist/components/blocks/text/live-selection-edit.d.ts +68 -0
- package/dist/components/blocks/text/live-selection-edit.js +120 -0
- package/dist/components/blocks/text/live-split-rebalance.d.ts +56 -0
- package/dist/components/blocks/text/live-split-rebalance.js +239 -0
- package/dist/components/blocks/text/marker-completion.d.ts +18 -0
- package/dist/components/blocks/text/marker-completion.js +29 -0
- package/dist/components/blocks/text/pending-mark-insert.d.ts +37 -0
- package/dist/components/blocks/text/pending-mark-insert.js +188 -0
- package/dist/components/blocks/text/screen-diff.d.ts +20 -0
- package/dist/components/blocks/text/screen-diff.js +39 -0
- package/dist/components/blocks/text/text-clipboard.d.ts +70 -0
- package/dist/components/blocks/text/text-clipboard.js +131 -0
- package/dist/components/blocks/text/text-keydown.d.ts +39 -0
- package/dist/components/blocks/text/text-keydown.js +80 -0
- package/dist/components/blocks/text/text-render.d.ts +67 -0
- package/dist/components/blocks/text/text-render.js +215 -0
- package/dist/components/blocks/text/widget-adjacency.d.ts +30 -0
- package/dist/components/blocks/text/widget-adjacency.js +68 -0
- package/dist/components/blocks/text/widget-interaction.d.ts +90 -0
- package/dist/components/blocks/text/widget-interaction.js +591 -0
- package/dist/components/blocks/widget-portal.d.ts +54 -0
- package/dist/components/blocks/widget-portal.js +136 -0
- package/dist/components/built-in-blocks.d.ts +7 -0
- package/dist/components/built-in-blocks.js +77 -0
- package/dist/components/editor-root-clipboard.d.ts +29 -0
- package/dist/components/editor-root-clipboard.js +105 -0
- package/dist/components/editor-root-keydown.d.ts +47 -0
- package/dist/components/editor-root-keydown.js +88 -0
- package/dist/components/editor-root-listeners.d.ts +44 -0
- package/dist/components/editor-root-listeners.js +112 -0
- package/dist/components/image/ImageOverlayHost.svelte +115 -0
- package/dist/components/image/ImageOverlayHost.svelte.d.ts +20 -0
- package/dist/components/image/ImageProperties.svelte +153 -0
- package/dist/components/image/ImageProperties.svelte.d.ts +14 -0
- package/dist/components/image/ImageResizeHandles.svelte +188 -0
- package/dist/components/image/ImageResizeHandles.svelte.d.ts +10 -0
- package/dist/components/image/image-edit-commit.d.ts +38 -0
- package/dist/components/image/image-edit-commit.js +159 -0
- package/dist/components/image/image-resize.d.ts +11 -0
- package/dist/components/image/image-resize.js +38 -0
- package/dist/components/image/image-source-bytes.d.ts +15 -0
- package/dist/components/image/image-source-bytes.js +73 -0
- package/dist/components/image/image-widget-editing.d.ts +7 -0
- package/dist/components/image/image-widget-editing.js +41 -0
- package/dist/components/image/widget-dom.d.ts +11 -0
- package/dist/components/image/widget-dom.js +110 -0
- package/dist/components/image/widget-selection-state.svelte.d.ts +18 -0
- package/dist/components/image/widget-selection-state.svelte.js +26 -0
- package/dist/components/link-card/LinkCard.svelte +190 -0
- package/dist/components/link-card/LinkCard.svelte.d.ts +18 -0
- package/dist/components/link-card/LinkCardHost.svelte +181 -0
- package/dist/components/link-card/LinkCardHost.svelte.d.ts +25 -0
- package/dist/components/link-card/link-card-commit.d.ts +50 -0
- package/dist/components/link-card/link-card-commit.js +134 -0
- package/dist/components/link-card/link-card-entry.d.ts +39 -0
- package/dist/components/link-card/link-card-entry.js +51 -0
- package/dist/components/link-card/link-card-state.svelte.d.ts +44 -0
- package/dist/components/link-card/link-card-state.svelte.js +46 -0
- package/dist/components/lrd-map-gate.d.ts +18 -0
- package/dist/components/lrd-map-gate.js +22 -0
- package/dist/components/paste-image-arm.d.ts +29 -0
- package/dist/components/paste-image-arm.js +61 -0
- package/dist/components/portal.d.ts +11 -0
- package/dist/components/portal.js +29 -0
- package/dist/core/directive/activate.d.ts +8 -0
- package/dist/core/directive/activate.js +25 -0
- package/dist/core/directive/container-opener.d.ts +7 -0
- package/dist/core/directive/container-opener.js +162 -0
- package/dist/core/directive/grammar.d.ts +50 -0
- package/dist/core/directive/grammar.js +88 -0
- package/dist/core/directive/kinds.d.ts +31 -0
- package/dist/core/directive/kinds.js +137 -0
- package/dist/core/directive/registry.d.ts +39 -0
- package/dist/core/directive/registry.js +49 -0
- package/dist/core/directive/text-recognizer.d.ts +8 -0
- package/dist/core/directive/text-recognizer.js +87 -0
- package/dist/core/escapable.d.ts +6 -0
- package/dist/core/escapable.js +6 -0
- package/dist/core/inline/backticks.d.ts +16 -0
- package/dist/core/inline/backticks.js +62 -0
- package/dist/core/inline/character-refs.d.ts +7 -0
- package/dist/core/inline/character-refs.js +70 -0
- package/dist/core/inline/destination-bytes.d.ts +7 -0
- package/dist/core/inline/destination-bytes.js +14 -0
- package/dist/core/inline/entity-widget.d.ts +11 -0
- package/dist/core/inline/entity-widget.js +21 -0
- package/dist/core/inline/format-toggle.d.ts +50 -0
- package/dist/core/inline/format-toggle.js +533 -0
- package/dist/core/inline/html-entities.d.ts +8 -0
- package/dist/core/inline/html-entities.js +2134 -0
- package/dist/core/inline/html-tag-grammar.d.ts +16 -0
- package/dist/core/inline/html-tag-grammar.js +58 -0
- package/dist/core/inline/image-dimensions.d.ts +6 -0
- package/dist/core/inline/image-dimensions.js +42 -0
- package/dist/core/inline/index.d.ts +42 -0
- package/dist/core/inline/index.js +77 -0
- package/dist/core/inline/inline-cache.d.ts +20 -0
- package/dist/core/inline/inline-cache.js +28 -0
- package/dist/core/inline/inline-widgets.d.ts +124 -0
- package/dist/core/inline/inline-widgets.js +131 -0
- package/dist/core/inline/link-reference-resolver.d.ts +21 -0
- package/dist/core/inline/link-reference-resolver.js +46 -0
- package/dist/core/inline/raw-html-widget.d.ts +10 -0
- package/dist/core/inline/raw-html-widget.js +22 -0
- package/dist/core/inline/scan/autolinks.d.ts +23 -0
- package/dist/core/inline/scan/autolinks.js +354 -0
- package/dist/core/inline/scan/brackets.d.ts +11 -0
- package/dist/core/inline/scan/brackets.js +284 -0
- package/dist/core/inline/scan/code-spans.d.ts +7 -0
- package/dist/core/inline/scan/code-spans.js +27 -0
- package/dist/core/inline/scan/emphasis.d.ts +10 -0
- package/dist/core/inline/scan/emphasis.js +276 -0
- package/dist/core/inline/scan/index.d.ts +7 -0
- package/dist/core/inline/scan/index.js +176 -0
- package/dist/core/inline/scan/plugin-syntax.d.ts +62 -0
- package/dist/core/inline/scan/plugin-syntax.js +150 -0
- package/dist/core/inline/scan/scan-state.d.ts +47 -0
- package/dist/core/inline/scan/scan-state.js +48 -0
- package/dist/core/inline/scan/simple-nodes.d.ts +14 -0
- package/dist/core/inline/scan/simple-nodes.js +53 -0
- package/dist/core/inline/scan/url.d.ts +12 -0
- package/dist/core/inline/scan/url.js +82 -0
- package/dist/core/inline/transparency.d.ts +8 -0
- package/dist/core/inline/transparency.js +39 -0
- package/dist/core/inline/visibility.d.ts +81 -0
- package/dist/core/inline/visibility.js +144 -0
- package/dist/core/inline/walk.d.ts +14 -0
- package/dist/core/inline/walk.js +29 -0
- package/dist/core/inline-render.d.ts +48 -0
- package/dist/core/inline-render.js +322 -0
- package/dist/core/lines.d.ts +44 -0
- package/dist/core/lines.js +91 -0
- package/dist/core/node-views.d.ts +22 -0
- package/dist/core/node-views.js +8 -0
- package/dist/core/nodes.d.ts +297 -0
- package/dist/core/nodes.js +83 -0
- package/dist/core/parser.d.ts +68 -0
- package/dist/core/parser.js +189 -0
- package/dist/core/parsers/blockquote.d.ts +17 -0
- package/dist/core/parsers/blockquote.js +69 -0
- package/dist/core/parsers/built-in-openers.d.ts +8 -0
- package/dist/core/parsers/built-in-openers.js +124 -0
- package/dist/core/parsers/fence-syntax.d.ts +24 -0
- package/dist/core/parsers/fence-syntax.js +44 -0
- package/dist/core/parsers/fenced-code.d.ts +7 -0
- package/dist/core/parsers/fenced-code.js +29 -0
- package/dist/core/parsers/heading.d.ts +3 -0
- package/dist/core/parsers/heading.js +6 -0
- package/dist/core/parsers/html-block.d.ts +17 -0
- package/dist/core/parsers/html-block.js +94 -0
- package/dist/core/parsers/indented-code.d.ts +4 -0
- package/dist/core/parsers/indented-code.js +41 -0
- package/dist/core/parsers/link-reference.d.ts +7 -0
- package/dist/core/parsers/link-reference.js +152 -0
- package/dist/core/parsers/list.d.ts +19 -0
- package/dist/core/parsers/list.js +133 -0
- package/dist/core/parsers/paragraph.d.ts +10 -0
- package/dist/core/parsers/paragraph.js +42 -0
- package/dist/core/parsers/table-completion.d.ts +13 -0
- package/dist/core/parsers/table-completion.js +48 -0
- package/dist/core/parsers/table.d.ts +18 -0
- package/dist/core/parsers/table.js +110 -0
- package/dist/core/parsers/thematic-break.d.ts +1 -0
- package/dist/core/parsers/thematic-break.js +19 -0
- package/dist/core/serializer.d.ts +15 -0
- package/dist/core/serializer.js +11 -0
- package/dist/core/terminator-escalation.d.ts +8 -0
- package/dist/core/terminator-escalation.js +18 -0
- package/dist/core/url-policy.d.ts +14 -0
- package/dist/core/url-policy.js +44 -0
- package/dist/cursor/content-offsets.d.ts +29 -0
- package/dist/cursor/content-offsets.js +151 -0
- package/dist/cursor/coordinate-spaces.d.ts +65 -0
- package/dist/cursor/coordinate-spaces.js +66 -0
- package/dist/cursor/dom-walk.d.ts +18 -0
- package/dist/cursor/dom-walk.js +31 -0
- package/dist/cursor/edge-affinity.d.ts +44 -0
- package/dist/cursor/edge-affinity.js +60 -0
- package/dist/cursor/focused-caret.d.ts +10 -0
- package/dist/cursor/focused-caret.js +24 -0
- package/dist/cursor/height-model.d.ts +24 -0
- package/dist/cursor/height-model.js +72 -0
- package/dist/cursor/height-oracle.d.ts +24 -0
- package/dist/cursor/height-oracle.js +94 -0
- package/dist/cursor/overlay-rects.d.ts +8 -0
- package/dist/cursor/overlay-rects.js +19 -0
- package/dist/cursor/overlay-remeasure.d.ts +17 -0
- package/dist/cursor/overlay-remeasure.js +31 -0
- package/dist/cursor/pending-marks.d.ts +24 -0
- package/dist/cursor/pending-marks.js +36 -0
- package/dist/cursor/point-offset.d.ts +21 -0
- package/dist/cursor/point-offset.js +48 -0
- package/dist/cursor/reveal-anchor.d.ts +30 -0
- package/dist/cursor/reveal-anchor.js +31 -0
- package/dist/cursor/reveal-source.d.ts +35 -0
- package/dist/cursor/reveal-source.js +51 -0
- package/dist/cursor/scroll-ancestors.d.ts +28 -0
- package/dist/cursor/scroll-ancestors.js +89 -0
- package/dist/cursor/scrollport.d.ts +20 -0
- package/dist/cursor/scrollport.js +41 -0
- package/dist/cursor/sticky-column.d.ts +35 -0
- package/dist/cursor/sticky-column.js +64 -0
- package/dist/cursor/sticky-measure.d.ts +16 -0
- package/dist/cursor/sticky-measure.js +97 -0
- package/dist/cursor/typography-estimates.d.ts +21 -0
- package/dist/cursor/typography-estimates.js +21 -0
- package/dist/cursor/visual-lines.d.ts +25 -0
- package/dist/cursor/visual-lines.js +125 -0
- package/dist/cursor/widget-offset.d.ts +132 -0
- package/dist/cursor/widget-offset.js +543 -0
- package/dist/debug/diagnostics-report.d.ts +17 -0
- package/dist/debug/diagnostics-report.js +25 -0
- package/dist/debug/dump-tree.d.ts +6 -0
- package/dist/debug/dump-tree.js +114 -0
- package/dist/debug/inspect.d.ts +19 -0
- package/dist/debug/inspect.js +116 -0
- package/dist/debug/interaction-trace.d.ts +39 -0
- package/dist/debug/interaction-trace.js +105 -0
- package/dist/debug/operations-log.d.ts +14 -0
- package/dist/debug/operations-log.js +30 -0
- package/dist/decorations/buckets.d.ts +40 -0
- package/dist/decorations/buckets.js +87 -0
- package/dist/decorations/decoration-state.svelte.d.ts +20 -0
- package/dist/decorations/decoration-state.svelte.js +195 -0
- package/dist/decorations/island-dom.d.ts +35 -0
- package/dist/decorations/island-dom.js +148 -0
- package/dist/decorations/reserved-attrs.d.ts +12 -0
- package/dist/decorations/reserved-attrs.js +47 -0
- package/dist/decorations/types.d.ts +68 -0
- package/dist/decorations/types.js +5 -0
- package/dist/decorations/widget-dom.d.ts +15 -0
- package/dist/decorations/widget-dom.js +35 -0
- package/dist/dev-warn.d.ts +13 -0
- package/dist/dev-warn.js +28 -0
- package/dist/editor-actions/ancestry-folds.d.ts +32 -0
- package/dist/editor-actions/ancestry-folds.js +106 -0
- package/dist/editor-actions/block-edit-core.d.ts +29 -0
- package/dist/editor-actions/block-edit-core.js +277 -0
- package/dist/editor-actions/block-edit-scope.d.ts +79 -0
- package/dist/editor-actions/block-edit-scope.js +76 -0
- package/dist/editor-actions/block-edit.d.ts +8 -0
- package/dist/editor-actions/block-edit.js +111 -0
- package/dist/editor-actions/commit/history.d.ts +9 -0
- package/dist/editor-actions/commit/history.js +77 -0
- package/dist/editor-actions/commit/text-batch.d.ts +34 -0
- package/dist/editor-actions/commit/text-batch.js +61 -0
- package/dist/editor-actions/commit/undo-controller.d.ts +7 -0
- package/dist/editor-actions/commit/undo-controller.js +570 -0
- package/dist/editor-actions/container-block-component.d.ts +85 -0
- package/dist/editor-actions/container-block-component.js +228 -0
- package/dist/editor-actions/container-edit.d.ts +7 -0
- package/dist/editor-actions/container-edit.js +49 -0
- package/dist/editor-actions/container-exit-overrides.d.ts +16 -0
- package/dist/editor-actions/container-exit-overrides.js +38 -0
- package/dist/editor-actions/deps.d.ts +60 -0
- package/dist/editor-actions/deps.js +1 -0
- package/dist/editor-actions/enter-completion.d.ts +25 -0
- package/dist/editor-actions/enter-completion.js +76 -0
- package/dist/editor-actions/focus/focus-dispatch.d.ts +36 -0
- package/dist/editor-actions/focus/focus-dispatch.js +106 -0
- package/dist/editor-actions/focus/focus-landing.d.ts +7 -0
- package/dist/editor-actions/focus/focus-landing.js +39 -0
- package/dist/editor-actions/focus/focus.d.ts +7 -0
- package/dist/editor-actions/focus/focus.js +74 -0
- package/dist/editor-actions/index.d.ts +15 -0
- package/dist/editor-actions/index.js +19 -0
- package/dist/editor-actions/inline-range-commit.d.ts +20 -0
- package/dist/editor-actions/inline-range-commit.js +63 -0
- package/dist/editor-actions/list-context.d.ts +25 -0
- package/dist/editor-actions/list-context.js +285 -0
- package/dist/editor-actions/list-overrides.d.ts +13 -0
- package/dist/editor-actions/list-overrides.js +26 -0
- package/dist/editor-actions/merge-fallback.d.ts +19 -0
- package/dist/editor-actions/merge-fallback.js +27 -0
- package/dist/editor-actions/nested/nested-actions.d.ts +65 -0
- package/dist/editor-actions/nested/nested-actions.js +59 -0
- package/dist/editor-actions/nested/nested-block-edit.d.ts +10 -0
- package/dist/editor-actions/nested/nested-block-edit.js +181 -0
- package/dist/editor-actions/nested/nested-focus.d.ts +9 -0
- package/dist/editor-actions/nested/nested-focus.js +30 -0
- package/dist/editor-actions/paste-coordinator.d.ts +4 -0
- package/dist/editor-actions/paste-coordinator.js +20 -0
- package/dist/editor-actions/plugin/chrome-leaf.d.ts +25 -0
- package/dist/editor-actions/plugin/chrome-leaf.js +80 -0
- package/dist/editor-actions/plugin/container.d.ts +137 -0
- package/dist/editor-actions/plugin/container.js +351 -0
- package/dist/editor-actions/plugin/directive-container.d.ts +13 -0
- package/dist/editor-actions/plugin/directive-container.js +31 -0
- package/dist/editor-actions/reorder-action.d.ts +17 -0
- package/dist/editor-actions/reorder-action.js +104 -0
- package/dist/editor-actions/reorder-drag.d.ts +34 -0
- package/dist/editor-actions/reorder-drag.js +152 -0
- package/dist/editor-actions/replacement-focus.d.ts +27 -0
- package/dist/editor-actions/replacement-focus.js +60 -0
- package/dist/editor-actions/search-replace.d.ts +6 -0
- package/dist/editor-actions/search-replace.js +145 -0
- package/dist/editor-actions/table-context.d.ts +34 -0
- package/dist/editor-actions/table-context.js +292 -0
- package/dist/editor-actions/unwrap-strategies.d.ts +14 -0
- package/dist/editor-actions/unwrap-strategies.js +131 -0
- package/dist/editor-actions/whole-block-focus-surface.d.ts +51 -0
- package/dist/editor-actions/whole-block-focus-surface.js +171 -0
- package/dist/editor-events.d.ts +89 -0
- package/dist/editor-events.js +96 -0
- package/dist/editor-keys.d.ts +197 -0
- package/dist/editor-keys.js +40 -0
- package/dist/editor-props.d.ts +164 -0
- package/dist/editor-props.js +1 -0
- package/dist/editor-rects.d.ts +58 -0
- package/dist/editor-rects.js +114 -0
- package/dist/env.d.ts +12 -0
- package/dist/env.js +20 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +19 -0
- package/dist/invariants/commit-paths.d.ts +10 -0
- package/dist/invariants/commit-paths.js +25 -0
- package/dist/invariants/commit-scope.d.ts +9 -0
- package/dist/invariants/commit-scope.js +16 -0
- package/dist/invariants/context-keys.d.ts +7 -0
- package/dist/invariants/context-keys.js +14 -0
- package/dist/invariants/descriptor.d.ts +10 -0
- package/dist/invariants/descriptor.js +24 -0
- package/dist/invariants/inline-transitions.d.ts +23 -0
- package/dist/invariants/inline-transitions.js +52 -0
- package/dist/invariants/install.d.ts +36 -0
- package/dist/invariants/install.js +66 -0
- package/dist/invariants/landable-caret.d.ts +12 -0
- package/dist/invariants/landable-caret.js +31 -0
- package/dist/invariants/marker-css-parity.d.ts +10 -0
- package/dist/invariants/marker-css-parity.js +87 -0
- package/dist/invariants/node-shape.d.ts +44 -0
- package/dist/invariants/node-shape.js +258 -0
- package/dist/invariants/registry.d.ts +126 -0
- package/dist/invariants/registry.js +349 -0
- package/dist/invariants/render-fidelity.d.ts +8 -0
- package/dist/invariants/render-fidelity.js +33 -0
- package/dist/invariants/selection-endpoints.d.ts +19 -0
- package/dist/invariants/selection-endpoints.js +78 -0
- package/dist/invariants/single-node-sink.d.ts +9 -0
- package/dist/invariants/single-node-sink.js +16 -0
- package/dist/invariants/snapshot-integrity.d.ts +17 -0
- package/dist/invariants/snapshot-integrity.js +24 -0
- package/dist/invariants/split-landing.d.ts +8 -0
- package/dist/invariants/split-landing.js +15 -0
- package/dist/invariants/structural-descriptor.d.ts +21 -0
- package/dist/invariants/structural-descriptor.js +50 -0
- package/dist/perf/instruments.d.ts +49 -0
- package/dist/perf/instruments.js +154 -0
- package/dist/perf/use-mount-gauge.svelte.d.ts +1 -0
- package/dist/perf/use-mount-gauge.svelte.js +15 -0
- package/dist/plugin.d.ts +102 -0
- package/dist/plugin.js +156 -0
- package/dist/plugins/admonitions/AdmonitionBlock.svelte +144 -0
- package/dist/plugins/admonitions/AdmonitionBlock.svelte.d.ts +11 -0
- package/dist/plugins/admonitions/admonition-kind.d.ts +8 -0
- package/dist/plugins/admonitions/admonition-kind.js +97 -0
- package/dist/plugins/admonitions/convert-document.d.ts +11 -0
- package/dist/plugins/admonitions/convert-document.js +39 -0
- package/dist/plugins/admonitions/gh-alert.d.ts +13 -0
- package/dist/plugins/admonitions/gh-alert.js +83 -0
- package/dist/plugins/admonitions/github-alert-kind.d.ts +12 -0
- package/dist/plugins/admonitions/github-alert-kind.js +125 -0
- package/dist/plugins/admonitions/index.d.ts +6 -0
- package/dist/plugins/admonitions/index.js +8 -0
- package/dist/plugins/admonitions/kinds.d.ts +26 -0
- package/dist/plugins/admonitions/kinds.js +18 -0
- package/dist/plugins/admonitions/register.d.ts +7 -0
- package/dist/plugins/admonitions/register.js +12 -0
- package/dist/plugins/details/DetailsBlock.svelte +129 -0
- package/dist/plugins/details/DetailsBlock.svelte.d.ts +11 -0
- package/dist/plugins/details/details-disclosure.svelte.d.ts +17 -0
- package/dist/plugins/details/details-disclosure.svelte.js +24 -0
- package/dist/plugins/details/details-kind.d.ts +25 -0
- package/dist/plugins/details/details-kind.js +237 -0
- package/dist/plugins/details/index.d.ts +2 -0
- package/dist/plugins/details/index.js +2 -0
- package/dist/plugins/details/register.d.ts +6 -0
- package/dist/plugins/details/register.js +15 -0
- package/dist/plugins/emoji/emoji-plugin.d.ts +2 -0
- package/dist/plugins/emoji/emoji-plugin.js +10 -0
- package/dist/plugins/emoji/emoji-recognizer.d.ts +18 -0
- package/dist/plugins/emoji/emoji-recognizer.js +63 -0
- package/dist/plugins/emoji/emoji-table.d.ts +1 -0
- package/dist/plugins/emoji/emoji-table.js +1919 -0
- package/dist/plugins/emoji/index.d.ts +3 -0
- package/dist/plugins/emoji/index.js +4 -0
- package/dist/plugins/footnotes/FootnoteDefinition.svelte +103 -0
- package/dist/plugins/footnotes/FootnoteDefinition.svelte.d.ts +13 -0
- package/dist/plugins/footnotes/FootnoteReference.svelte +50 -0
- package/dist/plugins/footnotes/FootnoteReference.svelte.d.ts +4 -0
- package/dist/plugins/footnotes/constants.d.ts +2 -0
- package/dist/plugins/footnotes/constants.js +4 -0
- package/dist/plugins/footnotes/footnote-definition.d.ts +14 -0
- package/dist/plugins/footnotes/footnote-definition.js +147 -0
- package/dist/plugins/footnotes/footnote-lookup.d.ts +9 -0
- package/dist/plugins/footnotes/footnote-lookup.js +33 -0
- package/dist/plugins/footnotes/footnote-numbering.d.ts +23 -0
- package/dist/plugins/footnotes/footnote-numbering.js +78 -0
- package/dist/plugins/footnotes/footnote-reference.d.ts +7 -0
- package/dist/plugins/footnotes/footnote-reference.js +54 -0
- package/dist/plugins/footnotes/footnotes-plugin.d.ts +7 -0
- package/dist/plugins/footnotes/footnotes-plugin.js +17 -0
- package/dist/plugins/footnotes/index.d.ts +5 -0
- package/dist/plugins/footnotes/index.js +6 -0
- package/dist/plugins/highlight-occurrences/highlight-occurrences-plugin.d.ts +17 -0
- package/dist/plugins/highlight-occurrences/highlight-occurrences-plugin.js +31 -0
- package/dist/plugins/highlight-occurrences/index.d.ts +1 -0
- package/dist/plugins/highlight-occurrences/index.js +1 -0
- package/dist/plugins/highlight-occurrences/occurrence-source.d.ts +26 -0
- package/dist/plugins/highlight-occurrences/occurrence-source.js +54 -0
- package/dist/plugins/highlight-occurrences/occurrences.d.ts +33 -0
- package/dist/plugins/highlight-occurrences/occurrences.js +108 -0
- package/dist/plugins/latex/BlockMath.svelte +119 -0
- package/dist/plugins/latex/BlockMath.svelte.d.ts +21 -0
- package/dist/plugins/latex/MathInline.svelte +28 -0
- package/dist/plugins/latex/MathInline.svelte.d.ts +4 -0
- package/dist/plugins/latex/index.d.ts +3 -0
- package/dist/plugins/latex/index.js +4 -0
- package/dist/plugins/latex/latex-kind.d.ts +17 -0
- package/dist/plugins/latex/latex-kind.js +224 -0
- package/dist/plugins/latex/math-completion.d.ts +10 -0
- package/dist/plugins/latex/math-completion.js +19 -0
- package/dist/plugins/latex/math-renderer.d.ts +26 -0
- package/dist/plugins/latex/math-renderer.js +42 -0
- package/dist/plugins/latex/register.d.ts +10 -0
- package/dist/plugins/latex/register.js +23 -0
- package/dist/plugins/latex/renderer.d.ts +13 -0
- package/dist/plugins/latex/renderer.js +34 -0
- package/dist/plugins/mermaid/MermaidBlock.svelte +572 -0
- package/dist/plugins/mermaid/MermaidBlock.svelte.d.ts +11 -0
- package/dist/plugins/mermaid/index.d.ts +3 -0
- package/dist/plugins/mermaid/index.js +3 -0
- package/dist/plugins/mermaid/mermaid-kind.d.ts +38 -0
- package/dist/plugins/mermaid/mermaid-kind.js +176 -0
- package/dist/plugins/mermaid/mermaid-renderer.d.ts +28 -0
- package/dist/plugins/mermaid/mermaid-renderer.js +35 -0
- package/dist/plugins/mermaid/register.d.ts +10 -0
- package/dist/plugins/mermaid/register.js +20 -0
- package/dist/plugins/mermaid/renderer.d.ts +11 -0
- package/dist/plugins/mermaid/renderer.js +53 -0
- package/dist/plugins/parrot/ParrotBlock.svelte +342 -0
- package/dist/plugins/parrot/ParrotBlock.svelte.d.ts +21 -0
- package/dist/plugins/parrot/index.d.ts +1 -0
- package/dist/plugins/parrot/index.js +2 -0
- package/dist/plugins/parrot/parrot-plugin.d.ts +3 -0
- package/dist/plugins/parrot/parrot-plugin.js +49 -0
- package/dist/plugins/toc/TocBlock.svelte +218 -0
- package/dist/plugins/toc/TocBlock.svelte.d.ts +24 -0
- package/dist/plugins/toc/heading-outline.d.ts +28 -0
- package/dist/plugins/toc/heading-outline.js +65 -0
- package/dist/plugins/toc/index.d.ts +2 -0
- package/dist/plugins/toc/index.js +2 -0
- package/dist/plugins/toc/navigation-queue.d.ts +12 -0
- package/dist/plugins/toc/navigation-queue.js +21 -0
- package/dist/plugins/toc/toc-plugin.d.ts +16 -0
- package/dist/plugins/toc/toc-plugin.js +71 -0
- package/dist/presentation-mode.d.ts +32 -0
- package/dist/presentation-mode.js +50 -0
- package/dist/reactivity/block-list-state.svelte.d.ts +22 -0
- package/dist/reactivity/block-list-state.svelte.js +46 -0
- package/dist/reactivity/block-window.svelte.d.ts +41 -0
- package/dist/reactivity/block-window.svelte.js +93 -0
- package/dist/reactivity/content-version.svelte.d.ts +14 -0
- package/dist/reactivity/content-version.svelte.js +16 -0
- package/dist/reactivity/list-windowing.svelte.d.ts +100 -0
- package/dist/reactivity/list-windowing.svelte.js +402 -0
- package/dist/reactivity/measure-batch.d.ts +15 -0
- package/dist/reactivity/measure-batch.js +18 -0
- package/dist/reactivity/publish-ref.svelte.d.ts +54 -0
- package/dist/reactivity/publish-ref.svelte.js +128 -0
- package/dist/reactivity/scope-geometry.d.ts +18 -0
- package/dist/reactivity/scope-geometry.js +29 -0
- package/dist/reactivity/state-registry.d.ts +10 -0
- package/dist/reactivity/state-registry.js +44 -0
- package/dist/reactivity/use-container-windowing.svelte.d.ts +35 -0
- package/dist/reactivity/use-container-windowing.svelte.js +95 -0
- package/dist/reactivity/window-slice.d.ts +11 -0
- package/dist/reactivity/window-slice.js +9 -0
- package/dist/scan-index.d.ts +11 -0
- package/dist/scan-index.js +27 -0
- package/dist/schema/block-commands.d.ts +105 -0
- package/dist/schema/block-commands.js +179 -0
- package/dist/schema/block-completions.d.ts +33 -0
- package/dist/schema/block-completions.js +49 -0
- package/dist/schema/block-component-registry.d.ts +33 -0
- package/dist/schema/block-component-registry.js +33 -0
- package/dist/schema/block-kind-descriptor.d.ts +281 -0
- package/dist/schema/block-kind-descriptor.js +187 -0
- package/dist/schema/block-openers.d.ts +94 -0
- package/dist/schema/block-openers.js +150 -0
- package/dist/schema/built-in-descriptors.d.ts +8 -0
- package/dist/schema/built-in-descriptors.js +560 -0
- package/dist/schema/child-spans.d.ts +25 -0
- package/dist/schema/child-spans.js +161 -0
- package/dist/schema/closure.d.ts +39 -0
- package/dist/schema/closure.js +55 -0
- package/dist/schema/command-id.d.ts +20 -0
- package/dist/schema/command-id.js +42 -0
- package/dist/schema/commands.d.ts +147 -0
- package/dist/schema/commands.js +294 -0
- package/dist/schema/container-raw.d.ts +21 -0
- package/dist/schema/container-raw.js +43 -0
- package/dist/schema/container-rebuilders.d.ts +32 -0
- package/dist/schema/container-rebuilders.js +95 -0
- package/dist/schema/define-plugin-block.d.ts +15 -0
- package/dist/schema/define-plugin-block.js +18 -0
- package/dist/schema/fenced-code-raw.d.ts +43 -0
- package/dist/schema/fenced-code-raw.js +220 -0
- package/dist/schema/global-commands.d.ts +11 -0
- package/dist/schema/global-commands.js +40 -0
- package/dist/schema/inline-construct-policy.d.ts +131 -0
- package/dist/schema/inline-construct-policy.js +96 -0
- package/dist/schema/keybinding-overrides.d.ts +39 -0
- package/dist/schema/keybinding-overrides.js +43 -0
- package/dist/schema/keybindings.d.ts +36 -0
- package/dist/schema/keybindings.js +75 -0
- package/dist/schema/merge-rules.d.ts +28 -0
- package/dist/schema/merge-rules.js +57 -0
- package/dist/schema/opener-priorities.d.ts +16 -0
- package/dist/schema/opener-priorities.js +16 -0
- package/dist/schema/operations.d.ts +110 -0
- package/dist/schema/operations.js +6 -0
- package/dist/schema/plugin-activation.d.ts +13 -0
- package/dist/schema/plugin-activation.js +18 -0
- package/dist/schema/plugin-editor-context.d.ts +33 -0
- package/dist/schema/plugin-editor-context.js +65 -0
- package/dist/schema/plugin-install.d.ts +58 -0
- package/dist/schema/plugin-install.js +132 -0
- package/dist/schema/plugin-kind.d.ts +21 -0
- package/dist/schema/plugin-kind.js +86 -0
- package/dist/schema/plugin-name.d.ts +1 -0
- package/dist/schema/plugin-name.js +7 -0
- package/dist/schema/register-once.d.ts +9 -0
- package/dist/schema/register-once.js +35 -0
- package/dist/schema/registration-checks.d.ts +32 -0
- package/dist/schema/registration-checks.js +122 -0
- package/dist/schema/registration-pending.d.ts +28 -0
- package/dist/schema/registration-pending.js +48 -0
- package/dist/schema/registry-reset.d.ts +7 -0
- package/dist/schema/registry-reset.js +30 -0
- package/dist/schema/registry-view.d.ts +30 -0
- package/dist/schema/registry-view.js +39 -0
- package/dist/schema/reserved-chords.d.ts +35 -0
- package/dist/schema/reserved-chords.js +313 -0
- package/dist/schema/reserved-chrome.d.ts +23 -0
- package/dist/schema/reserved-chrome.js +33 -0
- package/dist/schema/table-cell-raw.d.ts +14 -0
- package/dist/schema/table-cell-raw.js +30 -0
- package/dist/schema/whole-block-unit.d.ts +8 -0
- package/dist/schema/whole-block-unit.js +12 -0
- package/dist/search/document-scan.d.ts +22 -0
- package/dist/search/document-scan.js +39 -0
- package/dist/search/matcher.d.ts +31 -0
- package/dist/search/matcher.js +70 -0
- package/dist/search/regex-executor.d.ts +41 -0
- package/dist/search/regex-executor.js +165 -0
- package/dist/search/replace.d.ts +15 -0
- package/dist/search/replace.js +29 -0
- package/dist/search/search-state.svelte.d.ts +59 -0
- package/dist/search/search-state.svelte.js +231 -0
- package/dist/selection/autoscroll.d.ts +26 -0
- package/dist/selection/autoscroll.js +91 -0
- package/dist/selection/block-hit-test.d.ts +37 -0
- package/dist/selection/block-hit-test.js +59 -0
- package/dist/selection/caret-doors.d.ts +20 -0
- package/dist/selection/caret-doors.js +44 -0
- package/dist/selection/caret-restore.d.ts +15 -0
- package/dist/selection/caret-restore.js +32 -0
- package/dist/selection/char-endpoint-snap.d.ts +15 -0
- package/dist/selection/char-endpoint-snap.js +39 -0
- package/dist/selection/clipboard-text.d.ts +13 -0
- package/dist/selection/clipboard-text.js +314 -0
- package/dist/selection/cross-block/clipboard.d.ts +14 -0
- package/dist/selection/cross-block/clipboard.js +20 -0
- package/dist/selection/cross-block/dispatch.d.ts +78 -0
- package/dist/selection/cross-block/dispatch.js +57 -0
- package/dist/selection/cross-block/format-range.d.ts +38 -0
- package/dist/selection/cross-block/format-range.js +193 -0
- package/dist/selection/cross-block/format-toggle.d.ts +27 -0
- package/dist/selection/cross-block/format-toggle.js +109 -0
- package/dist/selection/cross-block/keydown.d.ts +11 -0
- package/dist/selection/cross-block/keydown.js +339 -0
- package/dist/selection/cross-block/ops.d.ts +56 -0
- package/dist/selection/cross-block/ops.js +210 -0
- package/dist/selection/cross-block/paste.d.ts +8 -0
- package/dist/selection/cross-block/paste.js +156 -0
- package/dist/selection/cross-block/pointer.d.ts +19 -0
- package/dist/selection/cross-block/pointer.js +80 -0
- package/dist/selection/cross-block/type-replace.d.ts +10 -0
- package/dist/selection/cross-block/type-replace.js +129 -0
- package/dist/selection/dead-space-caret.d.ts +39 -0
- package/dist/selection/dead-space-caret.js +173 -0
- package/dist/selection/drag-pointer.d.ts +22 -0
- package/dist/selection/drag-pointer.js +89 -0
- package/dist/selection/gap-caret.d.ts +47 -0
- package/dist/selection/gap-caret.js +72 -0
- package/dist/selection/keyboard-extend.d.ts +43 -0
- package/dist/selection/keyboard-extend.js +257 -0
- package/dist/selection/native-bridge.d.ts +47 -0
- package/dist/selection/native-bridge.js +204 -0
- package/dist/selection/nearest-block.d.ts +43 -0
- package/dist/selection/nearest-block.js +73 -0
- package/dist/selection/path-lookup.d.ts +32 -0
- package/dist/selection/path-lookup.js +132 -0
- package/dist/selection/path-math.d.ts +35 -0
- package/dist/selection/path-math.js +90 -0
- package/dist/selection/pointer-session.d.ts +43 -0
- package/dist/selection/pointer-session.js +111 -0
- package/dist/selection/primitives.d.ts +75 -0
- package/dist/selection/primitives.js +91 -0
- package/dist/selection/range-delete-ceremony.d.ts +85 -0
- package/dist/selection/range-delete-ceremony.js +197 -0
- package/dist/selection/range-delete-chrome.d.ts +39 -0
- package/dist/selection/range-delete-chrome.js +105 -0
- package/dist/selection/range-delete-table-coverage.d.ts +28 -0
- package/dist/selection/range-delete-table-coverage.js +168 -0
- package/dist/selection/range-delete-table.d.ts +16 -0
- package/dist/selection/range-delete-table.js +347 -0
- package/dist/selection/range-delete.d.ts +33 -0
- package/dist/selection/range-delete.js +121 -0
- package/dist/selection/selection-description.d.ts +7 -0
- package/dist/selection/selection-description.js +18 -0
- package/dist/selection/selection-restore.d.ts +46 -0
- package/dist/selection/selection-restore.js +66 -0
- package/dist/selection/selection-state.svelte.d.ts +72 -0
- package/dist/selection/selection-state.svelte.js +247 -0
- package/dist/selection/shared-keydown.d.ts +61 -0
- package/dist/selection/shared-keydown.js +150 -0
- package/dist/selection/table-endpoint-snap.d.ts +55 -0
- package/dist/selection/table-endpoint-snap.js +143 -0
- package/dist/selection/table-rect-extend.d.ts +23 -0
- package/dist/selection/table-rect-extend.js +46 -0
- package/dist/selection/widget-range-paint.d.ts +12 -0
- package/dist/selection/widget-range-paint.js +44 -0
- package/dist/styles/editor-theme.css +151 -0
- package/dist/styles/editor.css +585 -0
- package/dist/testing/conformance-core.d.ts +45 -0
- package/dist/testing/conformance-core.js +105 -0
- package/dist/testing/container-conformance.d.ts +137 -0
- package/dist/testing/container-conformance.js +533 -0
- package/dist/testing/headless-actions.d.ts +37 -0
- package/dist/testing/headless-actions.js +118 -0
- package/dist/testing/inline-conformance.d.ts +50 -0
- package/dist/testing/inline-conformance.js +442 -0
- package/dist/testing/kind-conformance.d.ts +54 -0
- package/dist/testing/kind-conformance.js +325 -0
- package/dist/testing/mount-dom-stubs.d.ts +8 -0
- package/dist/testing/mount-dom-stubs.js +19 -0
- package/dist/testing/parse-convergence.d.ts +14 -0
- package/dist/testing/parse-convergence.js +81 -0
- package/dist/testing.d.ts +20 -0
- package/dist/testing.js +61 -0
- package/dist/tree-operations/blockquote.d.ts +18 -0
- package/dist/tree-operations/blockquote.js +59 -0
- package/dist/tree-operations/children.d.ts +17 -0
- package/dist/tree-operations/children.js +56 -0
- package/dist/tree-operations/cleanup.d.ts +10 -0
- package/dist/tree-operations/cleanup.js +25 -0
- package/dist/tree-operations/clone.d.ts +5 -0
- package/dist/tree-operations/clone.js +43 -0
- package/dist/tree-operations/container-lift.d.ts +9 -0
- package/dist/tree-operations/container-lift.js +29 -0
- package/dist/tree-operations/index.d.ts +16 -0
- package/dist/tree-operations/index.js +14 -0
- package/dist/tree-operations/list/empty-check.d.ts +7 -0
- package/dist/tree-operations/list/empty-check.js +19 -0
- package/dist/tree-operations/list/exit-replacement.d.ts +13 -0
- package/dist/tree-operations/list/exit-replacement.js +49 -0
- package/dist/tree-operations/list/item-partition.d.ts +13 -0
- package/dist/tree-operations/list/item-partition.js +33 -0
- package/dist/tree-operations/list/list-builders.d.ts +44 -0
- package/dist/tree-operations/list/list-builders.js +129 -0
- package/dist/tree-operations/list/ordered-markers.d.ts +36 -0
- package/dist/tree-operations/list/ordered-markers.js +114 -0
- package/dist/tree-operations/list/reconcile-task.d.ts +12 -0
- package/dist/tree-operations/list/reconcile-task.js +47 -0
- package/dist/tree-operations/list/sublist-separator.d.ts +13 -0
- package/dist/tree-operations/list/sublist-separator.js +34 -0
- package/dist/tree-operations/list/terminator.d.ts +17 -0
- package/dist/tree-operations/list/terminator.js +60 -0
- package/dist/tree-operations/list/unwrap-merge.d.ts +29 -0
- package/dist/tree-operations/list/unwrap-merge.js +202 -0
- package/dist/tree-operations/node-ops.d.ts +296 -0
- package/dist/tree-operations/node-ops.js +1428 -0
- package/dist/tree-operations/parse-block.d.ts +3 -0
- package/dist/tree-operations/parse-block.js +8 -0
- package/dist/tree-operations/paste/apply.d.ts +12 -0
- package/dist/tree-operations/paste/apply.js +75 -0
- package/dist/tree-operations/paste/body-write.d.ts +19 -0
- package/dist/tree-operations/paste/body-write.js +53 -0
- package/dist/tree-operations/paste/container-match.d.ts +32 -0
- package/dist/tree-operations/paste/container-match.js +238 -0
- package/dist/tree-operations/paste/container-paste.d.ts +10 -0
- package/dist/tree-operations/paste/container-paste.js +27 -0
- package/dist/tree-operations/paste/dispatch.d.ts +61 -0
- package/dist/tree-operations/paste/dispatch.js +138 -0
- package/dist/tree-operations/paste/find-enclosing-list.d.ts +8 -0
- package/dist/tree-operations/paste/find-enclosing-list.js +30 -0
- package/dist/tree-operations/paste/focus-target.d.ts +27 -0
- package/dist/tree-operations/paste/focus-target.js +40 -0
- package/dist/tree-operations/paste/hooks.d.ts +17 -0
- package/dist/tree-operations/paste/hooks.js +80 -0
- package/dist/tree-operations/paste/list-absorb.d.ts +29 -0
- package/dist/tree-operations/paste/list-absorb.js +106 -0
- package/dist/tree-operations/paste/list-break-out.d.ts +42 -0
- package/dist/tree-operations/paste/list-break-out.js +136 -0
- package/dist/tree-operations/paste/parent-scope.d.ts +17 -0
- package/dist/tree-operations/paste/parent-scope.js +35 -0
- package/dist/tree-operations/paste/paste-deps.d.ts +20 -0
- package/dist/tree-operations/paste/paste-deps.js +6 -0
- package/dist/tree-operations/paste/paste-replacement.d.ts +8 -0
- package/dist/tree-operations/paste/paste-replacement.js +60 -0
- package/dist/tree-operations/paste/paste-transforms.d.ts +25 -0
- package/dist/tree-operations/paste/paste-transforms.js +72 -0
- package/dist/tree-operations/paste/replace-block-at-parent.d.ts +29 -0
- package/dist/tree-operations/paste/replace-block-at-parent.js +75 -0
- package/dist/tree-operations/paste/strategy.d.ts +9 -0
- package/dist/tree-operations/paste/strategy.js +20 -0
- package/dist/tree-operations/paste/table-slice.d.ts +10 -0
- package/dist/tree-operations/paste/table-slice.js +39 -0
- package/dist/tree-operations/paste-surfaces.d.ts +61 -0
- package/dist/tree-operations/paste-surfaces.js +15 -0
- package/dist/tree-operations/path-mutate.d.ts +12 -0
- package/dist/tree-operations/path-mutate.js +20 -0
- package/dist/tree-operations/reorder-unit.d.ts +17 -0
- package/dist/tree-operations/reorder-unit.js +37 -0
- package/dist/tree-operations/reorder.d.ts +13 -0
- package/dist/tree-operations/reorder.js +57 -0
- package/dist/tree-operations/sharing.d.ts +18 -0
- package/dist/tree-operations/sharing.js +14 -0
- package/dist/tree-operations/splice-many.d.ts +9 -0
- package/dist/tree-operations/splice-many.js +22 -0
- package/dist/tree-operations/structural-change.d.ts +65 -0
- package/dist/tree-operations/structural-change.js +117 -0
- package/dist/tree-operations/sub-table-copy.d.ts +6 -0
- package/dist/tree-operations/sub-table-copy.js +43 -0
- package/dist/tree-operations/table-mutations.d.ts +17 -0
- package/dist/tree-operations/table-mutations.js +101 -0
- package/dist/tree-operations/unshare.d.ts +101 -0
- package/dist/tree-operations/unshare.js +248 -0
- package/dist/undo/manager.d.ts +2 -0
- package/dist/undo/manager.js +50 -0
- package/dist/undo/types.d.ts +44 -0
- package/dist/undo/types.js +7 -0
- package/docs/guide/consumer-guide.md +1098 -0
- package/docs/guide/directives.md +223 -0
- package/docs/guide/plugin-api.md +329 -0
- package/docs/guide/plugin-guide/conspiracy.gif +0 -0
- package/docs/guide/plugin-guide/parrot-frames.md +209 -0
- package/docs/guide/plugin-guide/parrot.gif +0 -0
- package/docs/guide/plugin-guide.md +1693 -0
- package/docs/guide/plugin-testing.md +463 -0
- package/package.json +238 -0
|
@@ -0,0 +1,1098 @@
|
|
|
1
|
+
# Consumer Guide
|
|
2
|
+
|
|
3
|
+
How to embed, theme, and wire the editor as a library. Everything else has its own doc:
|
|
4
|
+
|
|
5
|
+
- The [plugin guide](plugin-guide.md): teaching the editor new blocks or new inline syntax.
|
|
6
|
+
- The [plugin API reference](plugin-api.md): all the methods in the full authoring surface.
|
|
7
|
+
- The [plugin testing guide](plugin-testing.md): proving a plugin behaves before you ship it.
|
|
8
|
+
- The [directive grammar](directives.md): what a `:::name` block may look like.
|
|
9
|
+
|
|
10
|
+
All of these ship in the npm package under `docs/guide` (this one included), so whatever's in your `node_modules` matches the version you installed.
|
|
11
|
+
|
|
12
|
+
This is gonna be a long one, so here are the sections:
|
|
13
|
+
|
|
14
|
+
| Section | What it covers |
|
|
15
|
+
| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
|
|
16
|
+
| [What you are embedding](#what-you-are-embedding) | What you're mounting, how you save, and a few things worth knowing before the rest |
|
|
17
|
+
| [What your build needs](#what-your-build-needs) | Node version, TypeScript settings, and how the package expects to be bundled |
|
|
18
|
+
| [The public surface](#the-public-surface) | What the package exports, and what the version number promises about it |
|
|
19
|
+
| [Props](#props) | Every prop, and which ones you can change after mount |
|
|
20
|
+
| [The instance surface](#the-instance-surface) | The methods on a mounted editor: read the document, move the caret, run commands |
|
|
21
|
+
| [Events](#events) | The five channels an editor reports on, and what each one carries |
|
|
22
|
+
| [Presentation modes](#presentation-modes) | One document shown five ways, from raw Markdown to fully rendered |
|
|
23
|
+
| [Images and links](#images-and-links) | Rewriting URLs, importing pasted images, and which URLs the editor refuses to load |
|
|
24
|
+
| [Plugins](#plugins) | Installing plugins, why the whole app should share one set, and the nine that ship in the box |
|
|
25
|
+
| [Theming](#theming) | The CSS variables the editor reads, and three ways to restyle it |
|
|
26
|
+
| [Keyboard shortcuts](#keyboard-shortcuts) | Every shortcut, how to rebind or disable one, and which keys the editor swallows |
|
|
27
|
+
| [Embedding in a host layout](#embedding-in-a-host-layout) | Letting your page scroll the editor, and putting your own content above the document |
|
|
28
|
+
| [Embedding in a webview shell](#embedding-in-a-webview-shell) | What a desktop wrapper like Tauri or Electron changes about keys and the clipboard, and what to verify by hand |
|
|
29
|
+
| [Diagnostics](#diagnostics) | Getting a usable bug report out of a user's session |
|
|
30
|
+
| [Building your own chrome](#building-your-own-chrome) | Toolbars, highlights, and navigation built around the document, with worked recipes |
|
|
31
|
+
| [Rewriting a document](#rewriting-a-document) | Bulk edits: read the Markdown out, transform it, hand it back |
|
|
32
|
+
|
|
33
|
+
## What you are embedding
|
|
34
|
+
|
|
35
|
+
aragonite is a Markdown editor you mount as a Svelte component. You hand it Markdown and read Markdown back, unchanged (there's no in-between document format). On screen the document is a stack of blocks (a paragraph, a heading, a list, a table), each its own editing surface, with the Markdown markers visible but dimmed.
|
|
36
|
+
|
|
37
|
+
```mermaid
|
|
38
|
+
flowchart LR
|
|
39
|
+
app[your app] -- "source" --> parse[parse]
|
|
40
|
+
parse --> blocks[editable blocks]
|
|
41
|
+
blocks -- "getSource()" --> app
|
|
42
|
+
blocks -- "getEvents()" --> app
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The editor owns the caret, the tree, and the undo stack. You own load, save, and dirty state. When you want the document on disk, call `getSource()` and write it.
|
|
46
|
+
|
|
47
|
+
```svelte
|
|
48
|
+
<script>
|
|
49
|
+
import { Editor } from '@voithos-labs/aragonite';
|
|
50
|
+
import '@voithos-labs/aragonite/styles/editor-theme.css';
|
|
51
|
+
|
|
52
|
+
let editor;
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<Editor bind:this={editor} source={'# Hello\n'} theme="dark" />
|
|
56
|
+
<button onclick={() => save(editor.getSource())}>Save</button>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
A few things in the above example snippet are decently important; you might want to pay attention to them.
|
|
60
|
+
|
|
61
|
+
1. **`source` seeds the document at mount**, and re-seeds it if the prop later changes. It's not a two way bound: the editor never writes back into it, so the document you read is always `getSource()`.
|
|
62
|
+
2. **`bind:this` is how you talk to a mounted editor.** For example, you might want to use important read functions like `getSource()` and `getSelection()`, or important write functions like `setSelection()` and `runCommand()`. [The instance surface](#the-instance-surface) covers all of it.
|
|
63
|
+
3. **Theming is CSS custom properties.** [Theming](#theming) has the variables and how to customize yours.
|
|
64
|
+
|
|
65
|
+
Two more that aren't in the snippet but bite early: plugin registration is process-global and happens once at mount, but each editor activates exactly the plugins its own `plugins` prop lists ([Plugins](#plugins)); and `editor.__test.*` is internal and will move, so don't build on it.
|
|
66
|
+
|
|
67
|
+
## What your build needs
|
|
68
|
+
|
|
69
|
+
Three things. Though, prob already true in sveltekit apps (if thats you, skip ahead).
|
|
70
|
+
|
|
71
|
+
1. **Node `^20.19.0 || >=22.12.0`.** That floor is Vite's, not ours: the package ships uncompiled Svelte, so your bundler's requirement is the real one.
|
|
72
|
+
2. **TypeScript resolves the package through its `exports` map**, which needs one of these in `tsconfig.json`:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{ "compilerOptions": { "moduleResolution": "bundler" } }
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`node16` and `nodenext` work too. Classic `node` doesn't read `exports`, and the symptom is odd: the root `@voithos-labs/aragonite` import typechecks fine while every subpath import (`/plugin`, `/plugins/*`, `/testing`) reports "Cannot find module".
|
|
79
|
+
|
|
80
|
+
3. **Your Vite Svelte plugin compiles the package out of `node_modules`**, since it ships `.svelte` and `.svelte.js` files uncompiled. That's `vite-plugin-svelte`'s default, so a plain config is all it takes:
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
// vite.config.js
|
|
84
|
+
import { sveltekit } from '@sveltejs/kit/vite';
|
|
85
|
+
export default { plugins: [sveltekit()] };
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
If you hand-tune `optimizeDeps` or `ssr.noExternal`, keep `@voithos-labs/aragonite` on the Svelte plugin's side of those lists rather than pre-bundling it.
|
|
89
|
+
|
|
90
|
+
## The public surface
|
|
91
|
+
|
|
92
|
+
Everything supported is exported from `@voithos-labs/aragonite`. Before 1.0 the public surface is still unstable, and the changelog records any change to it. From 1.0 onwards, a breaking change to the surface rides a major version, while additive needs ship as minors. Note, the list below will be (or at least attempted to be) kept up to date; for the actual list of exports see `src/lib/index.ts`.
|
|
93
|
+
|
|
94
|
+
| Group | What you get |
|
|
95
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
96
|
+
| **Component** | `Editor`, plus `EditorProps` and `EditorInstance` (the prop shape and the `bind:this` surface) |
|
|
97
|
+
| **Policy types** | `ResolveImageUrl`, `ResolveLinkUrl`, `ImageLoadPolicy` for the URL and image props; `PastedImage` and `PasteImageHook` for the image-import hook |
|
|
98
|
+
| **Plugins** | `installPlugins` for a parse-only pipeline with no editor mounted; `EditorPlugin` (the unit a plugin exports) and `EditorPluginEntry` (a `plugins` array entry: a bare unit, or `{ plugin, options }`) |
|
|
99
|
+
| **Selection + keymap** | `EditorSelection` (what `getSelection()` returns) and `normalizeSelection`, which puts a selection's two endpoints in document order; `KeybindingOverride` and `CommandId` (what the `keybindings` prop takes) |
|
|
100
|
+
| **Commands** | `TOOLBAR_COMMANDS`, the command ids a formatting toolbar calls through `runCommand` |
|
|
101
|
+
| **Search** | `SearchState`, `SearchOptions`, `Match`: the find/replace controller, its options, and one hit |
|
|
102
|
+
| **Decorations** | `DecorationRegistry` and the decoration types: what `getDecorations()` returns |
|
|
103
|
+
| **Rects** | `EditorRects` (what `getRects()` returns: on-screen geometry over the document) and `SELECTION_END`, the value its range calls accept as "through the end of the block" |
|
|
104
|
+
| **CST utilities** | `parse` / `serialize` for round-tripping Markdown outside the component (CST: the concrete syntax tree, the parsed form of a document); `parseInline`, `getContentRange`, `isProseKind` for inspecting a block's inline content and editable range |
|
|
105
|
+
| **Node types** | `CstNode`, `Document`, the block-kind and inline-node unions, and the per-kind metadata shapes: the vocabulary for reading a parsed document. `NodeView` / `DocumentView` are their read-only forms; every node the editor hands you to read is typed as a view, so mutating the live tree is a compile error, not a convention |
|
|
106
|
+
| **Events** | `EditorEvents` and the payload types the subscription surface emits |
|
|
107
|
+
| **Diagnostics** | `EditorDiagnostics` (what `getDiagnostics()` returns) and `InteractionTraceEntry` |
|
|
108
|
+
|
|
109
|
+
## Props
|
|
110
|
+
|
|
111
|
+
| Prop | What it does |
|
|
112
|
+
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
113
|
+
| `source` | Seeds the document at mount, and re-seeds it whenever the prop changes; never two-way bound |
|
|
114
|
+
| `theme` | Theme name, reflected to `data-editor-theme` on the editor root: `'dark'` (default), `'light'`, or a name of your own (see [Theming](#theming)) |
|
|
115
|
+
| `presentationMode` | How the document presents, from raw source to fully rendered: `'source'` (default), `'reading'`, `'preview-block'`, `'preview-inline'`, or `'live'` (see [Presentation modes](#presentation-modes)) |
|
|
116
|
+
| `plugins` | Plugin units installed once at mount, in array order, before the first parse; the array is also the set this editor activates (see [Plugins](#plugins)) |
|
|
117
|
+
| `keybindings` | Rebind or disable the editor's shortcuts, per instance (see [Rebinding chords](#rebinding-chords)) |
|
|
118
|
+
| `resolveImageUrl` | Rewrite a raw image URL before it reaches `img.src` (to resolve a relative path, say) |
|
|
119
|
+
| `resolveLinkUrl` | Rewrite a raw link destination at render time |
|
|
120
|
+
| `imageLoadPolicy` | `'auto'` (load images) or `'placeholder'` (defer loading) |
|
|
121
|
+
| `onLinkActivate` | Handle an activated link (Ctrl/Cmd+click while editing, plain click in reading mode) instead of the default `window.open` |
|
|
122
|
+
| `onPasteImage` | Import hook for a paste that carries image files: you store them, and return the Markdown that stands in (see [Image paste](#image-paste)) |
|
|
123
|
+
| `header` | Your own UI above the first block, rendered inside the editor's scroll container (see [The header slot](#the-header-slot)) |
|
|
124
|
+
| `scrollMode` | `'self'` (default: the editor scrolls itself) or `'host'` (an ancestor of yours scrolls it; see [Host scroll mode](#host-scroll-mode)) |
|
|
125
|
+
| `blockDragHandles` | Opt into the pointer affordances: the block drag handle and the table's row and column grips, revealed on hover and shown outright on touch (default off); keyboard reorder (Alt+Arrow) and the cell menu need no opt-in |
|
|
126
|
+
| `searchBar` | The built-in find/replace bar and its Mod+F / Mod+H shortcuts (default on) |
|
|
127
|
+
| `searchBarAnchor` | An element to render that same bar into, instead of inside the editor root (see [Where the find bar lives](#where-the-find-bar-lives)) |
|
|
128
|
+
|
|
129
|
+
**Set once at mount:** `resolveImageUrl`, `resolveLinkUrl`, `imageLoadPolicy`, `onLinkActivate`, `onPasteImage`, `blockDragHandles`, `scrollMode`, and `plugins`. Set them at mount and leave them; a swap later isn't guaranteed to reach blocks that are already built.
|
|
130
|
+
|
|
131
|
+
**Read live:** `theme`, `searchBar`, `searchBarAnchor`, `presentationMode`, and `keybindings` may change after mount, and `header` re-renders like any other Svelte snippet.
|
|
132
|
+
|
|
133
|
+
## The instance surface
|
|
134
|
+
|
|
135
|
+
You set the editor up with props when it mounts. After that you talk to it through the `bind:this` handle. Here's what you can read:
|
|
136
|
+
|
|
137
|
+
| Method | What it answers |
|
|
138
|
+
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
139
|
+
| `getSource()` | The live document, serialized back to Markdown |
|
|
140
|
+
| `getSelection()` | A frozen snapshot of the current selection, or `null` |
|
|
141
|
+
| `getBlockKindAt(path)` | What kind of block sits at a path, or `null` |
|
|
142
|
+
| `canRunCommand(id)` / `isCommandActive(id)` | Whether a toolbar button should be enabled, and whether it should paint pressed (see [Toolbar commands](#toolbar-commands)) |
|
|
143
|
+
| `getEvents()` | The subscription surface (see [Events](#events)) |
|
|
144
|
+
| `getSearch()` | The find/replace controller (see [Driving search yourself](#driving-search-yourself)) |
|
|
145
|
+
| `getRects()` | Where things are on screen (see [Screen geometry](#screen-geometry)) |
|
|
146
|
+
| `getDecorations()` | The registry for your own view-only annotations (see [Decorations](#decorations)) |
|
|
147
|
+
| `getDiagnostics()` | The bug-report tooling (see [Diagnostics](#diagnostics)) |
|
|
148
|
+
| `reservedChords()` / `claimsChord(event)` | Which shortcuts this editor consumes (see [Which shortcuts the editor consumes](#which-shortcuts-the-editor-consumes)) |
|
|
149
|
+
|
|
150
|
+
And what you can write:
|
|
151
|
+
|
|
152
|
+
| Method | What it does |
|
|
153
|
+
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
154
|
+
| `setSelection(snapshot)` | Puts a `getSelection()` snapshot back on the document (see [Restoring a selection](#restoring-a-selection)) |
|
|
155
|
+
| `placeCaretAtPoint(x, y)` | Lands the caret at a viewport point, exactly as a click there would (see [Placing the caret at a point](#placing-the-caret-at-a-point)) |
|
|
156
|
+
| `insertMarkdown(md)` | Inserts Markdown at the caret, exactly as pasting it would (see [Inserting Markdown at the caret](#inserting-markdown-at-the-caret)) |
|
|
157
|
+
| `runCommand(id)` | Runs an editor command by name, no keystroke involved (see [Toolbar commands](#toolbar-commands)) |
|
|
158
|
+
|
|
159
|
+
### Reading the document and the selection
|
|
160
|
+
|
|
161
|
+
Before the methods, there are some terms you ought to understand.
|
|
162
|
+
|
|
163
|
+
A **path** is the list of child indices that walks from the document root down to a block: `[3]` is the fourth top-level block, `[3, 0]` is the first child inside it.
|
|
164
|
+
|
|
165
|
+
**Virtual rendering** (aka windowing) means the editor only mounts the blocks near the viewport, which is why big documents stay fast. So a block can exist in the document and not in the DOM, and a method that targets one sometimes has to **reveal** it (scroll until it mounts) before it can act.
|
|
166
|
+
|
|
167
|
+
`getBlockKindAt(path: number[]): AnyBlockKind | null`
|
|
168
|
+
|
|
169
|
+
Tells you what kind of block sits at a path, or `null` if nothing does (an out-of-range index, or the empty path, which is the document itself). Handy for questions like "is that selection endpoint inside a table?" without walking the document yourself. Plugin blocks answer with the name they were registered under.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
editor.getBlockKindAt([0]); // 'heading'
|
|
173
|
+
editor.getBlockKindAt([3]); // 'table'
|
|
174
|
+
editor.getBlockKindAt([3, 0]); // 'tableRow'
|
|
175
|
+
editor.getBlockKindAt([99]); // null
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`getSelection(): EditorSelection | null`
|
|
179
|
+
|
|
180
|
+
Returns a snapshot of the current selection (a copy, so changing it changes nothing), or `null` when the editor isn't focused.
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
editor.getSelection();
|
|
184
|
+
// {
|
|
185
|
+
// anchor: { path: [3], offset: 12 },
|
|
186
|
+
// focus: { path: [5, 1], offset: 0 }
|
|
187
|
+
// }
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`anchor` is where the selection started and `focus` is where it ends, so a plain caret has the two equal. `offset` is a character index into the block's source, with one exception: inside a table it's a cell index (row by row), and the point carries `cellCoordinate: true` to say so. Well, mostly. A selection lying wholly inside one table uses cell indices without the flag, so check the block's kind (`getBlockKindAt(anchor.path) === 'table'`) before you trust `offset` as a character. [The selection toolbar recipe](#recipe-a-selection-toolbar) shows this in place.
|
|
191
|
+
|
|
192
|
+
### Restoring a selection
|
|
193
|
+
|
|
194
|
+
`setSelection(selection: EditorSelection): Promise<boolean>`
|
|
195
|
+
|
|
196
|
+
Puts a `getSelection()` snapshot back, say to restore the caret you saved for a document, or after swapping `source`. It's async because the target block may not be mounted yet; the restore reveals it first.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
const saved = editor.getSelection();
|
|
200
|
+
// later, after a reload or a source swap
|
|
201
|
+
const ok = await editor.setSelection(saved); // true when placed and in view
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
`true` means placed **and** in view, the same way `scrollTo` answers ([Screen geometry](#screen-geometry)). [The insert toolbar recipe](#recipe-an-insert-toolbar) uses this stash-and-restore to survive a focus-stealing button.
|
|
205
|
+
|
|
206
|
+
`false` never throws, and covers three shapes:
|
|
207
|
+
|
|
208
|
+
| Why it answered `false` | What happened anyway |
|
|
209
|
+
| -------------------------------------------------------------- | --------------------------------------------------------- |
|
|
210
|
+
| The path no longer addresses any block | Nothing: no scroll, no focus steal, no state write |
|
|
211
|
+
| The path resolves, but its block's element never appeared | The scroll ran, and cross-block state was re-established |
|
|
212
|
+
| The caret placed, but the scroll could not settle it into view | The caret is placed; only the viewport ended up elsewhere |
|
|
213
|
+
|
|
214
|
+
Two notes on the third shape:
|
|
215
|
+
|
|
216
|
+
- Since 0.9.36 it has one more trigger: a later programmatic reveal (your own `scrollTo` or `navigateTo`, or the find bar navigating) issued before this restore settles takes the viewport, and the restore stops competing rather than fighting the newer target. An ordinary user gesture is not that case; typing, clicking, or scrolling while a restore settles changes nothing about the outcome.
|
|
217
|
+
- Branch on it as "the viewport did not end up where I asked", never as "nothing happened". Re-placing a fallback selection there would discard a caret that landed correctly.
|
|
218
|
+
|
|
219
|
+
Out-of-range offsets clamp, each in its own coordinate space: a character offset clamps to the block's source length, and an endpoint addressing a table clamps to the last cell, so a huge offset there becomes the bottom-right cell rather than a character position.
|
|
220
|
+
|
|
221
|
+
What `selectionChange` reports while a restore runs:
|
|
222
|
+
|
|
223
|
+
- **On success, every emission carries the restored selection.** The editor holds the channel until the state write and the caret landing have both happened, so a handler that treats the first event as authoritative (a persist-on-change host, say) saves the right one. Reading back with `getSelection()` after the await is still correct, just no longer necessary.
|
|
224
|
+
- **The browser's own `selectionchange` may still deliver a trailing duplicate** of the same value, so make the handler idempotent rather than counting events.
|
|
225
|
+
- **The failed-placement `false` is the exception.** A collapsed or within-block restore into a resolvable-but-unmounted block clears the old selection and then finds no element, so its one emission reports what was there before. Treat a `false` restore as "read the selection back", not as an authoritative event.
|
|
226
|
+
|
|
227
|
+
### Placing the caret at a point
|
|
228
|
+
|
|
229
|
+
`placeCaretAtPoint(x: number, y: number): boolean`
|
|
230
|
+
|
|
231
|
+
Lands the caret at a viewport point the way a click there would, and tells you whether it landed. It's for a shell that owns UI beside the document (a margin, a gutter): you decide whether a click on your territory should go to the editor, the editor decides where the caret goes. `false` means nothing focusable was under the point, so the click is still yours to handle. The dead-margin note in [Host scroll mode](#host-scroll-mode) ends in exactly this call.
|
|
232
|
+
|
|
233
|
+
```ts
|
|
234
|
+
gutter.addEventListener('click', (e) => {
|
|
235
|
+
const landed = editor.placeCaretAtPoint(e.clientX, e.clientY); // true
|
|
236
|
+
if (!landed) openGutterMenu(e);
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Two things about where a point lands:
|
|
241
|
+
|
|
242
|
+
- **A point resolves against the blocks on screen**, with one exception: a point below the whole document lands at the real end, even when virtual rendering has the tail unmounted. That landing has to mount its target first, so the call returns `true` and the caret shows up a frame or two later. The exception is there so your own below-the-editor click handler and this call agree on which block they mean.
|
|
243
|
+
- **A point can land between two blocks.** Above a document that opens with a table, say, the caret parks at the document-start boundary rather than clamping into the table. That caret is real (typing there inserts a paragraph) but it isn't part of the public selection shape in this version, so the call returns `true` while `getSelection()` reports `null`.
|
|
244
|
+
|
|
245
|
+
### Inserting Markdown at the caret
|
|
246
|
+
|
|
247
|
+
`insertMarkdown(md: string): boolean`
|
|
248
|
+
|
|
249
|
+
Inserts Markdown at the caret the way a paste would. `md` is any Markdown string, `**hi**` or a whole table. The usual caller is a toolbar button inserting a canned snippet; [the insert toolbar recipe](#recipe-an-insert-toolbar) is built on this call.
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
editor.insertMarkdown('**hi**'); // true
|
|
253
|
+
editor.insertMarkdown('| a | b |\n| --- | --- |\n| | |\n'); // true, and a table lands as a block
|
|
254
|
+
editor.insertMarkdown('**hi**'); // false with no caret (reading mode, or focus outside the editor)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
One call runs the whole paste route:
|
|
258
|
+
|
|
259
|
+
1. Any registered paste transforms rewrite the text first (a plugin hook, see the [plugin guide](plugin-guide.md)).
|
|
260
|
+
2. A live selection is deleted, then the text is spliced in the way a paste would pick: a table as a block, a one-liner inline at the caret, list items absorbed into a matching list.
|
|
261
|
+
3. Focus lands at the end of the insertion, and the whole thing is one undo entry.
|
|
262
|
+
|
|
263
|
+
`false` means nothing changed: no caret in this editor, reading mode, or a caret parked between two blocks. `true` means the pipeline took the text, not that the edit has landed yet, so read the result off the `edit` channel rather than calling `getSource()` on the next line.
|
|
264
|
+
|
|
265
|
+
### Toolbar commands
|
|
266
|
+
|
|
267
|
+
`runCommand(commandId: string): boolean`
|
|
268
|
+
|
|
269
|
+
Runs an editor command by name at the focused block, no keystroke involved. It's what a formatting button calls: the button means "toggle bold", not "press Ctrl+B", so a user who rebinds the shortcut moves it without silently rewiring your button. The command behaves exactly as it would from the keyboard: same edit, one undo entry, caret and selection left where the keystroke would leave them.
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { TOOLBAR_COMMANDS } from '@voithos-labs/aragonite';
|
|
273
|
+
|
|
274
|
+
editor.runCommand(TOOLBAR_COMMANDS.toggleStrong); // true, the selection is now **bold**
|
|
275
|
+
editor.runCommand('format.toggleStrong'); // same thing, by the raw id
|
|
276
|
+
editor.runCommand('nope'); // false, unknown id, nothing changed
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
The ids you can pass:
|
|
280
|
+
|
|
281
|
+
- **`TOOLBAR_COMMANDS`** (exported from the package) has what a selection toolbar needs: `toggleStrong`, `toggleEmphasis`, `toggleStrikethrough`, `toggleCode`, and `editLink`. The rest of the built-in commands stay internal for now.
|
|
282
|
+
- **A plugin's global command name.** `registerGlobalCommand` registers it (see the [plugin guide](plugin-guide.md)), and it resolves ahead of the focused block, so you can fire a plugin's editor-wide action without a keystroke. A plugin's per-block command stays keyboard-only.
|
|
283
|
+
|
|
284
|
+
What the boolean means:
|
|
285
|
+
|
|
286
|
+
- **`true` means the editor took the command, not that the edit has landed.** A toggle inside a construct whose markers a preview mode has revealed (see [Presentation modes](#presentation-modes)) settles that reveal first, so read the outcome on the `edit` channel rather than polling `getSource()`.
|
|
287
|
+
- **`false` means nothing changed**: an unknown id, reading mode, a command that needs a focused block when none is, or the link editor over a selection spanning blocks (a link lives inside one block, and a range across blocks gives it none).
|
|
288
|
+
|
|
289
|
+
Two more things before you wire buttons:
|
|
290
|
+
|
|
291
|
+
- **`editLink` only does something in `'live'` mode**, where a link's destination is hidden. In every other editable mode the URL is already on screen, so the call is consumed (`true`) and no card opens, same as pressing `Mod+K` there.
|
|
292
|
+
- **Over a selection spanning blocks**, a format toggle rewrites every block the range touches (the first block's tail, each middle block whole, the last block's head) as one undo entry. It applies everywhere unless every block already carries the mark, in which case it removes it everywhere. Blocks that can't hold inline syntax (a code block, a thematic break) are skipped and the rest still change. A table joins by its cells: the range covers each cell whole, so the cells it lights up are the cells it marks.
|
|
293
|
+
|
|
294
|
+
`canRunCommand(commandId: string): boolean`
|
|
295
|
+
|
|
296
|
+
Tells you whether `runCommand(id)` would reach the command right now, which is what greys a toolbar button out instead of hiding it. It answers `false` exactly where `runCommand` declines before dispatch: an unknown id, reading mode, a block-scoped id with nothing focused, and the link editor while the selection spans blocks. `true` means reachable, not that it'll write (across blocks it may find no block that can hold the mark), so keep reading `runCommand`'s boolean too.
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
// with a selection spanning two paragraphs
|
|
300
|
+
editor.canRunCommand(TOOLBAR_COMMANDS.toggleStrong); // true
|
|
301
|
+
editor.canRunCommand(TOOLBAR_COMMANDS.editLink); // false, a link can't span blocks
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
`isCommandActive(commandId: string): boolean`
|
|
305
|
+
|
|
306
|
+
Tells you whether the command's toggle reads ON at the caret or selection, which is what a toolbar paints a pressed state from. It reads the same bytes the toggle would rewrite, so the pressed paint and the press can't disagree. It's state, not permission: it composes with `canRunCommand` rather than repeating it, and a disabled button may still paint pressed.
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
// caret inside **bold**
|
|
310
|
+
editor.isCommandActive(TOOLBAR_COMMANDS.toggleStrong); // true
|
|
311
|
+
editor.isCommandActive(TOOLBAR_COMMANDS.toggleEmphasis); // false
|
|
312
|
+
editor.isCommandActive(TOOLBAR_COMMANDS.editLink); // true only in live mode, with the caret in a link
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Three details:
|
|
316
|
+
|
|
317
|
+
- Over a selection spanning blocks it reports the range's coverage: `true` only when every block the range touches already carries the mark, the same reading the press uses.
|
|
318
|
+
- A selection with no text to format (one lying entirely inside a run of markers, the `**`s themselves, which source mode lets you select) keeps its pressed paint while the press writes nothing: the read reports the run around the selection, and the press declines rather than guessing.
|
|
319
|
+
- The link editor isn't a mark, so it reads its own state: `true` in live mode when the caret, or a selection lying wholly inside a link, sits in the link its card would edit. `false` for an id with no state of its own, and with nothing focused.
|
|
320
|
+
|
|
321
|
+
Ask both questions on the same `selectionChange`: answering every button there costs one read of the focused block, not one per button. [The selection toolbar recipe](#recipe-a-selection-toolbar) puts all three together.
|
|
322
|
+
|
|
323
|
+
## Events
|
|
324
|
+
|
|
325
|
+
Subscribe through `editor.getEvents()`. `on(name, handler)` returns the function that unsubscribes.
|
|
326
|
+
|
|
327
|
+
```ts
|
|
328
|
+
const events = editor.getEvents();
|
|
329
|
+
const off = events.on('edit', (e) => console.log(e.op, e.path));
|
|
330
|
+
// later
|
|
331
|
+
off();
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Five channels:
|
|
335
|
+
|
|
336
|
+
| Channel | Fires |
|
|
337
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------------------------------- |
|
|
338
|
+
| `edit` | After every applied edit: a structural operation, a batch of typing (consecutive keystrokes flush as one), an undo or redo |
|
|
339
|
+
| `selectionChange` | Whenever the selection changes; the payload is the snapshot, or `null` |
|
|
340
|
+
| `error` | On a failure the editor contained rather than threw |
|
|
341
|
+
| `presentationModeChange` | After a `presentationMode` prop change; the payload is the effective mode (never at mount) |
|
|
342
|
+
| `themeChange` | After a `theme` prop change; the payload is the theme name (never at mount) |
|
|
343
|
+
|
|
344
|
+
Events fire synchronously from wherever they happen, and **a handler must not edit the document**: reentrant edits aren't supported.
|
|
345
|
+
|
|
346
|
+
What each channel hands you:
|
|
347
|
+
|
|
348
|
+
**`edit`** carries an `EditEvent`, `{ op, path, detail?, timestamp }`, discriminated by `op`. The per-operation `detail` shapes live in the source types and grow as operations are added.
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
events.on('edit', (e) => e);
|
|
352
|
+
// { op: 'input', path: [2], detail: { byteLength: 1 }, timestamp: 1788390000412 }
|
|
353
|
+
// { op: 'split', path: [2], detail: { at: 14 }, timestamp: 1788390001033 }
|
|
354
|
+
// { op: 'delete', path: [1], detail: { crossBlock: true }, timestamp: 1788390004120 }
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
`path` is document-absolute for every operation, nested ones and the typing flush included: it walks from the document root to the block that was operated on. One event names one path even when the write spanned several blocks; a `delete` or `updateContent` that did carries `detail.crossBlock: true`, and a host that reconciles incrementally should re-read the whole affected range on those rather than just `path`.
|
|
358
|
+
|
|
359
|
+
**`selectionChange`** carries the `EditorSelection` snapshot, or `null` when nothing is focused.
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
events.on('selectionChange', (sel) => sel);
|
|
363
|
+
// { anchor: { path: [0], offset: 3 }, focus: { path: [0], offset: 3 } }
|
|
364
|
+
// null (focus left the editor)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Read the value the channel settles on rather than counting emissions. Most changes emit once, but a caret landing between two blocks emits a short burst, and its last value is `null`, since a between-blocks caret sits outside the public selection shape. Focus leaving the editor reads `null` too, even where the browser's own range survives unfocused, so a button greyed off this channel can't go stale when the user clicks out.
|
|
368
|
+
|
|
369
|
+
**`error`** carries an `EditorError`, `{ origin, error, context? }`.
|
|
370
|
+
|
|
371
|
+
```ts
|
|
372
|
+
events.on('error', (err) => err);
|
|
373
|
+
// { origin: 'link', error: Error('aragonite: blocked link with disallowed scheme: file:///notes.md'), context: { url: 'file:///notes.md' } }
|
|
374
|
+
// { origin: 'command', error: TypeError(...), context: { kind: 'paragraph', command: 'my.command', plugin: 'my-plugin' } }
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`origin` is one of `subscriber`, `render`, `commit`, `command`, `decoration`, `clipboard`, or `link`, and `context` carries what's known for it:
|
|
378
|
+
|
|
379
|
+
| Origin | `context` |
|
|
380
|
+
| ------------ | -------------------------------------------------------------- |
|
|
381
|
+
| `render` | `path` of the block |
|
|
382
|
+
| `commit` | `op` and `path` |
|
|
383
|
+
| `command` | `kind`, `command`, and `plugin` when a plugin owns the command |
|
|
384
|
+
| `decoration` | `source`, the decoration source's name |
|
|
385
|
+
| `clipboard` | `path` the paste was aimed at, when it was aimed at a range |
|
|
386
|
+
| `link` | `url` the editor refused |
|
|
387
|
+
| `subscriber` | nothing; one of your own handlers threw |
|
|
388
|
+
|
|
389
|
+
**`presentationModeChange`** and **`themeChange`** carry bare values (`'reading'`, `'light'`), not envelopes, and never fire at mount. Only plugin content that paints its own colors needs `themeChange`; anything styled through the tokens rethemes itself through the CSS cascade.
|
|
390
|
+
|
|
391
|
+
## Presentation modes
|
|
392
|
+
|
|
393
|
+
`presentationMode` dials one document from the raw side to the rendered side, and you can switch it at runtime like `theme`. Every mode is CSS over the same render path: the bytes and the offsets are the source document's in all of them.
|
|
394
|
+
|
|
395
|
+
| Mode | What you see | Editable |
|
|
396
|
+
| -------------------- | ---------------------------------------------------------------- | -------- |
|
|
397
|
+
| `'source'` (default) | Styled source, every marker visible but dimmed | yes |
|
|
398
|
+
| `'reading'` | Rendered, no markers, no caret | no |
|
|
399
|
+
| `'preview-block'` | Rendered, except the block holding the caret shows its source | yes |
|
|
400
|
+
| `'preview-inline'` | Rendered, except the construct under the caret shows its markers | yes |
|
|
401
|
+
| `'live'` | Rendered, markers never shown | yes |
|
|
402
|
+
|
|
403
|
+
**`'source'`** is what you get by default: every Markdown marker renders, dimmed, and everything is editable.
|
|
404
|
+
|
|
405
|
+
**`'reading'`** is a rendered reading view, and it writes no bytes. Markers are hidden by CSS (the document and its offsets are untouched), inline widgets (an image, a rendered emoji) draw, and list bullets and numbers show as rendered chrome (chrome: what the editor paints around the text, not bytes in the document). Blocks aren't `contenteditable` here, so there's no caret inside a block and you move around by mouse, the same deal as other reading views (Obsidian's reading mode has no caret either).
|
|
406
|
+
|
|
407
|
+
- Inert: typing, paste, cut, Enter and Backspace, undo and redo, block commands, checkbox toggles, drag handles, table structure edits.
|
|
408
|
+
- Still live: text selection, copy (the rendered text, markers excluded), scrolling, find (not replace), and links, which open on plain click since there's no caret to place.
|
|
409
|
+
- One more thing is live because it isn't an edit: a `<details>` block can be opened and closed, so a reader can actually read a collapsed section. That flip is view state (the source doesn't change, no `edit` event fires, nothing lands on the undo stack) and it's forgotten when you leave the mode, where the document's own `open` attribute is the truth again. A task checkbox stays inert by contrast, because toggling one would rewrite the document.
|
|
410
|
+
|
|
411
|
+
**`'preview-block'`** hides markers block by block, and everything is editable. Every block looks rendered except the one holding the caret, which shows its full styled source. Typing, splitting, merging, selecting, undo, and search all work exactly as in source mode.
|
|
412
|
+
|
|
413
|
+
- Only the one block holding the caret shows source. A container's chrome (a blockquote's border, a directive's gutter) isn't markers and never toggles, and a focused list item shows its own bullet or number as source while its sibling items stay rendered.
|
|
414
|
+
- A block with nothing behind its markers (a bare `# `, an empty fence) keeps them on screen wherever it sits, so it stays visible and editable.
|
|
415
|
+
- The hiding is CSS keyed on which block is focused, so the marker DOM stays put and a click lands the caret at the content it hit; the markers appear around it without shifting it.
|
|
416
|
+
|
|
417
|
+
**`'preview-inline'`** hides markers construct by construct, the Obsidian-style default: the thing under the cursor shows its syntax. Unfocused blocks look exactly like `preview-block`. Inside the focused block, each inline construct (bold, italic, strikethrough, inline code, links, image alt syntax) keeps its markers hidden until the caret enters it. Arrow or click into `**bold**` and the `**`s appear around the caret; leave and they fold back. Nested constructs reveal their whole enclosing chain, so the syntax you're editing is always fully visible, and a revealed construct is ordinary source text, so typing and undo behave exactly as in source mode.
|
|
418
|
+
|
|
419
|
+
- Whole-block syntax (a heading's `## `, code fences) shows whenever its block is focused, as in `preview-block`, and a table reveals as a whole when it's focused rather than construct by construct.
|
|
420
|
+
- The caret is an offset into the block's raw source (its bytes, markers included) and a revealed construct's source is visible, so typing lands exactly where the caret shows; there's no hidden-cursor guesswork.
|
|
421
|
+
- Where two constructs meet at one boundary (`**a***b*`) both reveal and a keystroke inserts between them. Walking left into a construct's opening markers reaches them, and `Home` lands at the first visible position, just inside.
|
|
422
|
+
- A focused list item keeps its bullet or number as rendered chrome here (`preview-block` shows it as source), and escapes (`\`) and hard line breaks reveal whenever their block is focused, not by caret proximity.
|
|
423
|
+
|
|
424
|
+
**`'live'`** is the rendered end that's still fully editable. Where `preview-inline` reveals the construct under the caret, live reveals nothing: `**bold**` renders as bold whether the caret is inside it or not, a heading with a word behind it shows no `## `, and a link shows its text with the destination out of sight. What does stay on screen is chrome with nothing behind it: a construct with no content (a bare `# `, an empty fence) keeps its markers dimmed so the block stays visible and editable, and the first character of content folds them away. Everything a source-mode caret can do still works: typing, selection, `Enter`, `Backspace`, undo, search and replace, tables, drag handles, plugins.
|
|
425
|
+
|
|
426
|
+
Hiding every marker means one screen position can mean two raw offsets wherever a construct's delimiters sit. Live answers that with five rules, each applied in one place so it holds for every gesture:
|
|
427
|
+
|
|
428
|
+
- **A character typed at a hidden edge follows how the caret got there.** Arriving from outside a construct types outside it; walking into it types inside. A construct that never grows at its edges (a link) always takes the outside.
|
|
429
|
+
- **A caret seated at an extreme lands outside.** `Home`, `End`, and collapsing a selection put the caret past the delimiters, not between them. A seat isn't a step, so the direction of the key that produced it doesn't decide the side.
|
|
430
|
+
- **`Enter` inside a construct closes it and reopens it.** Splitting `**bold**` down the middle leaves two balanced constructs rather than one stranded delimiter in each half, and a split link carries its destination into both halves. Where no balanced rewrite shows what the screen showed (a code span whose reopened backticks would collide with its own, say), the split falls back to a plain byte cut.
|
|
431
|
+
- **A join cleans up after itself.** `Backspace`, `Delete`, a range delete, typing over a selection, and a paste all go through the same code: a delimiter run the cut orphaned goes with the cut instead of appearing on screen, and a closer meeting an opener around nothing is dropped. Every candidate cleanup is checked against what the two sides showed, and the byte-literal join stands when it can't be.
|
|
432
|
+
- **The format toggles work at a collapsed caret.** `Mod+B`, `Mod+I`, `Mod+Shift+X`, and `Mod+E` over a selection wrap or unwrap it as always; at a caret they arm the format for the next thing you type, which is what a mode with no visible delimiters needs. A selection ending on a space wraps the word and leaves the space beside it (a run closing against whitespace is no run at all), and a press whose wrap the screen wouldn't survive writes nothing rather than printing delimiters you can't see to delete.
|
|
433
|
+
|
|
434
|
+
Three more live-mode facts:
|
|
435
|
+
|
|
436
|
+
- **Reading a link's destination.** The link card is the only place a URL shows in this mode. `Mod+K` with the caret inside a link opens it with focus in the URL field, a click on a link opens the same card beside a caret that stays the document's, and editing the URL commits as one undoable step.
|
|
437
|
+
- **Copy yields the source bytes** (`**bold**`, not `bold`), because the caret's offsets are the source's. Reading mode is the one mode that copies the rendered text, since it has no caret and nothing to paste back into.
|
|
438
|
+
- **Search matches the source bytes too**, so a query spanning a construct boundary misses what the screen appears to show: `beta gamma` finds nothing in `**beta** gamma`, where the bytes between the words are `** `. Matches inside a construct's own text work normally.
|
|
439
|
+
|
|
440
|
+
Bytes only change where a rule above says so; a gesture that strands nothing writes exactly what source mode writes. One exception: `Backspace` at the very start of a `# ` with no heading text drops the construct, where source mode does nothing.
|
|
441
|
+
|
|
442
|
+
**The language chip.** Wherever a mode hides a fenced code block's fence, a small chip appears at the code box's top-right on hover or with the caret inside. It shows the block's language, and outside reading mode a click turns it into a field where Enter commits a new one as a single undoable edit. It's the only way to reach an info string (the text after the opening fence that names the language) in those modes; source mode shows the fence itself and gets no chip.
|
|
443
|
+
|
|
444
|
+
The effective mode is reflected as `data-presentation` on the editor root (absent in source mode, so default-mode DOM is unchanged) and announced on the `presentationModeChange` channel.
|
|
445
|
+
|
|
446
|
+
## Images and links
|
|
447
|
+
|
|
448
|
+
Four props deal with URLs: `resolveImageUrl` and `resolveLinkUrl` rewrite a raw URL at render time (resolve a relative path, map an app-internal scheme), `imageLoadPolicy` defers image loading, and `onLinkActivate` takes over what happens when a link is activated. Two of these need more than a table row: the paste-an-image hook, and which URLs the editor will render at all.
|
|
449
|
+
|
|
450
|
+
### Image paste
|
|
451
|
+
|
|
452
|
+
`onPasteImage` is the import hook for a paste carrying image files. The editor hands you each image, you store it however your app stores assets, and you return the Markdown that stands in for it: a wiki-style embed, a URL, whatever your `resolveImageUrl` understands. Return `null` to skip that image.
|
|
453
|
+
|
|
454
|
+
```svelte
|
|
455
|
+
<Editor
|
|
456
|
+
{source}
|
|
457
|
+
onPasteImage={async (image) => {
|
|
458
|
+
// image: { blob, mimeType, suggestedName? }
|
|
459
|
+
const id = await uploadToMyStore(image.blob, image.suggestedName ?? 'pasted.png');
|
|
460
|
+
return ``;
|
|
461
|
+
}}
|
|
462
|
+
/>
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
**Installing the hook takes the whole paste.** The clipboard's `text/plain` isn't pasted alongside, and with no hook installed an image-bearing paste behaves like it would in an editor with no image support at all.
|
|
466
|
+
|
|
467
|
+
- **Once per image, in clipboard order, one insertion.** The images are offered one after another and what they return is inserted as a single edit, so one paste is one undo entry and a single Ctrl+Z takes the whole thing back.
|
|
468
|
+
- **Failure is skip-and-continue.** A hook that rejects on one image surfaces on the `error` channel with `origin: 'clipboard'` (the origin for any contained failure on the paste route), and the remaining images still land. A hook that answers `null` for every image still consumes the paste; there's no `text/plain` waiting behind it.
|
|
469
|
+
- **The paste replaces the selection it lands on**, within a block and across blocks alike, like every other paste. The deletion runs only after your hook has answered, so a declined or failed import destroys nothing.
|
|
470
|
+
- **A block can disappear mid-import.** If the block the paste fired from is unmounted before a slow hook resolves, the insertion is declined on the `error` channel (same `clipboard` origin) rather than dropping Markdown somewhere the user never pointed.
|
|
471
|
+
|
|
472
|
+
For the curious, where the Markdown lands when the user moves the caret during a slow upload: a paste inside one block freezes its anchor at paste time, so a caret moved mid-upload doesn't drag the insertion with it. A paste over a selection spanning blocks follows the live selection instead, because that route resolves its endpoints by path at insertion time, so a selection extended during the import is the one that gets replaced. The difference is deliberate; snapshotting the second case would mean fighting the code that owns delete-and-insert as one operation.
|
|
473
|
+
|
|
474
|
+
### Which URLs render
|
|
475
|
+
|
|
476
|
+
The scheme check runs at render time, on whatever `resolveImageUrl` / `resolveLinkUrl` returned. A URL outside the admitted set renders inert: the image never loads and its widget is marked blocked, a link becomes an unlinked span, and the Markdown bytes are untouched either way. That blocked state isn't `imageLoadPolicy: 'placeholder'`, which defers loading an image the policy allows.
|
|
477
|
+
|
|
478
|
+
| Where | Admitted schemes |
|
|
479
|
+
| --------- | -------------------------------- |
|
|
480
|
+
| `img` src | `http`, `https`, `data`, `asset` |
|
|
481
|
+
| link href | `http`, `https`, `mailto`, `tel` |
|
|
482
|
+
|
|
483
|
+
A URL with no scheme at all (relative, fragment) is admitted at both. The two sets differ on purpose: `asset:` hands bytes to an `<img>`, and nothing has asked to navigate to one, so the same URL that renders as an image is refused as a link destination.
|
|
484
|
+
|
|
485
|
+
- **`asset:` is there for the desktop case.** Tauri's `convertFileSrc` yields `http://asset.localhost/…` on Windows and `asset://localhost/…` on macOS and Linux, so a shell whose local images all render on a Windows machine can have every one of them blocked on the other two. Both forms pass; test on each platform regardless.
|
|
486
|
+
- **A custom host protocol isn't admitted.** A scheme your shell registers for itself is one the allowlist doesn't know, and images carrying it render blocked. Map it to an admitted scheme inside `resolveImageUrl` (the check runs on what your resolver returns), or serve those bytes over the shell's own `http(s)` origin.
|
|
487
|
+
- **The allowlist isn't consumer-extensible.** Widening it for arbitrary host protocols is a decision deferred to the API freeze rather than answered by an ad-hoc prop; `resolveImageUrl` covers the case until then.
|
|
488
|
+
|
|
489
|
+
## Plugins
|
|
490
|
+
|
|
491
|
+
Plugins teach the editor new block and inline kinds. Writing one is the [plugin guide](plugin-guide.md)'s subject (with every method in the [API reference](plugin-api.md)); installing one is a prop:
|
|
492
|
+
|
|
493
|
+
```svelte
|
|
494
|
+
<script>
|
|
495
|
+
import { Editor } from '@voithos-labs/aragonite';
|
|
496
|
+
import { plugins } from './plugins'; // one array, declared once, shared by every editor
|
|
497
|
+
</script>
|
|
498
|
+
|
|
499
|
+
<Editor {source} {plugins} />
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
Plugins install once at mount, in array order, before the first parse. Build the array once, in a shared module, and pass that same array to every `<Editor>` in your app (why is under "one plugin set per app" below). An inline array in the markup re-creates the plugins on every render, which is harmless (you get a dev-build warning) but noise you don't need.
|
|
503
|
+
|
|
504
|
+
**Installation is process-global.** The grammar (the kinds, their components, their parsing rules, their commands) is one shared set per JavaScript context, the way `customElements` is, so registering the same kind twice is a conflict rather than a per-instance override. Runtime state is per instance: selection, undo history, and every cache are one editor's own, and nothing one instance does reaches another. Mounting several editors on one page is fine; they share one grammar and never any state. Three consequences:
|
|
505
|
+
|
|
506
|
+
- **Passing the same plugin to two editors registers it once.** Per-instance configuration still works: an entry may be `{ plugin, options }` instead of a bare plugin, and each editor gets its own `options` even though the registration is shared (the split-pane case). Reach for this over the plugin's own factory argument for anything two editors would vary, because a factory argument only takes effect on the first install.
|
|
507
|
+
- **The prop is the enablement set.** Registration is shared; activation is per editor. An editor runs the hooks, resolves the kinds, answers the commands and their chords, and applies the paste transforms of exactly the plugins it lists, so leaving one out of an editor's array switches it off for that editor. Its blocks still parse (the seed parse reads the whole grammar) and then render as plain editable source, which is the same fallback an unknown kind gets. Two things aren't scoped yet: a plugin's inline syntax and directive names still reach every editor, and the chords a plugin's own block types define are still listed by `reservedChords()` in an editor that left the plugin out. That is an over-report, not a swallowed key, since those blocks never render there. An editor mounted with no `plugins` prop is the exception: it activates everything installed in the process.
|
|
508
|
+
- **A later editor may mount carrying a plugin an earlier one never had.** The late install is legal and serves the new editor's own parse; an editor that already parsed doesn't re-parse against the newer grammar, and a dev-build warning names the late registration.
|
|
509
|
+
- **For a `parse()` pipeline with no `<Editor>` mounted**, call `installPlugins(plugins)` from the package to make the grammar live.
|
|
510
|
+
|
|
511
|
+
**One plugin set per app, not per route.** Installation is first-wins: the first set to install decides the grammar for the whole process, and a later route's different set is ignored with a dev-build warning. Under SSR, first-wins turns per-route sets into a hydration hazard:
|
|
512
|
+
|
|
513
|
+
1. The server process outlives a request, so whichever route it happened to render first decided the server's grammar.
|
|
514
|
+
2. Each browser load starts fresh, so the client decides its grammar from the route it actually loaded.
|
|
515
|
+
3. When the two disagree, the server-rendered block and the hydrating one resolve to different kinds, and the block fails at its error boundary.
|
|
516
|
+
|
|
517
|
+
One shared set removes the disagreement by construction, which is a lot cheaper than debugging a hydration mismatch that only shows up on one route.
|
|
518
|
+
|
|
519
|
+
The same rule covers [directive](directives.md) names. A plugin that claims an already-claimed `:::name` wins or loses by which setup ran first in the process, which under SSR is route order again, so a pre-claim behaves predictably only when one set installs everywhere. And per-route variation belongs in per-instance options (`{ plugin, options }` in the array), not in per-route plugin sets: an option baked into a plugin factory at definition time is process-global, so the first route to load would fix it for every other one.
|
|
520
|
+
|
|
521
|
+
### Bundled plugins
|
|
522
|
+
|
|
523
|
+
Nine first-party plugins ship in the package as subpath exports. Install them like any other plugin:
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
import { admonitionsPlugin } from '@voithos-labs/aragonite/plugins/admonitions';
|
|
527
|
+
import { detailsPlugin } from '@voithos-labs/aragonite/plugins/details';
|
|
528
|
+
import { tocPlugin } from '@voithos-labs/aragonite/plugins/toc';
|
|
529
|
+
import { footnotesPlugin } from '@voithos-labs/aragonite/plugins/footnotes';
|
|
530
|
+
import { emojiPlugin } from '@voithos-labs/aragonite/plugins/emoji';
|
|
531
|
+
import { highlightOccurrencesPlugin } from '@voithos-labs/aragonite/plugins/highlight-occurrences';
|
|
532
|
+
import { latexPlugin } from '@voithos-labs/aragonite/plugins/latex';
|
|
533
|
+
import { mermaidPlugin } from '@voithos-labs/aragonite/plugins/mermaid';
|
|
534
|
+
import { parrotPlugin } from '@voithos-labs/aragonite/plugins/parrot';
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
| Plugin | What it teaches the editor |
|
|
538
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
539
|
+
| `admonitionsPlugin()` | `:::name` directive callouts and native GitHub alerts (`> [!NOTE]` blockquotes) render as styled boxes, GitHub bytes untouched |
|
|
540
|
+
| `detailsPlugin()` | A canonical `<details>` HTML block (`<details>` or `<details open>`, a `<summary>` line, a Markdown body, `</details>`) becomes an editable collapsible section whose summary is a real editable child; a non-canonical `<details …>` stays a plain HTML block |
|
|
541
|
+
| `tocPlugin()` | A `[[toc]]` line becomes a live table of contents: every heading in the document, indented by level, each entry navigating to its heading on click or on Enter from the keyboard |
|
|
542
|
+
| `footnotesPlugin()` | GFM footnotes: `[^label]: content` definitions render as an editable block, and `[^label]` references render as superscript numbers in first-reference order |
|
|
543
|
+
| `emojiPlugin()` | GitHub `:shortcode:` emoji: a bare `:name:` renders as a glyph while the literal `:name:` bytes stay in the source; without the plugin, `:name:` is ordinary prose |
|
|
544
|
+
| `highlightOccurrencesPlugin()` | Every other occurrence of the word under the caret is highlighted across the document's prose blocks once you stop typing, as a view-only decoration, never a byte change |
|
|
545
|
+
| `latexPlugin({ renderer })` | All three GitHub math forms through one injected engine: inline `$…$`, block `$$…$$`, and the fenced ` ```math ` form; uninstalled, each stays its plain reading (prose, or a plain `math` code block) |
|
|
546
|
+
| `mermaidPlugin({ renderer? })` | A ` ```mermaid ` fence renders as a diagram through an injected engine; without one, the fence renders statically (the source, styled) |
|
|
547
|
+
| `parrotPlugin()` | A `%%parrot` line renders as an animated ASCII party parrot, with whatever follows the marker as its caption; uninstalled, the line is ordinary prose |
|
|
548
|
+
|
|
549
|
+
A few of them take options or need a word more.
|
|
550
|
+
|
|
551
|
+
**Admonitions.** Pass `{ convertAlertsOnPaste: true }` to rewrite pasted GitHub alerts to directive source instead of rendering them natively.
|
|
552
|
+
|
|
553
|
+
**Table of contents.** The walk descends into containers, so headings inside blockquotes, lists, and callouts are listed too, and navigation is view-only, so entries work in every presentation mode. `{ maxDepth }` (1 through 6, default 6) lists only the top levels, and it's read per instance:
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
import { tocPlugin, type TocOptions } from '@voithos-labs/aragonite/plugins/toc';
|
|
557
|
+
|
|
558
|
+
const plugins = [{ plugin: tocPlugin(), options: { maxDepth: 3 } satisfies TocOptions }];
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Two editors in one process can list different depths this way; the factory form, `tocPlugin({ maxDepth: 3 })`, is the default for an instance that declares none. The `satisfies TocOptions` is there because `options` is `unknown` to the editor, and with it a typo or an out-of-range level stays a compile error. At runtime anything that isn't a level from 1 to 6 falls back to the factory value.
|
|
562
|
+
|
|
563
|
+
**Footnotes.** A reference jumps to its definition, on plain click in reading mode and on Ctrl/Cmd+click elsewhere (the same gesture links take); a plain click in an editing mode still opens the reference's source to edit. The definition's own `[^label]` marker is the way back, on the same gesture, and it lands the caret right after the first citation. Backspace at the start of a note's body unwraps it: the first block lifts out and the marker stays on whatever's left. One clipboard consequence: copying part of a single-paragraph definition's body carries its `[^label]: ` marker along (the marker is that block's own source, and a slice without it would re-parse as a bare paragraph), so pasting that slice elsewhere lands a second definition under the same label.
|
|
564
|
+
|
|
565
|
+
**Math and diagrams.** latex and mermaid render through injected engines that never ride the main bundle: each has a `/renderer` subpath adapter, and its engine (`katex` / `mermaid`) is an optional peer dependency you install only if you use it.
|
|
566
|
+
|
|
567
|
+
```ts
|
|
568
|
+
import { katexRenderer } from '@voithos-labs/aragonite/plugins/latex/renderer'; // imports katex + its CSS
|
|
569
|
+
import { mermaidRenderer } from '@voithos-labs/aragonite/plugins/mermaid/renderer'; // dynamic-imports mermaid
|
|
570
|
+
|
|
571
|
+
latexPlugin({ renderer: katexRenderer });
|
|
572
|
+
mermaidPlugin({ renderer: mermaidRenderer });
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
The two differ on whether the renderer is required, on purpose. Math without a renderer has no honest fallback (a formula would render as nothing), so `latexPlugin` requires one at the type level. A mermaid block without an engine still has a useful static form (the fenced source, styled), so `mermaidPlugin()` is legal and renders statically; supply the renderer when you want live diagrams. The latex adapter imports `katex/dist/katex.min.css` on your behalf (it's the one bundled-plugin module with a side effect); no other setup is needed.
|
|
576
|
+
|
|
577
|
+
## Theming
|
|
578
|
+
|
|
579
|
+
The module owns its CSS. Two stylesheets ship under `styles/`:
|
|
580
|
+
|
|
581
|
+
- **`editor.css`**: the structural rules. The component imports it itself; nothing to do.
|
|
582
|
+
- **`editor-theme.css`**: the default token palette, light and dark. Import it for the default look, or replace it wholesale to retheme. It's the authoritative manifest, so read it for the exact token set and values rather than trusting a copy in a doc. This one included.
|
|
583
|
+
|
|
584
|
+
A plugin's render engine may carry its own stylesheet (KaTeX's `katex.min.css`, say). That CSS is the plugin's to load, not the editor module's.
|
|
585
|
+
|
|
586
|
+
### Scope
|
|
587
|
+
|
|
588
|
+
Nothing is declared on `:root`; the module never puts custom properties into your global scope. The tokens come in two tiers, and the tier decides where you override:
|
|
589
|
+
|
|
590
|
+
- **Host-chrome tokens** are your vocabulary: the editor only reads them, and their defaults live behind the opt-in `aragonite-editor-theme` class alone. A host with a theme system of its own declares the same names anywhere in its cascade (`:root` included), skips the class, and the editor blends in with no bridge stylesheet. Standalone, add the class to a wrapper for the built-in palette; non-editor UI inside the wrapper (a surrounding toolbar, say) inherits it too.
|
|
591
|
+
- **Editor-owned tokens** (the syntax and code palettes, the overlays, and the surfaces in the second table under [Theme tokens](#theme-tokens)) keep their defaults on `.editor` itself, so they render correctly with or without the class.
|
|
592
|
+
|
|
593
|
+
### Light and dark
|
|
594
|
+
|
|
595
|
+
Mode keys on `data-editor-theme` on the scoped element. Set the `theme` prop on `<Editor>` (`'dark'` default, `'light'`, or any custom name); on an `aragonite-editor-theme` wrapper, set the attribute directly. Dark is the base, and `'light'` overrides only the tokens that differ. In a themed host the attribute governs the editor-owned tier alone; the host-chrome tier's mode is whatever your own theme applied.
|
|
596
|
+
|
|
597
|
+
The prop is live: changing it rethemes through the cascade, and plugin content whose colors an engine paints rather than CSS (a Mermaid diagram's SVG) is redrawn for the new theme. A theme change writes no document bytes.
|
|
598
|
+
|
|
599
|
+
### Overriding and custom themes
|
|
600
|
+
|
|
601
|
+
Three paths, by how much you want to change:
|
|
602
|
+
|
|
603
|
+
1. **Override individual tokens.** Host-chrome tokens: declare them anywhere in your cascade (`:root { --color-accent: #f90; }` reaches the editor, as long as no `aragonite-editor-theme` ancestor sits between and shadows it). Editor-owned tokens: declare them on `.editor` (or a narrower selector of yours) in a stylesheet loaded after `editor-theme.css`, so `.editor { --syntax-heading: #f90; }` wins. Per mode: `.editor[data-editor-theme='light'] { … }`.
|
|
604
|
+
2. **Add a named theme.** Define `.editor[data-editor-theme='solarized'] { … }` and pass `theme="solarized"`. The base block supplies fallbacks for any token the custom theme omits, so a partial theme overrides only what it names.
|
|
605
|
+
3. **Replace wholesale.** Skip `editor-theme.css` and ship your own token file scoped to `.editor`.
|
|
606
|
+
|
|
607
|
+
### Theme tokens
|
|
608
|
+
|
|
609
|
+
The role table below is the stable **host-chrome contract**: the tokens the editor and its plugins read to blend into your app, named the way a host theme system names them. Declare them anywhere in your cascade, or take the defaults through the opt-in class.
|
|
610
|
+
|
|
611
|
+
| Role | Token(s) |
|
|
612
|
+
| ------------- | -------------------------------------------------------------------------- |
|
|
613
|
+
| **Font** | `--font-editor`, `--editor-font-size` _(mode-independent; one value each)_ |
|
|
614
|
+
| **Radius** | `--radius-ui` _(controls)_, `--radius-surface` _(overlays, popovers)_ |
|
|
615
|
+
| **Surface** | `--color-surface` |
|
|
616
|
+
| **Text** | `--color-text-secondary` _(body)_, `--color-text-primary` |
|
|
617
|
+
| **Muted** | `--color-ui-muted`, `--color-ui-dulled` |
|
|
618
|
+
| **Accent** | `--color-accent` |
|
|
619
|
+
| **Selection** | `--color-selection` |
|
|
620
|
+
| **Borders** | `--color-border` |
|
|
621
|
+
| **Error** | `--color-error` |
|
|
622
|
+
|
|
623
|
+
The editor supplies these host-family surfaces itself, in both modes, because a host vocabulary rarely names them. Override them at `.editor`; a `:root` declaration would lose to the default:
|
|
624
|
+
|
|
625
|
+
| Role | Token(s) |
|
|
626
|
+
| --------------- | ----------------------------------------------------------------- |
|
|
627
|
+
| **Backgrounds** | `--color-bg-secondary`, `--color-bg-elevated`, `--color-bg-muted` |
|
|
628
|
+
| **Muted** | `--color-text-muted`, `--color-ui-faint` _(the hover veil)_ |
|
|
629
|
+
|
|
630
|
+
**Every `--color-*` token has a light and a dark default** (the base block is dark, `data-editor-theme='light'` overrides it), so a read resolves in either mode. The font and radius tokens are the exceptions: mode-independent, declared once.
|
|
631
|
+
|
|
632
|
+
**`--color-selection` is a base three tints derive from.** The selection overlay, the search-match tint, and the block-reorder highlight are translucent tints of it at fixed alphas, so naming the one base moves all three and keeps their relative weights. Declaring an individual tint at `.editor` still wins over the derivation, if you want one of them somewhere else.
|
|
633
|
+
|
|
634
|
+
**The radii are partial by design.** The two tokens cover the corners a host theme has an opinion about: its controls and its elevated surfaces. Editor chrome whose corner is neither (a hairline focus ring, a scrollbar thumb, an inline-code pill) keeps a literal value, so declaring the tokens rounds what you'd expect a theme to round and leaves the rest alone.
|
|
635
|
+
|
|
636
|
+
**`--editor-font-size` is the type-scale root.** Headings, code, markers, and chrome are all `em`-relative, so overriding this one token scales the whole surface. In a themed host (no opt-in class) set it on any ancestor and it inherits straight in. Under `aragonite-editor-theme` the class declares `1rem`, which shadows any value from above it, so set it at `.editor` or below the class, or bridge it through a property of your own:
|
|
637
|
+
|
|
638
|
+
```css
|
|
639
|
+
.editor {
|
|
640
|
+
--editor-font-size: var(--my-zoom, 1rem);
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
A live change is supported, and virtual rendering re-estimates the document at the new scale, so a zoom control is a first-class use of the token.
|
|
645
|
+
|
|
646
|
+
Outside this contract sits the editor's own visual language: the syntax and code-token palettes, the marker colors, the selection, search, and reorder tints (derived from `--color-selection`, above), and the surfaces windowing paints where blocks aren't mounted yet. Those are dark-based or mode-independent; read `editor-theme.css` if you mean to retheme them.
|
|
647
|
+
|
|
648
|
+
**Plugin fallbacks.** A plugin reading a token keeps an inline fallback (`var(--color-text-muted, #aaaaaa)`) so it renders with no host, and every fallback matches the token's dark base value in `editor-theme.css`, never the light one. Which scopes a fallback fires in follows the tier: an editor-owned token defaults on `.editor`, so its fallback only fires outside the editor, while a host-chrome token defaults behind the opt-in class alone, so in a host that skips the class the fallback fires inside `.editor` too.
|
|
649
|
+
|
|
650
|
+
## Keyboard shortcuts
|
|
651
|
+
|
|
652
|
+
Two terms before the table. A **chord** is one key plus its modifiers, written as one string: `Mod+Shift+X` names one press. Chord strings put the modifiers in a fixed order (`Mod`, `Alt`, `Shift`) before the key's own value, single letters uppercased. **`Mod`** is the platform modifier: Ctrl on Windows and Linux, Cmd on macOS.
|
|
653
|
+
|
|
654
|
+
Shifted symbols aren't modeled: `Shift+1` reaches the editor as whatever symbol the keyboard layout produces, so bind digits and letters (`Mod+7`), never the shifted symbol.
|
|
655
|
+
|
|
656
|
+
This table is for a reader. An app deriving an accelerator map should read `editor.reservedChords()` instead, since that set is composed from the live keymaps and covers chords claimed outside them (see [Which shortcuts the editor consumes](#which-shortcuts-the-editor-consumes)). The selection chords are one example: Shift+Arrow, `Mod+Shift+Home` / `Mod+Shift+End`, and the repeated `Mod+A` escalation go through the cross-block selection code rather than the keymap, so they aren't rebindable and aren't listed here.
|
|
657
|
+
|
|
658
|
+
Tables also have pointer affordances the table has no row for: with `blockDragHandles` on, every row and column carries a grip, revealed on hover and shown outright on touch, that you can drag to reorder it or click for a row/column action menu. Right-clicking any cell opens that same menu (with cut/copy/paste) whether the grips are on or off, and Shift+F10 or the Context Menu key opens it from the keyboard.
|
|
659
|
+
|
|
660
|
+
| Action | Chord |
|
|
661
|
+
| ----------------------------------- | --------------------------------------------------- |
|
|
662
|
+
| **Editing** | |
|
|
663
|
+
| Bold (toggle strong) | `Mod+B` |
|
|
664
|
+
| Italic (toggle emphasis) | `Mod+I` |
|
|
665
|
+
| Strikethrough | `Mod+Shift+X` |
|
|
666
|
+
| Inline code | `Mod+E` |
|
|
667
|
+
| Edit a link's URL (live mode) | `Mod+K` (caret inside a link; opens the link card) |
|
|
668
|
+
| Cycle heading level | `Mod+0`–`Mod+6` (0 clears, 1–6 set `#`–`######`) |
|
|
669
|
+
| Split a block | `Enter` (in a code block, inserts a newline) |
|
|
670
|
+
| Hard line break | `Shift+Enter` |
|
|
671
|
+
| Merge into the block before / after | `Backspace` / `Delete` (at the block's start / end) |
|
|
672
|
+
| Indent / outdent a list item | `Tab` / `Shift+Tab` |
|
|
673
|
+
| Indent / dedent a code line | `Tab` / `Shift+Tab` |
|
|
674
|
+
| Insert a tab in prose | `Tab` |
|
|
675
|
+
| Undo | `Mod+Z` |
|
|
676
|
+
| Redo | `Mod+Y` or `Mod+Shift+Z` |
|
|
677
|
+
| **Block reorder** | |
|
|
678
|
+
| Move block up / down | `Alt+↑` / `Alt+↓` |
|
|
679
|
+
| **Find / replace** | |
|
|
680
|
+
| Open find | `Mod+F` |
|
|
681
|
+
| Open find + replace | `Mod+H` |
|
|
682
|
+
| Next / previous match | `Enter` / `Shift+Enter` (in the find field) |
|
|
683
|
+
| Close search | `Esc` |
|
|
684
|
+
| **Tables** | |
|
|
685
|
+
| Move between cells | `Tab` / `Shift+Tab`, arrow keys |
|
|
686
|
+
| Next row (or add one) | `Enter` (from the last cell, appends a row) |
|
|
687
|
+
| Insert row below / above | `Mod+Enter` / `Mod+Shift+Enter` |
|
|
688
|
+
| Insert column right / left | `Alt+Shift+→` / `Alt+Shift+←` |
|
|
689
|
+
| Delete row | `Mod+Shift+Backspace` |
|
|
690
|
+
| Delete column | `Alt+Shift+Backspace` |
|
|
691
|
+
| Move row up / down | `Alt+↑` / `Alt+↓` |
|
|
692
|
+
| Move column left / right | `Alt+←` / `Alt+→` |
|
|
693
|
+
| Move the whole table up / down | `Mod+Alt+↑` / `Mod+Alt+↓` |
|
|
694
|
+
| Cycle column alignment | `Mod+Shift+A` |
|
|
695
|
+
| Create a table | type a header row (`\| a \| b \|`), then `Enter` |
|
|
696
|
+
| **Clipboard** | |
|
|
697
|
+
| Copy / cut a focused block | `Mod+C` / `Mod+X` |
|
|
698
|
+
| Copy / cut a selected image | `Mod+C` / `Mod+X` |
|
|
699
|
+
|
|
700
|
+
**Typing a table into existence.** A table's header and delimiter lines have to be adjacent, which Enter alone could never produce, so a paragraph holding just a header row (`| a | b |`) is completed by `Enter` into a finished table (delimiter, one empty body row, caret in the first body cell) as one undoable step. It needs the leading pipe, so a paragraph that merely contains one (`ls | grep foo`) is left alone, and one undo restores the row you typed.
|
|
701
|
+
|
|
702
|
+
**A merge that wouldn't read back as one block is refused.** `Backspace` / `Delete` at a boundary joins the two blocks only where the joined bytes re-parse as a single block; otherwise the press moves the caret across the boundary and the document is untouched.
|
|
703
|
+
|
|
704
|
+
**The Editing rows assume a caret in ordinary block content.** Inside a table cell, `Enter`, `Tab`, and `Shift+Tab` mean what the Tables rows say instead: the `tableCell` keymap binds them to the cell's own commands, which shadow the prose bindings while the caret is in a cell. `Alt+↑` / `Alt+↓` likewise move the caret's row rather than the block; the whole table moves among its siblings on `Mod+Alt+↑` / `Mod+Alt+↓`.
|
|
705
|
+
|
|
706
|
+
**Whole-block clipboard.** A block focused as a whole (a thematic break, a plugin diagram) has no text selection, so `Mod+C` / `Mod+X` copy or cut the block's own Markdown (cut removes the block), and the same chords on a selected inline image act on the image's source. In reading mode copy works and cut degrades to copy.
|
|
707
|
+
|
|
708
|
+
**Menu clipboard caveats.** The right-click menu's Cut/Copy write the cell's rendered text, which differs from keyboard `Mod+X`'s raw-source slice for a cell holding an inline widget (a literal `<br>`, say). Menu Paste reads through `navigator.clipboard.readText()`, the one clipboard path not yet proven on the Tauri/wry webview. Keyboard `Mod+V` is unaffected.
|
|
709
|
+
|
|
710
|
+
### Rebinding chords
|
|
711
|
+
|
|
712
|
+
The `keybindings` prop rebinds (or disables, with `command: null`) chords that go through the keymap: the Editing, Block reorder, and Tables families above, and any chord a plugin kind adds.
|
|
713
|
+
|
|
714
|
+
```svelte
|
|
715
|
+
<Editor
|
|
716
|
+
{source}
|
|
717
|
+
keybindings={[
|
|
718
|
+
{ kind: 'listItem', chord: 'Tab', command: null },
|
|
719
|
+
{ kind: 'tableCell', chord: 'ArrowUp', command: 'table.deleteRow' }
|
|
720
|
+
]}
|
|
721
|
+
/>
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
An override's `kind` scope takes a plugin kind too; name it through the plugin's exported kind constant, which is a branded string, so a raw literal won't typecheck. A bind reaches every surface the editor owns, including the ones with no focused block for a kind scope to apply to: the caret between two blocks, a block focused as a whole (a thematic break), and the document with nothing focused inside it. A disable unbinds the command but the press is still consumed, as [Which shortcuts the editor consumes](#which-shortcuts-the-editor-consumes) explains.
|
|
725
|
+
|
|
726
|
+
Scoping by kind is what makes the shared structural chords reachable, since a chord like `Tab` is bound separately on every kind that wants it. The first entry above frees `Tab` inside list items (for focus traversal in a form-embedded editor, say) and leaves `Tab` alone in code blocks and prose.
|
|
727
|
+
|
|
728
|
+
**Scope table chords to `tableCell`, not `table`.** Inside a table the cell holds the caret, so the cell's kind is what resolves a chord: `{ kind: 'tableCell', chord: 'Mod+Enter', command: null }` frees the insert-row chord, while the same entry scoped to `table` resolves against a block that never gets a keystroke and silently does nothing.
|
|
729
|
+
|
|
730
|
+
Two cell gestures sit outside the keymap entirely, because both depend on where the caret sits inside the cell rather than on the chord: arrow navigation between cells, and the three-stage `Mod+A` (cell text, then the table, then the document). They aren't commands, so the two override directions are asymmetric:
|
|
731
|
+
|
|
732
|
+
- A **disable** can't reach them. `{ kind: 'tableCell', chord: 'Mod+A', command: null }` unbinds nothing (there was no binding) and the three-stage gesture keeps running.
|
|
733
|
+
- A **bind** shadows them completely. The second entry above, `ArrowUp` bound to `table.deleteRow`, resolves first and the cell never navigates. That's the intended precedence (an explicit binding wins), but it means claiming an arrow or `Mod+A` for your own command takes the built-in gesture with it.
|
|
734
|
+
|
|
735
|
+
Disabling `Tab` or `Enter` for `tableCell` likewise leaves the cell with no way to reach the next cell or append a row, so scope those deliberately.
|
|
736
|
+
|
|
737
|
+
The Find / replace family doesn't consult the override map at all: those chords are wired straight into the search components, and aren't rebindable today.
|
|
738
|
+
|
|
739
|
+
**Plugin-global chords resolve last.** A plugin's global command (see the [plugin guide](plugin-guide.md#block-commands)) may claim a chord, and it resolves after every `keybindings` override, built-in kind chord, and built-in global chord, so a plugin chord never shadows a built-in binding, and `Mod+F` / `Mod+H` are reserved outright. The shadow runs the other way by design: a built-in kind's own chord beats a plugin-global chord on that kind, not elsewhere. A plugin's `Mod+B` fires on a thematic break (which binds no `Mod+B`) but yields to bold-toggle inside a paragraph.
|
|
740
|
+
|
|
741
|
+
### Which shortcuts the editor consumes
|
|
742
|
+
|
|
743
|
+
An app that registers its own accelerators needs to know what the document already claims. Ask the editor rather than keeping a copy.
|
|
744
|
+
|
|
745
|
+
`reservedChords(): ReadonlySet<string>`
|
|
746
|
+
|
|
747
|
+
Every modifier chord this instance consumes, normalized.
|
|
748
|
+
|
|
749
|
+
`claimsChord(event: KeyboardEvent): boolean`
|
|
750
|
+
|
|
751
|
+
The same question for one keystroke, answered with the editor's own normalization, so a host key handler doesn't re-derive the platform rule: Ctrl and Cmd both fold to `Mod` (a macOS `Ctrl+B` and a `Cmd+B` give the same answer), and a CapsLock-uppercased letter matches its lowercase binding.
|
|
752
|
+
|
|
753
|
+
```ts
|
|
754
|
+
editor.reservedChords();
|
|
755
|
+
// Set { 'Mod+B', 'Mod+I', 'Mod+Shift+X', 'Mod+E', 'Mod+K', 'Mod+Z', 'Mod+Y', 'Mod+Shift+Z', 'Alt+ArrowUp', 'Mod+F', 'Mod+H', ... }
|
|
756
|
+
|
|
757
|
+
// editorEl: the element you mounted <Editor> into
|
|
758
|
+
window.addEventListener(
|
|
759
|
+
'keydown',
|
|
760
|
+
(e) => {
|
|
761
|
+
if (editorEl.contains(e.target as Node) && editor.claimsChord(e)) return; // the document's, leave it
|
|
762
|
+
runMyAccelerator(e);
|
|
763
|
+
},
|
|
764
|
+
{ capture: true }
|
|
765
|
+
);
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
The set is composed on each call, not baked at build time, so it already reflects the block kinds your plugins registered, the global chords claimed by the plugins this editor listed, and the `keybindings` overrides you passed: a chord you disabled globally drops out (a per-kind disable can't, since other kinds still claim it), one you bound appears, and turning `searchBar` off drops `Mod+F` and `Mod+H` with it.
|
|
769
|
+
|
|
770
|
+
**Modifier chords only, by design.** Bare keys (`Enter`, `Tab`, `Escape`, the arrows, `Backspace`) never appear: a focused document owns them whatever the set says, so an app shortcut bound to one is lost while the caret is in a block regardless. That makes the set the right input for an accelerator table and the wrong input for a "what can I press here" help sheet; for that, use the [shortcut table](#keyboard-shortcuts).
|
|
771
|
+
|
|
772
|
+
**Consumed means consumed, even where the chord does nothing.** A chord in the set is swallowed on every surface the editor owns, not only where it acts: `Mod+K` with the caret outside a link opens no card and still takes the press, because a chord the editor reported as claimed must never fall through to the browser's own default. Reading mode runs nothing at all, and splits the set in two: the history chords stay consumed there (a read-only document mustn't fall through to the browser's own undo), while a block-scoped keymap chord finds nothing to run and is left to the page.
|
|
773
|
+
|
|
774
|
+
**A disable releases the command, not the press.** `{ chord: 'Mod+Z', command: null }` drops `Mod+Z` from the set, which is what lets you bind it app-wide: your handler fires whenever focus is outside the editor. Inside the editor the press is still swallowed and runs nothing. The asymmetry is deliberate: the alternative is falling through to the browser's own editing defaults, and native undo inside a `contenteditable` rewrites the document behind the editor's back, past its undo stack and its `edit` events. If you want the chord to act inside the document too, bind it to a command rather than disabling it.
|
|
775
|
+
|
|
776
|
+
Two things the answer can't cover. It describes the chords the editor consumes, not the ones that reach it: a shell that resolves its accelerator first takes the chord before any handler runs, which is [the webview section](#embedding-in-a-webview-shell)'s opening measurement. And a chord the editor doesn't claim isn't thereby free: the browser's own editing chords still apply inside a `contenteditable`.
|
|
777
|
+
|
|
778
|
+
## Embedding in a host layout
|
|
779
|
+
|
|
780
|
+
Two props decide how the editor sits in your page: who owns the scroll, and what rides above the document.
|
|
781
|
+
|
|
782
|
+
### Host scroll mode
|
|
783
|
+
|
|
784
|
+
By default the editor root is the scrollport (the box that scrolls): it owns its scroll position, and virtual rendering keeps the mounted block count proportional to the viewport rather than the document, which is what lets it hold a large file at all. `scrollMode='host'` is the embedded alternative: the root stops scrolling and grows to its content, and an ancestor of yours scrolls it. A shell that stacks several documents in one scroller (a journal, a comment thread) wants this; a whole-file editor doesn't.
|
|
785
|
+
|
|
786
|
+
**Virtual rendering follows the scroll.** The editor windows against whatever actually scrolls it, so a large document inside a page-scrolled shell stays bounded to the viewport just like a standalone one. Windowing only turns on past a size budget (a few viewports' worth of estimated height), so a small embedded entry never windows in either mode and pays nothing.
|
|
787
|
+
|
|
788
|
+
**The one trade is scroll anchoring.** The browser's native anchoring and windowing's own correction can't both hold one scroll position (they'd double-correct), so exactly one runs. While an embedded editor is windowing it corrects by hand and withdraws its subtree from your scroller's anchor candidates; below the budget it corrects nothing and stays a candidate. Two consequences: your scroller is otherwise untouched, and late-sizing content in your own chrome above a windowing editor isn't compensated while the viewport holds only editor content. Size your chrome up front (or reserve its height) if that matters to you.
|
|
789
|
+
|
|
790
|
+
What your CSS has to provide:
|
|
791
|
+
|
|
792
|
+
- **Resolve the scroller before the editor's first use.** The editor finds the ancestor that scrolls it once, at first need. A shell that swaps its scroller in afterwards (a panel that expands, a wrapper replaced on a route transition) leaves the editor measuring against the wrong box. Settle the layout first, or remount the editor.
|
|
793
|
+
- **A clipping wrapper needs left padding.** Host mode drops the editor's own padding, and the drag handle sits in a gutter outside the block box. A wrapper with `overflow: hidden` and no padding clips the handle away entirely, so pointer drag-reorder silently disappears. Reserve at least `0.85rem` on the left. This only matters with `blockDragHandles` on; keyboard reorder (Alt+Arrow) works either way.
|
|
794
|
+
- **The reading column's side inset belongs to the editor, not an ancestor.** Host mode drops the editor's own padding, so the inset that narrows the text column is yours to add, and where you put it decides whether the margin beside the text is clickable. On the editor element or the block list inside it, the editor claims the whole gutter and a click there lands the caret on the nearest line. On any ancestor, that band is your shell's: the click never reaches a surface the editor can claim, and the margin beside every line goes dead while looking like part of the document. If the band genuinely is your chrome, answer the click yourself and hand the point to [`placeCaretAtPoint(x, y)`](#placing-the-caret-at-a-point).
|
|
795
|
+
- **A drag autoscrolls whatever actually scrolls.** That's the nearest ancestor you made scrollable, or the page's own viewport when nothing between the editor and the document scrolls. One box it will never scroll is a fixed-height `overflow: hidden` wrapper: a reader can't wheel one back, so a drag that scrolled it would strand content out of reach. A programmatic reveal does move such a box, deliberately: it can put the block on screen and leave it there.
|
|
796
|
+
|
|
797
|
+
The root reflects a `data-scroll-mode` attribute in host mode. Treat it as an implementation detail, not contract (it may start showing up in self mode too), and style host-mode embeddings through your own wrapper elements, which you control; the mode's own layout rules are already scoped to the editor.
|
|
798
|
+
|
|
799
|
+
### The header slot
|
|
800
|
+
|
|
801
|
+
`header` is a Svelte snippet rendered inside the scroll container, above the first block: a document title, a properties panel, a tag row, chrome that belongs to the document rather than the app frame. It scrolls away with the document instead of pinning above it, and that's what lets the editor keep its own scrollport, and with it virtual rendering; chrome mounted outside the editor would need an outer scroller and forfeit both.
|
|
802
|
+
|
|
803
|
+
```svelte
|
|
804
|
+
{#snippet documentHero()}
|
|
805
|
+
<h1>{title}</h1>
|
|
806
|
+
<TagRow {tags} />
|
|
807
|
+
{/snippet}
|
|
808
|
+
|
|
809
|
+
<Editor {source} header={documentHero} />
|
|
810
|
+
```
|
|
811
|
+
|
|
812
|
+
- **The content is yours.** Links inside the slot follow your page's behavior rather than the editor's plain-click-edits policy, and a text field in the slot keeps its own keystrokes: `Mod+F` in a host title field opens your find, not the editor's.
|
|
813
|
+
- **Height changes don't slide the document.** A slot that grows or shrinks while the reader is scrolled down is compensated, so the block they were reading stays where it was. At the top of the document, growth pushes content down, which is what a reader looking at the header expects. In host mode the compensation follows the same rule as everything else: the editor writes your scroller while it's windowing, and leaves the shift to the browser's own anchoring below the budget.
|
|
814
|
+
- **The find bar overlays the slot's top strip.** The bar rides the editor's top edge in both modes. In self mode that means it covers the header only at the very top of the scroll; in host mode, where the root never scrolls, it covers it whenever the bar is open.
|
|
815
|
+
- **A header taller than the viewport degrades.** At the top of the scroll it leaves the block list no room to intersect the viewport, so almost nothing mounts until the reader scrolls past it. Accepted rather than special-cased: a header that tall isn't what the slot is for.
|
|
816
|
+
|
|
817
|
+
### Where the find bar lives
|
|
818
|
+
|
|
819
|
+
By default the bar pins to the editor root's top edge. In self-scroll mode that reads as a document's own find bar, which it is. In host-scroll mode the root is a box partway down someone else's page, so the bar rides that box: it sits mid-page and scrolls out of sight with the document it searches, while the pane's own chrome, where a reader expects a find field, stays empty.
|
|
820
|
+
|
|
821
|
+
`searchBarAnchor` fixes that without giving up the bar. Hand it an element and the editor renders the same bar into it. Everything else stays the editor's: the component, `Mod+F` / `Mod+H`, Esc, the match navigation, and the caret restore that puts the cursor back where the search started. Only the DOM position moves.
|
|
822
|
+
|
|
823
|
+
```svelte
|
|
824
|
+
<div class="pane-chrome" bind:this={findBarSlot}></div>
|
|
825
|
+
<div class="pane-body">
|
|
826
|
+
<Editor {source} scrollMode="host" searchBarAnchor={findBarSlot} />
|
|
827
|
+
</div>
|
|
828
|
+
```
|
|
829
|
+
|
|
830
|
+
- **The prop reads live.** `null` or `undefined` puts the bar back in the editor root, so an anchor that mounts with a panel and unmounts with it is fine. It has no effect while `searchBar` is `false`; that switch turns the whole feature off, chords included.
|
|
831
|
+
- **Placement inside the anchor is yours.** The editor treats the element as the box and exports no positioning knobs. The bar positions itself absolutely, so give the anchor `position: relative` (or another positioned ancestor) and a size; otherwise the bar resolves against whatever the page's layout offers next.
|
|
832
|
+
- **The bar carries the editor's theme scope with it.** Custom properties resolve by DOM ancestry, so an anchor outside the editor resolves whatever the page's cascade offers there. When the editor itself sits under `aragonite-editor-theme`, the relocated node carries that class and the effective `data-editor-theme` (both tracking a `theme` change live), so the bar keeps the built-in palette. In a themed host with no class, it deliberately carries neither: the anchor inherits your own tokens, which is the palette the bar should wear there.
|
|
833
|
+
|
|
834
|
+
## Embedding in a webview shell
|
|
835
|
+
|
|
836
|
+
A desktop shell (Tauri/wry, WebView2, Electron) runs the editor on the same engine a browser does, so nothing about the component changes. What changes is the layer above it: the shell decides which keystrokes reach the page, and which URLs resolve to a local file. A browser-driven test run can't observe any of that, so verify each item below in the built application; a green CI run tells you nothing here. Layout isn't shell-specific; [Embedding in a host layout](#embedding-in-a-host-layout) covers it. Neither is local-file image rendering, which is a URL-policy question; [Which URLs render](#which-urls-render) covers the `asset:` protocol and its per-platform forms.
|
|
837
|
+
|
|
838
|
+
### Chords the shell may claim
|
|
839
|
+
|
|
840
|
+
Which chords reach the page, and whether the shell or the document gets first refusal, is shell-specific. Where the shell resolves its accelerator first, the chord is consumed before any `keydown` reaches the document: the editor can't bind it, observe it, or report that it went missing, and no `keybindings` override reaches a key that never arrives. Where the shell dispatches to the page first, the editor sees the chord and a capture-phase `preventDefault()` suppresses the shell's own action. Tauri/wry on WebView2 measures as the second kind, with reload and the devtools chords all arriving at the document and their defaults preventable. Assume neither; measure yours.
|
|
841
|
+
|
|
842
|
+
- **Verify the chord map in the real shell.** A browser run proves the keymap resolves, not that the chord arrives. Walk the [shortcut table](#keyboard-shortcuts) and your own app's bindings in the built application, on every platform you ship, and derive the editor's half of that walk from [`reservedChords()`](#which-shortcuts-the-editor-consumes) rather than copying the table, so it can't go stale between releases.
|
|
843
|
+
- **A webview's zoom hotkeys may well be off already.** A zoom control driving `--editor-font-size` (see [Theming](#theming)) reaches for exactly the chords a webview is most likely to reserve, which makes it this section's bellwether. The collision isn't a given, though: Tauri's zoom-hotkey option defaults to off, so on that shell `Mod+=` and `Mod+-` arrive at the page untouched and a host zoom control bound to them works. Measure before designing around a collision, and before assuming there's none.
|
|
844
|
+
- **A chord's fate can differ between your debug build and your shipped one.** Tauri enables the web inspector by default in debug builds and gates it behind a feature flag in release builds, so `F12` opens devtools while you develop and finds nothing to open in what you ship. Measure in the build you ship.
|
|
845
|
+
- **The host's switches are coarse; the page's is fine.** A shell exposes a switch over a whole built-in accelerator family rather than a per-chord list, and there can be more than one family with its own default (Tauri splits page zoom out from the rest and defaults it off), so "the shell's accelerators" is rarely one setting. Where the shell dispatches to the page first, a capture-phase `preventDefault()` is the per-chord route its configuration doesn't offer. Check your shell's current documentation for what each switch covers.
|
|
846
|
+
- **A capture-phase key listener of your own needs an "inside the editor" guard.** A host that handles keys on `window` or `document` before the page sees them has to decline the ones headed for the editor, and the test is containment in the element you mounted `<Editor>` into (its root carries the `.editor` class). A guard inherited from a previously embedded editor keys off a selector that now matches nothing, reads as "never inside the editor", and quietly swallows every editing chord.
|
|
847
|
+
|
|
848
|
+
### Clipboard in a webview
|
|
849
|
+
|
|
850
|
+
**Plain text is the whole model.** Every copy and cut writes `text/plain`, every paste reads it, and there's no HTML flavor to negotiate. What crosses is Markdown source.
|
|
851
|
+
|
|
852
|
+
- **A clipboard event may target `document.body` rather than the editor.** Where the selection's focus end hosts no caret (an image-only paragraph, a thematic break), Chromium dispatches `copy` / `cut` / `paste` at the body instead of the focused block. The editor handles that with a root-level handler, so cross-block copy works. What it means for you: an editor clipboard event doesn't reliably originate inside the editor's DOM, so a host listener that claims clipboard events by "the target is outside the editor" will claim the editor's.
|
|
853
|
+
- **Multi-line writes normalize to the OS line ending.** The whole-block copy chord (`Mod+C` / `Mod+X` on a block focused as a whole) writes through `navigator.clipboard.writeText`, and Chromium rewrites a multi-line payload to the platform's line ending, CRLF on Windows. Pasting back into the editor re-normalizes to LF, so documents are unaffected; a host that reads the system clipboard itself normalizes on its own side.
|
|
854
|
+
- **That async write is the path to prove in your shell.** wry has refused `writeText` in some contexts, which is why every other clipboard route writes synchronously through the event object. A refused write is contained rather than thrown: nothing reaches the clipboard, a dev build warns, and a cut degrades to leaving the block alone.
|
|
855
|
+
|
|
856
|
+
### Verify in the shell
|
|
857
|
+
|
|
858
|
+
Run these by hand in the built application, once per platform you ship. Yes, by hand:
|
|
859
|
+
|
|
860
|
+
1. Every chord the editor and your app rely on, including whatever the shell reserves for zoom, devtools, and reload.
|
|
861
|
+
2. Select-all across blocks containing an image or a thematic break, copy, then paste into an external application.
|
|
862
|
+
3. The two routes that reach the async `navigator.clipboard` API instead of a clipboard event: whole-block `Mod+C` / `Mod+X` on a thematic break or a plugin diagram, and the table right-click menu's Paste (see "Menu clipboard caveats" under [Keyboard shortcuts](#keyboard-shortcuts)).
|
|
863
|
+
4. Multi-line text copied from a native application and pasted into a block.
|
|
864
|
+
5. An image pasted from the system clipboard, if `onPasteImage` is installed (see [Image paste](#image-paste)).
|
|
865
|
+
6. A local-file image, on each platform, since the asset protocol takes a different form on Windows (see [Which URLs render](#which-urls-render)).
|
|
866
|
+
7. Selection restore across a document or tab swap, if you persist a caret (see [Restoring a selection](#restoring-a-selection)).
|
|
867
|
+
|
|
868
|
+
A glitch that only reproduces inside the shell is what the interaction trace is for: arm it, reproduce, and serialize a report that travels back out. That's the next section.
|
|
869
|
+
|
|
870
|
+
## Diagnostics
|
|
871
|
+
|
|
872
|
+
`getDiagnostics()` is how a bug report gets out of the field. The editor's hardest bugs live in the inline layer, no point pretending otherwise: every state there is transient (the styled spans rebuild on each keystroke), so caret moves, marker reveals and folds, widget churn, and IME composition are gone by the time anyone reads a report. The **interaction trace** is a ring buffer that records those transitions as they happen. It ships switched off, behind one cheap check per recorder, so arming it is your call, and it's process-global: two editors on one page interleave their entries in the one buffer.
|
|
873
|
+
|
|
874
|
+
The workflow when a user hits an inline glitch: reproduce, serialize, attach.
|
|
875
|
+
|
|
876
|
+
```ts
|
|
877
|
+
const diag = editor.getDiagnostics();
|
|
878
|
+
diag.enableTrace(); // once, behind a "report a bug" affordance, say
|
|
879
|
+
// ...the user reproduces the glitch...
|
|
880
|
+
const report = diag.serializeDiagnostics();
|
|
881
|
+
|
|
882
|
+
diag.isTraceEnabled(); // true
|
|
883
|
+
diag.traceSnapshot(); // [{ t: 48211.3, site: 'reveal', kind: 'open', detail: { tier: 'inline', construct: 'strong:4-12' } }, ...]
|
|
884
|
+
diag.disableTrace();
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
`report` is a Markdown string you drop straight into a bug ticket: a title line carrying the timestamp, then a fenced section each for the trace tail, the recent operations, and the selection.
|
|
888
|
+
|
|
889
|
+
````markdown
|
|
890
|
+
## Interaction trace
|
|
891
|
+
|
|
892
|
+
```
|
|
893
|
+
[812ms ago] reveal/open tier=inline construct=strong:4-12
|
|
894
|
+
[640ms ago] text-render/cursor-capture walk=3
|
|
895
|
+
[12ms ago] pending-cursor/consume offset=9 applied=true
|
|
896
|
+
```
|
|
897
|
+
|
|
898
|
+
## Operations log
|
|
899
|
+
|
|
900
|
+
```
|
|
901
|
+
[3310ms ago] op=split path=[2] at=14
|
|
902
|
+
[1204ms ago] op=input path=[2]
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
## Selection
|
|
906
|
+
|
|
907
|
+
```
|
|
908
|
+
anchor=[2]@14 focus=[2]@14
|
|
909
|
+
```
|
|
910
|
+
````
|
|
911
|
+
|
|
912
|
+
`traceSnapshot()` returns the raw entries if you'd rather format them yourself; `disableTrace()` / `isTraceEnabled()` round out the switch.
|
|
913
|
+
|
|
914
|
+
**The document is excluded by default.** `serializeDiagnostics()` never includes the source unless you pass `{ includeSource: true }`, because a field report mustn't leak a user's content. Opt in only when the bytes are part of the repro and the user has consented.
|
|
915
|
+
|
|
916
|
+
The surface grows by adding methods to `EditorDiagnostics`, never a second object.
|
|
917
|
+
|
|
918
|
+
## Building your own chrome
|
|
919
|
+
|
|
920
|
+
Everything in this section builds UI around the document (toolbars, popups, highlights, navigation) without touching its bytes.
|
|
921
|
+
|
|
922
|
+
### Decorations
|
|
923
|
+
|
|
924
|
+
`getDecorations()` lets you register a view-only annotation source directly, no plugin needed: highlights, badges, folds that live and die with your app's state. It's the same registry a plugin reaches through its editor context, with the same contract: a named source whose `provide(document)` is pure over a read-only `DocumentView`, re-run after every edit, `invalidate()` for your own state changes, `dispose()` to remove.
|
|
925
|
+
|
|
926
|
+
```ts
|
|
927
|
+
const handle = editor.getDecorations().addSource({
|
|
928
|
+
name: 'stale-links',
|
|
929
|
+
provide: (doc) =>
|
|
930
|
+
staleLinks.map((link) => ({
|
|
931
|
+
type: 'mark',
|
|
932
|
+
path: link.path,
|
|
933
|
+
start: link.start,
|
|
934
|
+
end: link.end,
|
|
935
|
+
class: 'stale'
|
|
936
|
+
}))
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
staleLinks = await recheck(); // your own state changed, the document didn't
|
|
940
|
+
handle.invalidate(); // re-run provide now
|
|
941
|
+
handle.dispose(); // gone
|
|
942
|
+
```
|
|
943
|
+
|
|
944
|
+
Authoring semantics, the four decoration types, and the memoization recipe are in the [plugin guide](plugin-guide.md#decorations); everything there applies verbatim to a consumer-registered source.
|
|
945
|
+
|
|
946
|
+
### Screen geometry
|
|
947
|
+
|
|
948
|
+
`getRects()` answers "where is that, on screen?" in viewport coordinates.
|
|
949
|
+
|
|
950
|
+
```ts
|
|
951
|
+
const rects = editor.getRects();
|
|
952
|
+
rects.blockRect([3]); // DOMRect { x: 96, y: 412, width: 640, height: 58, ... }, or null when unmounted
|
|
953
|
+
rects.rangeRects([3], 0, 12); // [DOMRect, ...], one per visual line
|
|
954
|
+
rects.caretRect(); // DOMRect, or null
|
|
955
|
+
await rects.reveal([840]); // true once the block's element exists
|
|
956
|
+
await rects.scrollTo([840], { block: 'center' }); // true once it's in view
|
|
957
|
+
await rects.navigateTo([840]); // true: in view, and the caret sits at its start
|
|
958
|
+
await rects.navigateTo([840], 12); // the same, with the caret after the block's twelfth byte
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
| Method | Returns |
|
|
962
|
+
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
963
|
+
| `blockRect(path)` | The block's bounding box, or `null` when it isn't mounted |
|
|
964
|
+
| `rangeRects(path, start, end)` | The rects covering an inline range: one per visual line on wrapped text, one per cell on a table |
|
|
965
|
+
| `caretRect()` | The live native caret, or `null` (including whenever a cross-block selection is active) |
|
|
966
|
+
| `reveal(path)` | Mounts a block virtual rendering has unmounted, resolving `true` once its element exists |
|
|
967
|
+
| `scrollTo(path, opts?)` | Mounts the block, then scrolls the viewport to it (`opts.block`: `'nearest'` default, or `'center'`; `opts.hold`: keep holding it, default true) |
|
|
968
|
+
| `navigateTo(path, offset?)` | The same, plus lands the caret in the block (at its start, or at the offset you pass), which is what a navigation affordance owes the user |
|
|
969
|
+
|
|
970
|
+
Offsets are raw offsets into the block (dimmed markers included) on text blocks, and cell indices on tables. `rangeRects` accepts the exported `SELECTION_END` as `end`, meaning "through the block's last measurable position".
|
|
971
|
+
|
|
972
|
+
### Driving search yourself
|
|
973
|
+
|
|
974
|
+
`getSearch()` returns the find/replace controller (`SearchState`), the same engine the built-in bar drives. Set the query and options (case sensitivity, whole word, regex), step through matches, replace one or all. The `searchBar` prop renders the built-in UI over that controller; set it `false` to drive search from your own chrome.
|
|
975
|
+
|
|
976
|
+
```ts
|
|
977
|
+
const search = editor.getSearch();
|
|
978
|
+
search.open();
|
|
979
|
+
search.setQuery('gamma');
|
|
980
|
+
search.setOptions({ caseSensitive: true });
|
|
981
|
+
search.matches; // [{ path: [2], start: 7, end: 12 }, { path: [5, 0], start: 0, end: 5 }]
|
|
982
|
+
search.activeIndex; // 0, and the editor has scrolled to it
|
|
983
|
+
search.next(); // activeIndex is now 1
|
|
984
|
+
search.setReplacement('delta');
|
|
985
|
+
await search.replaceCurrent();
|
|
986
|
+
await search.replaceAll();
|
|
987
|
+
search.close();
|
|
988
|
+
```
|
|
989
|
+
|
|
990
|
+
### Recipe: navigating to a block
|
|
991
|
+
|
|
992
|
+
`getRects().navigateTo(path)` is the navigation call: jump to a heading, an outline entry, a cross-reference target. `scrollTo(path, opts)` is the same reveal-and-scroll without landing the caret, for moving the viewport without moving the selection (the built-in search does exactly that). Four things to know:
|
|
993
|
+
|
|
994
|
+
- **It mounts first.** A block virtual rendering has unmounted has no element to scroll to, so the call mounts it and then scrolls. `reveal(path)` is that same mount without the scroll, for measuring something offscreen.
|
|
995
|
+
- **The boolean is honest.** It resolves only after the position settles, so `true` means the block is genuinely in view, not merely that the call ran. A target that can't mount (one inside a collapsed `<details>` or admonition, say) resolves `false` and leaves nothing pinned.
|
|
996
|
+
- **`'nearest'` holds, `'center'` places.** The default `'nearest'` keeps the target visible through the reflow a mount triggers (images decoding above it collapse the document height). `'center'` places the block precisely once the scroll settles, and stops holding it after. Pass `hold: false` to hand the viewport straight back, which is what a restore that writes its own remembered scroll position afterwards wants.
|
|
997
|
+
- **Land the caret if a user asked to go there.** A navigation affordance that only scrolls leaves focus on whatever the user clicked, where the editor's chords don't reach: an undo typed right after the jump does nothing. `navigateTo` places the caret at the target through the same restore machinery `setSelection` and undo use, which is why it's a distinct call rather than a flag.
|
|
998
|
+
|
|
999
|
+
Finding the path in the first place: `parse(getSource())` gives you the document tree (every node has a `kind` and containers have `children`), so collect the headings, recursing into containers so a heading inside a blockquote or list is reachable too:
|
|
1000
|
+
|
|
1001
|
+
```ts
|
|
1002
|
+
import { parse } from '@voithos-labs/aragonite';
|
|
1003
|
+
|
|
1004
|
+
function headingPaths(node, path = []) {
|
|
1005
|
+
if (node.kind === 'heading' || node.kind === 'setextHeading') return [path];
|
|
1006
|
+
return (node.children ?? []).flatMap((child, i) => headingPaths(child, [...path, i]));
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
const paths = headingPaths(parse(editor.getSource())); // [[0], [4], [7, 2], ...]
|
|
1010
|
+
await editor.getRects().navigateTo(paths[2]); // true
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
The bundled toc plugin does exactly that walk over its live document, and clicking one of its entries is a `navigateTo` call.
|
|
1014
|
+
|
|
1015
|
+
### Recipe: a selection toolbar
|
|
1016
|
+
|
|
1017
|
+
Float a formatting bar above the user's selection. Nine steps, and the anchoring ones have a snippet after the list:
|
|
1018
|
+
|
|
1019
|
+
1. **Subscribe to `selectionChange`.** A `null` payload or a collapsed selection (anchor equals focus) hides the bar.
|
|
1020
|
+
2. **Put the endpoints in document order first.** `normalizeSelection(snapshot)` answers `{ start, end }` (by path, then by offset when the paths match), so a backward drag anchors exactly like a forward one. Anchor to `start`; a hand-rolled comparison gets the container-and-its-child pair wrong, where the shorter path is the earlier one.
|
|
1021
|
+
3. **Cross-block selections** (start and end in different blocks): anchor to `rangeRects(start.path, start.offset, SELECTION_END)`, the start block's rects from the selection to its end. Rect `[0]` is the first visual line; place the bar above its top-left.
|
|
1022
|
+
4. **Single-block selections**: `getSelection()` reports the range's real endpoints, so anchor with `rangeRects(start.path, start.offset, end.offset)`, the same call with a real end offset in place of `SELECTION_END`. (Reading the native `window.getSelection()` range works too, since within one block the editor delegates selection to the browser.) A selection **inside a table** shares the table's path on both endpoints and carries cell indices in `offset`, which the `cellCoordinate` flag need not mark, so exclude it with `getBlockKindAt(start.path) === 'table'`, never by the flag alone.
|
|
1023
|
+
5. **Re-anchor on the next `selectionChange`, not on scroll.** Rects are viewport-space snapshots; a `position: fixed` bar drifts under scroll until the selection next changes. Wire a scroll listener only if your UX demands live tracking.
|
|
1024
|
+
6. **Fire the buttons through `runCommand`, not synthetic keystrokes.** `runCommand(TOOLBAR_COMMANDS.toggleStrong)` says what the button means; a synthesized `Ctrl+B` says which key the button impersonates, and a user's rebind then silently rewires it.
|
|
1025
|
+
7. **Grey the declining buttons out with `canRunCommand`, on the same `selectionChange`.** Ask it per button and disable the ones that answer `false`, so a selection spanning blocks shows the link button dimmed rather than dead while the format toggles stay live. Still read `runCommand`'s boolean, per [Toolbar commands](#toolbar-commands).
|
|
1026
|
+
8. **Paint the pressed states with `isCommandActive`, on that same `selectionChange`.** A selection already inside a bold run shows the bold button pressed (`aria-pressed` is the accessible spelling), and pressing it then unwraps: the pressed paint and the press read the same bytes, so they agree by construction. In live mode a selection sitting inside a link shows the link button pressed the same way, off the link the card would edit, and clicking it opens that link's card with the selection left alone; a selection that runs out of the link isn't inside it, so the button unpresses and the click falls back to creating a new link over the range.
|
|
1027
|
+
9. **Keep focus in the document**, for the same reason the insert toolbar does: cancel the button's mousedown default, or restore a `getSelection()` snapshot before calling.
|
|
1028
|
+
|
|
1029
|
+
```ts
|
|
1030
|
+
import { normalizeSelection, SELECTION_END } from '@voithos-labs/aragonite';
|
|
1031
|
+
|
|
1032
|
+
editor.getEvents().on('selectionChange', (sel) => {
|
|
1033
|
+
if (!sel) return hide();
|
|
1034
|
+
const { start, end } = normalizeSelection(sel);
|
|
1035
|
+
if (editor.getBlockKindAt(start.path) === 'table') return hide();
|
|
1036
|
+
const sameBlock = start.path.join('.') === end.path.join('.');
|
|
1037
|
+
if (sameBlock && start.offset === end.offset) return hide();
|
|
1038
|
+
const rects = editor.getRects().rangeRects(start.path, start.offset, sameBlock ? end.offset : SELECTION_END);
|
|
1039
|
+
if (rects[0]) showAbove(rects[0]); // DOMRect of the first visual line
|
|
1040
|
+
});
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
The repository's `SelectionToolbar` component, mounted by the showcase's live mode and the dev harness alike, is this recipe end to end: both anchoring branches, the table exclusion, the five `TOOLBAR_COMMANDS` buttons greyed by `canRunCommand` and pressed by `isCommandActive`, and the mousedown cancel that keeps the caret in the document.
|
|
1044
|
+
|
|
1045
|
+
### Recipe: an insert toolbar
|
|
1046
|
+
|
|
1047
|
+
`insertMarkdown(md)` and `getRects()` are a toolbar: one writes, the other positions. Since bytes are the API, every construct is a snippet, including one a plugin contributes, with no new call to learn.
|
|
1048
|
+
|
|
1049
|
+
```svelte
|
|
1050
|
+
<button
|
|
1051
|
+
onmousedown={(e) => e.preventDefault()}
|
|
1052
|
+
onclick={() => editor.insertMarkdown('| Column | Column |\n| --- | --- |\n| | |\n')}
|
|
1053
|
+
>
|
|
1054
|
+
Table
|
|
1055
|
+
</button>
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
1. **Don't let the button take focus.** The call inserts at the caret, and a button that focuses on press has already destroyed it, so the call returns `false`. Cancel the press default, as above, so focus never leaves the document, or stash a `getSelection()` snapshot and `setSelection` it back before inserting.
|
|
1059
|
+
2. **Hand it canonical bytes.** A table button inserts `'| Column | Column |\n| --- | --- |\n| | |\n'`; a fence button `'```lang\n\n```\n'`. There's no per-construct API, so a new kind needs no new call. (A table is also typeable: a lone header row completed with `Enter` creates the same thing, per [Keyboard shortcuts](#keyboard-shortcuts).)
|
|
1060
|
+
3. **Position with `getRects()`.** `caretRect()` anchors a bar to the insertion point, `blockRect(path)` to the block. Both are viewport-space snapshots; re-read on the next `selectionChange`.
|
|
1061
|
+
4. **Read the result on the `edit` channel**, not on the line after the call: the commit lands on the editor's own flush.
|
|
1062
|
+
|
|
1063
|
+
The repository's `InsertToolbar` component, the fixed strip the showcase mounts under its header in live mode, is this recipe's reference: canonical snippet buttons, the mousedown cancel, and a no-caret greying read off `selectionChange`, the same decline `insertMarkdown` would answer, surfaced before the click.
|
|
1064
|
+
|
|
1065
|
+
## Rewriting a document
|
|
1066
|
+
|
|
1067
|
+
You never assemble an edit by hand. Edits happen through the component, every applied edit shows up on the `edit` channel, and how an edit is applied inside isn't part of the consumer contract.
|
|
1068
|
+
|
|
1069
|
+
Paste sits on that boundary: pasted text is parsed as authored. A plugin may rewrite it before it's parsed, through a paste-scoped hook (`registerPasteTransform`, in the [plugin guide](plugin-guide.md)). Never the load path, never typing. And for inserting at the caret rather than rewriting, the call is [`insertMarkdown`](#inserting-markdown-at-the-caret): it's a paste, so it carries the transforms, the undo entry, and the caret landing a paste does.
|
|
1070
|
+
|
|
1071
|
+
For rewriting a whole document (converting legacy syntax, migrating content, applying a bulk fix), work at the document level: read `getSource()`, transform the Markdown, write the result back through the `source` prop.
|
|
1072
|
+
|
|
1073
|
+
```svelte
|
|
1074
|
+
<script>
|
|
1075
|
+
let source = $state(initial);
|
|
1076
|
+
let editor;
|
|
1077
|
+
|
|
1078
|
+
function normalizeBullets() {
|
|
1079
|
+
source = editor.getSource().replaceAll(/^\* /gm, '- ');
|
|
1080
|
+
}
|
|
1081
|
+
</script>
|
|
1082
|
+
|
|
1083
|
+
<Editor bind:this={editor} {source} />
|
|
1084
|
+
```
|
|
1085
|
+
|
|
1086
|
+
The replacement is one document swap, so undo history and the caret don't survive it. That's the honest shape for an import-or-convert affordance; pretending otherwise would only hide the swap.
|
|
1087
|
+
|
|
1088
|
+
A transformer working over `parse`'s output can lean on how the document is put back together: `serialize` is exactly `prefix + Σ(child.leadingTrivia + child.raw) + suffix` over the document's children, so a rewrite can replace individual blocks' bytes and reassemble without touching the rest.
|
|
1089
|
+
|
|
1090
|
+
````ts
|
|
1091
|
+
import { parse, serialize } from '@voithos-labs/aragonite';
|
|
1092
|
+
|
|
1093
|
+
const doc = parse(editor.getSource());
|
|
1094
|
+
for (const block of doc.children) {
|
|
1095
|
+
if (block.kind === 'fencedCode') block.raw = block.raw.replace(/^```js\b/, '```javascript');
|
|
1096
|
+
}
|
|
1097
|
+
source = serialize(doc); // every other block's bytes are exactly as they were
|
|
1098
|
+
````
|