@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,1849 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setContext, tick, onMount, untrack } from 'svelte';
|
|
3
|
+
import '../styles/editor.css';
|
|
4
|
+
import type { BlockComponent } from '../block-component';
|
|
5
|
+
import type { AnyBlockKind, Document } from '../core/nodes';
|
|
6
|
+
import type { EditorProps, EditorInstance, EditorDiagnostics } from '../editor-props';
|
|
7
|
+
import type { EditorEvents } from '../editor-events';
|
|
8
|
+
import {
|
|
9
|
+
BLOCK_EDIT_KEY,
|
|
10
|
+
CONTAINER_EDIT_KEY,
|
|
11
|
+
EDITOR_DOC_KEY,
|
|
12
|
+
EDITOR_POLICIES_KEY,
|
|
13
|
+
EDITOR_SERVICES_KEY,
|
|
14
|
+
FOCUS_KEY,
|
|
15
|
+
HISTORY_KEY,
|
|
16
|
+
type BlockElLookup,
|
|
17
|
+
type DocumentGetter,
|
|
18
|
+
type EditorDoc,
|
|
19
|
+
type LinkReferenceResolverRef,
|
|
20
|
+
type EditorPolicies,
|
|
21
|
+
type EditorServices,
|
|
22
|
+
type PluginEditorLookup,
|
|
23
|
+
type ResolveImageUrl,
|
|
24
|
+
type ResolveLinkUrl
|
|
25
|
+
} from '../editor-keys';
|
|
26
|
+
import { createStickyColumnState } from '../cursor/sticky-column';
|
|
27
|
+
import { createEdgeAffinityState } from '../cursor/edge-affinity';
|
|
28
|
+
import { createPendingMarksState } from '../cursor/pending-marks';
|
|
29
|
+
import { createRevealAnchorState } from '../cursor/reveal-anchor';
|
|
30
|
+
import { createHeightOracle } from '../cursor/height-oracle';
|
|
31
|
+
import { ESTIMATE_BASE_FONT_SIZE, HEIGHT_ESTIMATES } from '../cursor/typography-estimates';
|
|
32
|
+
import {
|
|
33
|
+
clippingAncestors,
|
|
34
|
+
userScrollportFor,
|
|
35
|
+
type UserScrollport
|
|
36
|
+
} from '../cursor/scroll-ancestors';
|
|
37
|
+
import { createScrollport, type Scrollport } from '../cursor/scrollport';
|
|
38
|
+
import { createDeadSpaceCaret } from '../selection/dead-space-caret';
|
|
39
|
+
import { resetForPointerDown } from '../selection/cross-block/pointer';
|
|
40
|
+
import { createContentVersion } from '../reactivity/content-version.svelte';
|
|
41
|
+
import { useContainerWindowing } from '../reactivity/use-container-windowing.svelte';
|
|
42
|
+
import { refSlotsOver, replaceRefs, revealChildOrWait } from '../reactivity/publish-ref.svelte';
|
|
43
|
+
import { createSelectionState } from '../selection/selection-state.svelte';
|
|
44
|
+
import { createSelectionDescription } from '../selection/selection-description';
|
|
45
|
+
import { EDITOR_LABEL, movedBlockToPosition } from '../a11y-strings';
|
|
46
|
+
import type { EditorSelection } from '../selection/primitives';
|
|
47
|
+
import { createWidgetSelectionState } from './image/widget-selection-state.svelte';
|
|
48
|
+
import { bootstrapCodeLanguages } from './blocks/code/code-bootstrap';
|
|
49
|
+
import { assignIds } from '../block-id';
|
|
50
|
+
import { ensureEditableContainers, emptyParagraph } from '../tree-operations';
|
|
51
|
+
import { blockNodeAt, isBlockNode, nodeAt } from '../tree-operations/node-ops';
|
|
52
|
+
import { serialize } from '../core/serializer';
|
|
53
|
+
import { parse } from '../core/parser';
|
|
54
|
+
import { defaultLinkActivation } from '../core/url-policy';
|
|
55
|
+
import { advanceSignatureEpoch, lrdMapCouldChange } from './lrd-map-gate';
|
|
56
|
+
import {
|
|
57
|
+
buildLinkReferenceMap,
|
|
58
|
+
type LinkReferenceResolver
|
|
59
|
+
} from '../core/inline/link-reference-resolver';
|
|
60
|
+
import { createUndoManager } from '../undo/manager';
|
|
61
|
+
import { createSharingState } from '../tree-operations/sharing';
|
|
62
|
+
import { createEditorEvents, emitBlockedLinkError, emitCommandError } from '../editor-events';
|
|
63
|
+
import { createEditorActions, type EditorActionsDeps } from '../editor-actions';
|
|
64
|
+
import { createReorderAction } from '../editor-actions/reorder-action';
|
|
65
|
+
import { createSearchReplace } from '../editor-actions/search-replace';
|
|
66
|
+
import { createSearchState, type SearchState } from '../search/search-state.svelte';
|
|
67
|
+
import { createDecorationEngine } from '../decorations/decoration-state.svelte';
|
|
68
|
+
import type { DecorationRegistry } from '../decorations/types';
|
|
69
|
+
import { createEditorRects, type EditorRects } from '../editor-rects';
|
|
70
|
+
import { installReorderDrag } from '../editor-actions/reorder-drag';
|
|
71
|
+
import { createPasteCoordinator } from '../editor-actions/paste-coordinator';
|
|
72
|
+
import { createOperationsLog } from '../debug/operations-log';
|
|
73
|
+
import { dumpInteractionTrace, dumpOperationsLog } from '../debug/inspect';
|
|
74
|
+
import { buildDiagnosticsReport } from '../debug/diagnostics-report';
|
|
75
|
+
import {
|
|
76
|
+
enableInteractionTrace,
|
|
77
|
+
disableInteractionTrace,
|
|
78
|
+
isInteractionTraceEnabled,
|
|
79
|
+
interactionTraceSnapshot
|
|
80
|
+
} from '../debug/interaction-trace';
|
|
81
|
+
import { readCurrentSelection } from '../selection/native-bridge';
|
|
82
|
+
import { restoreSelection, type SelectionRestoreOutcome } from '../selection/selection-restore';
|
|
83
|
+
import { findSurfacePathForElement, readBlockPath } from '../selection/path-lookup';
|
|
84
|
+
import { createCaretRestore } from '../selection/caret-restore';
|
|
85
|
+
import { createCrossBlockHandlers } from '../selection/cross-block/dispatch';
|
|
86
|
+
import { createCrossBlockCommands } from '../selection/cross-block/format-toggle';
|
|
87
|
+
import { isPreviewMode } from '../presentation-mode';
|
|
88
|
+
import { normalizeKeybindingOverrides } from '../schema/keybinding-overrides';
|
|
89
|
+
import { createEditorRootKeydown } from './editor-root-keydown';
|
|
90
|
+
import { createEditorRootClipboard } from './editor-root-clipboard';
|
|
91
|
+
import {
|
|
92
|
+
installEditorBlurAnnouncer,
|
|
93
|
+
installModActiveTracker,
|
|
94
|
+
installSelectionChangeBridge,
|
|
95
|
+
installViewportHeightWatcher,
|
|
96
|
+
onRoot,
|
|
97
|
+
removeAll
|
|
98
|
+
} from './editor-root-listeners';
|
|
99
|
+
import { collectReservedChords, chordIsClaimed } from '../schema/reserved-chords';
|
|
100
|
+
import { portalInto } from './portal';
|
|
101
|
+
import {
|
|
102
|
+
registerEditor,
|
|
103
|
+
unregisterEditor,
|
|
104
|
+
markEditorInteracted,
|
|
105
|
+
releaseInteractedEditor,
|
|
106
|
+
isTextEntrySurface
|
|
107
|
+
} from '../active-editor';
|
|
108
|
+
import { ambientLengthOf } from '../ambient/ambient-dom';
|
|
109
|
+
import { toClampedRawOffset } from '../cursor/coordinate-spaces';
|
|
110
|
+
import { domTextOffsetAtNode } from '../cursor/widget-offset';
|
|
111
|
+
import {
|
|
112
|
+
canRunCommandById,
|
|
113
|
+
isCommandActiveById,
|
|
114
|
+
runCommandById,
|
|
115
|
+
type CommandDispatchContext,
|
|
116
|
+
type CommandErrorSink,
|
|
117
|
+
type KindCommandTarget
|
|
118
|
+
} from '../schema/block-commands';
|
|
119
|
+
import type { AnyCommandId } from '../schema/command-id';
|
|
120
|
+
import { installPlugins, normalizePluginEntries } from '../schema/plugin-install';
|
|
121
|
+
import {
|
|
122
|
+
activationFor,
|
|
123
|
+
everyInstalledPlugin,
|
|
124
|
+
kindEnablementFor
|
|
125
|
+
} from '../schema/plugin-activation';
|
|
126
|
+
import { createEditorPluginContexts, mintEditorId } from '../schema/plugin-editor-context';
|
|
127
|
+
import { bothEnable, createRegistryView, type KindEnablement } from '../schema/registry-view';
|
|
128
|
+
import BlockList from './BlockList.svelte';
|
|
129
|
+
import SearchBar from './SearchBar.svelte';
|
|
130
|
+
import ImageOverlayHost from './image/ImageOverlayHost.svelte';
|
|
131
|
+
import LinkCardHost from './link-card/LinkCardHost.svelte';
|
|
132
|
+
import { createLinkCardState } from './link-card/link-card-state.svelte';
|
|
133
|
+
import { LINK_ELEMENT_SELECTOR, resolveLinkAtPoint } from './blocks/text/link-at-point';
|
|
134
|
+
import { runStartupInvariantChecks } from '../invariants/install';
|
|
135
|
+
import { assertInvariant } from '../assert';
|
|
136
|
+
import { checkMarkerCssParity } from '../invariants/marker-css-parity';
|
|
137
|
+
import { checkLandableCaret } from '../invariants/landable-caret';
|
|
138
|
+
import { registerBuiltInBlocks } from './built-in-blocks';
|
|
139
|
+
import { BLOCK_CONTENT_SELECTOR } from './block-content-selector';
|
|
140
|
+
|
|
141
|
+
registerBuiltInBlocks();
|
|
142
|
+
bootstrapCodeLanguages();
|
|
143
|
+
runStartupInvariantChecks();
|
|
144
|
+
|
|
145
|
+
// `__registryEnablement` is a harness-only door; the intersection keeps it off the
|
|
146
|
+
// public EditorProps type.
|
|
147
|
+
let {
|
|
148
|
+
source = '',
|
|
149
|
+
resolveImageUrl,
|
|
150
|
+
resolveLinkUrl,
|
|
151
|
+
imageLoadPolicy = 'auto',
|
|
152
|
+
onLinkActivate,
|
|
153
|
+
onPasteImage,
|
|
154
|
+
header,
|
|
155
|
+
blockDragHandles = false,
|
|
156
|
+
searchBar = true,
|
|
157
|
+
searchBarAnchor,
|
|
158
|
+
keybindings,
|
|
159
|
+
theme = 'dark',
|
|
160
|
+
presentationMode = 'source',
|
|
161
|
+
scrollMode = 'self',
|
|
162
|
+
plugins,
|
|
163
|
+
__registryEnablement
|
|
164
|
+
}: EditorProps & { __registryEnablement?: KindEnablement } = $props();
|
|
165
|
+
|
|
166
|
+
// Snapshotted, not read live: set-once by contract, and a live read inside the
|
|
167
|
+
// windowing scopes' derived would make `scrollMode` a dependency of the hottest path.
|
|
168
|
+
// svelte-ignore state_referenced_locally
|
|
169
|
+
const hostScroll = scrollMode === 'host';
|
|
170
|
+
|
|
171
|
+
// Host mode asks two different questions one walk cannot serve (`cursor/
|
|
172
|
+
// scroll-ancestors` header): what a drag autoscrolls, and what bounds the visible
|
|
173
|
+
// region. Memoized on first read, so a host that swaps its scroller must remount.
|
|
174
|
+
let resolvedScrollHost: UserScrollport | null = null;
|
|
175
|
+
let resolvedClipBounds: HTMLElement[] = [];
|
|
176
|
+
let hostResolved = false;
|
|
177
|
+
function resolveHost(): void {
|
|
178
|
+
if (hostResolved || !editorEl) return;
|
|
179
|
+
resolvedScrollHost = userScrollportFor(editorEl);
|
|
180
|
+
resolvedClipBounds = clippingAncestors(editorEl);
|
|
181
|
+
hostResolved = true;
|
|
182
|
+
}
|
|
183
|
+
/** What a drag autoscrolls: the root in self mode, the nearest scrollable ancestor
|
|
184
|
+
* in host mode. Null only before the root mounts. */
|
|
185
|
+
function getScrollHost(): UserScrollport | null {
|
|
186
|
+
if (!hostScroll) return editorEl ?? null;
|
|
187
|
+
resolveHost();
|
|
188
|
+
return resolvedScrollHost;
|
|
189
|
+
}
|
|
190
|
+
/** Every clipping ancestor; their intersection with the viewport is what a reveal
|
|
191
|
+
* must land inside. */
|
|
192
|
+
function getClipBounds(): HTMLElement[] {
|
|
193
|
+
if (!hostScroll) return [];
|
|
194
|
+
resolveHost();
|
|
195
|
+
return resolvedClipBounds;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// The scrollport every windowing scope measures and writes, over the SAME scroller the
|
|
199
|
+
// autoscroll seam resolves — so the mode picks the target and nothing downstream branches
|
|
200
|
+
// on it. Memoized with that resolution.
|
|
201
|
+
let scrollport: Scrollport | null = null;
|
|
202
|
+
function getScrollport(): Scrollport | null {
|
|
203
|
+
if (!scrollport) {
|
|
204
|
+
const target = getScrollHost();
|
|
205
|
+
if (target) scrollport = createScrollport(target);
|
|
206
|
+
}
|
|
207
|
+
return scrollport;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Install before initDocument parses `source`, so plugin openers/directives are live
|
|
211
|
+
// for the seed grammar. Set-once by contract — a later prop change is ignored.
|
|
212
|
+
// svelte-ignore state_referenced_locally
|
|
213
|
+
const pluginEntries = plugins?.length ? normalizePluginEntries(plugins) : undefined;
|
|
214
|
+
if (pluginEntries) installPlugins(pluginEntries.plugins);
|
|
215
|
+
|
|
216
|
+
// The prop IS the enablement set: this editor activates exactly what it listed. No prop
|
|
217
|
+
// means no restriction, so everything installed in the process stays active here.
|
|
218
|
+
const activePlugins = pluginEntries
|
|
219
|
+
? activationFor(pluginEntries.plugins.map((p) => p.name))
|
|
220
|
+
: everyInstalledPlugin;
|
|
221
|
+
|
|
222
|
+
const overridesMap = $derived(normalizeKeybindingOverrides(keybindings));
|
|
223
|
+
|
|
224
|
+
// The one mode every door reports (root attribute, context getter, plugin contexts,
|
|
225
|
+
// events), and the seam an effective-vs-requested divergence would land in.
|
|
226
|
+
const effectiveMode = $derived(presentationMode);
|
|
227
|
+
// Replace is an edit, so it never engages in reading mode. One predicate feeds the
|
|
228
|
+
// write sites, the render gate, and the replace closures.
|
|
229
|
+
const canReplace = $derived(effectiveMode !== 'reading');
|
|
230
|
+
|
|
231
|
+
const resolveImageUrlImpl: ResolveImageUrl = (u) => (resolveImageUrl ? resolveImageUrl(u) : u);
|
|
232
|
+
const resolveLinkUrlImpl: ResolveLinkUrl = (u) => (resolveLinkUrl ? resolveLinkUrl(u) : u);
|
|
233
|
+
const activateLink = (url: string, event: MouseEvent) =>
|
|
234
|
+
onLinkActivate
|
|
235
|
+
? onLinkActivate(url, event)
|
|
236
|
+
: defaultLinkActivation(url, event, (blocked) => emitBlockedLinkError(events, blocked));
|
|
237
|
+
|
|
238
|
+
// ── State ───────────────────────────────────────────────────────────
|
|
239
|
+
|
|
240
|
+
function initDocument(src: string): {
|
|
241
|
+
doc: Document;
|
|
242
|
+
resolver: LinkReferenceResolver;
|
|
243
|
+
signature: string;
|
|
244
|
+
} {
|
|
245
|
+
const d = parse(src, { scope: 'document' });
|
|
246
|
+
if (d.children.length === 0) {
|
|
247
|
+
// Only the empty source parses to zero blocks — a blank line is a block of its
|
|
248
|
+
// own — so there is no authored ending to inherit and LF is the whole answer.
|
|
249
|
+
d.children.push(emptyParagraph('', '\n'));
|
|
250
|
+
}
|
|
251
|
+
for (const child of d.children) {
|
|
252
|
+
ensureEditableContainers(child);
|
|
253
|
+
}
|
|
254
|
+
const refMap = buildLinkReferenceMap(d.children);
|
|
255
|
+
return { doc: d, resolver: refMap.resolve, signature: refMap.signature };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// doc/blockIds are mutable state structural ops write through directly, so they
|
|
259
|
+
// cannot be $derived: snapshot at mount, re-sync via the $effect below.
|
|
260
|
+
// svelte-ignore state_referenced_locally
|
|
261
|
+
const initial = initDocument(source);
|
|
262
|
+
let doc = $state<Document>(initial.doc);
|
|
263
|
+
// svelte-ignore state_referenced_locally
|
|
264
|
+
let blockIds = $state<string[]>(assignIds(doc.children));
|
|
265
|
+
// Bumped by every byte-writing door. Inline widgets derive on it directly, and the
|
|
266
|
+
// decoration engine's `editEpoch` rides it a tick later.
|
|
267
|
+
const contentVersion = createContentVersion();
|
|
268
|
+
let currentResolver = $state<LinkReferenceResolver>(initial.resolver);
|
|
269
|
+
let currentSignature = $state<string>(initial.signature);
|
|
270
|
+
// Reference-bearing render memos key on this instead of the whole (~MB) signature.
|
|
271
|
+
let signatureEpoch = $state<number>(0);
|
|
272
|
+
/** One ref for every reader — the block components through context and the action bundles
|
|
273
|
+
* through their deps — so a post-commit rebuild reaches both without re-binding either. */
|
|
274
|
+
const linkRefView: LinkReferenceResolverRef = {
|
|
275
|
+
get current(): LinkReferenceResolver {
|
|
276
|
+
return currentResolver;
|
|
277
|
+
},
|
|
278
|
+
get signature(): string {
|
|
279
|
+
return currentSignature;
|
|
280
|
+
},
|
|
281
|
+
get epoch(): number {
|
|
282
|
+
return signatureEpoch;
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
// Plain, not `$state`: the top-level scope's slot storage (see `refSlotsOver`).
|
|
286
|
+
const blockRefs: (BlockComponent | undefined)[] = [];
|
|
287
|
+
const blockRefSlots = refSlotsOver(blockRefs);
|
|
288
|
+
let editorEl: HTMLDivElement | undefined = $state();
|
|
289
|
+
// Inside a themed host the editor sits under no opt-in class, and the portaled search
|
|
290
|
+
// bar must not carry one either: the class's defaults would shadow the host tokens the
|
|
291
|
+
// anchor already inherits.
|
|
292
|
+
const inThemedScope = $derived(!!editorEl?.closest('.aragonite-editor-theme'));
|
|
293
|
+
let headerEl: HTMLDivElement | undefined = $state();
|
|
294
|
+
let typeScaleProbeEl: HTMLDivElement | undefined = $state();
|
|
295
|
+
const undoManager = createUndoManager();
|
|
296
|
+
const sharing = createSharingState();
|
|
297
|
+
const stickyColumn = createStickyColumnState();
|
|
298
|
+
// Composed, not wired seam by seam: the marks are ephemeral caret state with the affinity's
|
|
299
|
+
// exact lifetime, so every door that invalidates the arrival side drops them by construction.
|
|
300
|
+
const pendingMarks = createPendingMarksState();
|
|
301
|
+
const edgeAffinity = createEdgeAffinityState({ onInvalidate: pendingMarks.reset });
|
|
302
|
+
const revealAnchor = createRevealAnchorState();
|
|
303
|
+
const operationsLog = createOperationsLog();
|
|
304
|
+
const events = createEditorEvents();
|
|
305
|
+
// getSelection is function-hoisted below — callback reads the fresh snapshot each time.
|
|
306
|
+
const selectionState = createSelectionState({
|
|
307
|
+
onChange: () => {
|
|
308
|
+
// The other half of the mutual exclusion `onSelect` carries: a range opened while a
|
|
309
|
+
// widget is selected (select-all declines to the widget's own keydown) would leave
|
|
310
|
+
// both live, and every dispatch keyed on "a widget is selected" would answer for
|
|
311
|
+
// the document-wide selection the user is looking at.
|
|
312
|
+
if (selectionState.isCrossBlock) widgetSelection.clear();
|
|
313
|
+
events.emit('selectionChange', getSelection());
|
|
314
|
+
},
|
|
315
|
+
getDoc: () => doc
|
|
316
|
+
});
|
|
317
|
+
const widgetSelection = createWidgetSelectionState({
|
|
318
|
+
onSelect: () => {
|
|
319
|
+
window.getSelection()?.removeAllRanges();
|
|
320
|
+
// Announced like the `source` swap: dropping the native range ends the document
|
|
321
|
+
// caret without moving a field the clear guards on, and the native `selectionchange`
|
|
322
|
+
// it fires bails on the empty range before it reaches the channel.
|
|
323
|
+
selectionState.batch(() => {
|
|
324
|
+
selectionState.clear();
|
|
325
|
+
selectionState.announceSelection();
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
let selectionDescription = $derived(
|
|
331
|
+
selectionState.isCrossBlock && selectionState.anchor && selectionState.focus
|
|
332
|
+
? createSelectionDescription({ anchor: selectionState.anchor, focus: selectionState.focus })
|
|
333
|
+
: ''
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
// Its own polite region: clobbering selectionDescription would drop the move from
|
|
337
|
+
// the a11y tree.
|
|
338
|
+
let reorderAnnouncement = $state('');
|
|
339
|
+
|
|
340
|
+
// Single elements, not per-block, so the hover/drag path adds no cost per mounted
|
|
341
|
+
// component.
|
|
342
|
+
let reorderGhost = $state<{ clientX: number; clientY: number; label: string } | null>(null);
|
|
343
|
+
let reorderLine = $state<{ left: number; top: number; width: number } | null>(null);
|
|
344
|
+
|
|
345
|
+
$effect(() => {
|
|
346
|
+
const dispose = events.on('edit', (e) => {
|
|
347
|
+
operationsLog.record({
|
|
348
|
+
op: e.op,
|
|
349
|
+
path: e.path,
|
|
350
|
+
detail: ('detail' in e ? e.detail : undefined) ?? {}
|
|
351
|
+
});
|
|
352
|
+
// The shell maintains only the LRD resolver (inline content computes lazily
|
|
353
|
+
// on read — core/inline/inline-cache), and hands out a fresh identity only on
|
|
354
|
+
// a real signature change: one per edit re-renders every block that read it.
|
|
355
|
+
if (lrdMapCouldChange(doc, e)) {
|
|
356
|
+
const newMap = buildLinkReferenceMap(doc.children);
|
|
357
|
+
const next = advanceSignatureEpoch(currentSignature, signatureEpoch, newMap.signature);
|
|
358
|
+
if (next.epoch !== signatureEpoch) {
|
|
359
|
+
currentResolver = newMap.resolve;
|
|
360
|
+
currentSignature = next.signature;
|
|
361
|
+
signatureEpoch = next.epoch;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
return () => dispose();
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// The one bump site for `editEpoch`. It rides the content version rather than the `edit`
|
|
369
|
+
// event, whose `input` is batched across a typing burst and would leave every source a
|
|
370
|
+
// pause behind the bytes; the tick keeps a source off a half-applied tree, and the
|
|
371
|
+
// no-source skip keeps an undecorated editor at zero keystroke work (perf:check).
|
|
372
|
+
let notifiedVersion = contentVersion.read();
|
|
373
|
+
$effect(() => {
|
|
374
|
+
const version = contentVersion.read();
|
|
375
|
+
if (version === notifiedVersion) return; // the mount run announces nothing
|
|
376
|
+
notifiedVersion = version;
|
|
377
|
+
if (decorationEngine.sourceCount > 0) void tick().then(() => decorationEngine.notifyEdit());
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Counts whole-document REPLACEMENTS, which the edit epoch cannot tell from a
|
|
381
|
+
// keystroke. Deliberately NOT $state: its readers run inside decoration `provide`,
|
|
382
|
+
// which must register no reactive dependency.
|
|
383
|
+
let documentGeneration = 0;
|
|
384
|
+
|
|
385
|
+
// `source !== lastSource` guard is load-bearing — see `docs/design/editor.md` § Reactive state plumbing.
|
|
386
|
+
// svelte-ignore state_referenced_locally
|
|
387
|
+
let lastSource = source;
|
|
388
|
+
$effect(() => {
|
|
389
|
+
if (source !== lastSource) {
|
|
390
|
+
// A pending typing batch addresses the OUTGOING document, so it flushes
|
|
391
|
+
// while its path still resolves; left armed, the timer fires note A's path
|
|
392
|
+
// against note B.
|
|
393
|
+
controller.flushDebouncedCheckpoint();
|
|
394
|
+
lastSource = source;
|
|
395
|
+
const reset = initDocument(source);
|
|
396
|
+
doc = reset.doc;
|
|
397
|
+
contentVersion.bump();
|
|
398
|
+
blockIds = assignIds(doc.children);
|
|
399
|
+
blockRefs.length = 0;
|
|
400
|
+
// Block ids never recur, so every measured height now keys a block that cannot
|
|
401
|
+
// come back: the scopes reseed from estimates exactly as they do on first load.
|
|
402
|
+
heightOracle.dropMeasured();
|
|
403
|
+
undoManager.clear();
|
|
404
|
+
stickyColumn.reset();
|
|
405
|
+
edgeAffinity.reset();
|
|
406
|
+
// Announced, not left to the clear: the swap usually arrives on a plain caret, which
|
|
407
|
+
// is native-only, so nothing editor-owned moves and subscribers would keep painting
|
|
408
|
+
// the outgoing document's selection. Batched, so a real range still emits once.
|
|
409
|
+
selectionState.batch(() => {
|
|
410
|
+
selectionState.clear();
|
|
411
|
+
selectionState.announceSelection();
|
|
412
|
+
});
|
|
413
|
+
documentGeneration++;
|
|
414
|
+
// The resolver refreshes unconditionally — the old one closes over the
|
|
415
|
+
// swapped-out doc — but the epoch bumps only on a differing LRD signature.
|
|
416
|
+
const next = advanceSignatureEpoch(currentSignature, signatureEpoch, reset.signature);
|
|
417
|
+
currentResolver = reset.resolver;
|
|
418
|
+
currentSignature = next.signature;
|
|
419
|
+
signatureEpoch = next.epoch;
|
|
420
|
+
}
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* The host's own chrome, mounted inside this root. Every rule meaning "this is the
|
|
425
|
+
* editor's own CONTENT" asks here rather than carrying its own `contains` copy,
|
|
426
|
+
* which is how one gets missed. The focusout guards keep using `contains` — for
|
|
427
|
+
* "did focus leave the whole widget", the slot IS part of the editor.
|
|
428
|
+
*/
|
|
429
|
+
function isHostChrome(node: Node | null): boolean {
|
|
430
|
+
return !!node && !!headerEl && headerEl.contains(node);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ── Dead-space caret ────────────────────────────────────────────────
|
|
434
|
+
|
|
435
|
+
// A click in the root's padding or below the last block places a caret rather than
|
|
436
|
+
// doing nothing. `getBlockComponent` is hoisted; the reset closure defers its reads.
|
|
437
|
+
const deadSpaceCaret = createDeadSpaceCaret({
|
|
438
|
+
getBlockComponent,
|
|
439
|
+
resetSelectionForClick: () =>
|
|
440
|
+
resetForPointerDown(selectionState, stickyColumn, edgeAffinity, false),
|
|
441
|
+
gapScope: {
|
|
442
|
+
getDoc: () => doc,
|
|
443
|
+
selection: selectionState,
|
|
444
|
+
getPresentationMode: () => effectiveMode
|
|
445
|
+
},
|
|
446
|
+
lastBlockIndex: () => doc.children.length - 1,
|
|
447
|
+
revealBlock: (index) => revealPath([index])
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
// ── Link card door ──────────────────────────────────────────────────
|
|
451
|
+
|
|
452
|
+
// The caret snapshot and the entry guards ride the STATE, not the callers, so entry path N+1
|
|
453
|
+
// cannot forget them. All three decline a cross-block range absolutely; on a same-block one
|
|
454
|
+
// they split by gesture: an unasked-for click must not interrupt it, the create gesture wraps
|
|
455
|
+
// it, and the chord has already resolved it against the construct it opens.
|
|
456
|
+
const linkCard = createLinkCardState({
|
|
457
|
+
onOpen: () => linkCardCaret.saveCurrent(),
|
|
458
|
+
canOpen: () => !selectionState.isCrossBlock && window.getSelection()?.isCollapsed !== false,
|
|
459
|
+
canEnter: () => !selectionState.isCrossBlock,
|
|
460
|
+
canOpenCreate: () =>
|
|
461
|
+
!selectionState.isCrossBlock && window.getSelection()?.isCollapsed === false
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
/** Open the card on the link `el` renders, or report that nothing there is one. The caret has
|
|
465
|
+
* already landed from mousedown, which is the one the state snapshots. */
|
|
466
|
+
function openLinkCard(el: Element): boolean {
|
|
467
|
+
const path = findSurfacePathForElement(el);
|
|
468
|
+
if (!path) return false;
|
|
469
|
+
const block = nodeAt(doc, path);
|
|
470
|
+
if (block === null || !isBlockNode(block)) return false;
|
|
471
|
+
const contentEl = getBlockElByPath(path);
|
|
472
|
+
if (!contentEl) return false;
|
|
473
|
+
const hit = resolveLinkAtPoint({ contentEl, block, path, linkRef: linkRefView });
|
|
474
|
+
if (!hit) return false;
|
|
475
|
+
return linkCard.open(hit.target);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// The card belongs to live mode alone; any other mode paints the destination bytes already.
|
|
479
|
+
$effect(() => {
|
|
480
|
+
if (effectiveMode !== 'live') linkCard.close();
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
// ── Hidden-run class probe ──────────────────────────────────────────
|
|
484
|
+
|
|
485
|
+
// Once per mode change, the probe pays the getComputedStyle the per-keystroke hidden-run
|
|
486
|
+
// predicate cannot afford, so the two class vocabularies cannot drift silently.
|
|
487
|
+
$effect(() => {
|
|
488
|
+
void effectiveMode;
|
|
489
|
+
if (!editorEl) return;
|
|
490
|
+
const root = editorEl;
|
|
491
|
+
assertInvariant('marker-css-parity', () => checkMarkerCssParity(root));
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
// ── Root gesture listeners ──────────────────────────────────────────
|
|
495
|
+
|
|
496
|
+
$effect(() => {
|
|
497
|
+
if (!editorEl) return;
|
|
498
|
+
const root = editorEl;
|
|
499
|
+
const handleClick = (e: MouseEvent) => {
|
|
500
|
+
const target = e.target as Element | null;
|
|
501
|
+
// Ahead of the anchor arm: a blocked-scheme link renders as a SPAN, and it is exactly
|
|
502
|
+
// the link a user opens the card to fix. Mod-click still activates, below.
|
|
503
|
+
if (effectiveMode === 'live' && !e.ctrlKey && !e.metaKey) {
|
|
504
|
+
const linkEl = target?.closest(LINK_ELEMENT_SELECTOR);
|
|
505
|
+
if (linkEl && !isHostChrome(linkEl) && openLinkCard(linkEl)) {
|
|
506
|
+
e.preventDefault();
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const anchor = target?.closest('a[href]') as HTMLAnchorElement | null;
|
|
511
|
+
// The helper claims only clicks whose target IS the root, so nothing the
|
|
512
|
+
// editor renders is touched.
|
|
513
|
+
if (!anchor) {
|
|
514
|
+
deadSpaceCaret.handleClick(root, e);
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
// Host chrome follows the page's link behaviour, not plain-click-edits.
|
|
518
|
+
if (isHostChrome(anchor)) return;
|
|
519
|
+
const href = anchor.getAttribute('href');
|
|
520
|
+
if (!href) return;
|
|
521
|
+
// Reading mode has no caret for a plain click to place, so links behave as
|
|
522
|
+
// in a rendered document.
|
|
523
|
+
if (e.ctrlKey || e.metaKey || effectiveMode === 'reading') {
|
|
524
|
+
e.preventDefault();
|
|
525
|
+
activateLink(href, e);
|
|
526
|
+
} else {
|
|
527
|
+
// Suppress the browser's link navigation; cursor placement comes from
|
|
528
|
+
// mousedown and is unaffected.
|
|
529
|
+
e.preventDefault();
|
|
530
|
+
}
|
|
531
|
+
};
|
|
532
|
+
return removeAll(
|
|
533
|
+
onRoot(root, 'click', handleClick),
|
|
534
|
+
onRoot(root, 'mousedown', (e: MouseEvent) => deadSpaceCaret.notePress(root, e))
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// Release on the next user-intent gesture, so the anchor holds only through the post-reveal
|
|
539
|
+
// settle. NOT on `scroll`: a programmatic correctAnchor write fires `scroll` itself and
|
|
540
|
+
// would self-release mid-settle. On the resolved PORT, not the root — the pin fights
|
|
541
|
+
// whoever scrolls the port, and in host mode that gesture lands outside the editor.
|
|
542
|
+
$effect(() => {
|
|
543
|
+
if (!editorEl) return;
|
|
544
|
+
const target = getScrollHost();
|
|
545
|
+
if (!target) return;
|
|
546
|
+
const release = () => revealAnchor.releaseAll();
|
|
547
|
+
return removeAll(
|
|
548
|
+
onRoot(target, 'keydown', release),
|
|
549
|
+
onRoot(target, 'pointerdown', release),
|
|
550
|
+
onRoot(target, 'wheel', release, { passive: true })
|
|
551
|
+
);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
$effect(() => {
|
|
555
|
+
if (!editorEl) return;
|
|
556
|
+
const root = editorEl;
|
|
557
|
+
// focusout bubbles, so reset only when focus leaves the editor entirely.
|
|
558
|
+
return onRoot(root, 'focusout', (e: FocusEvent) => {
|
|
559
|
+
const next = e.relatedTarget as Node | null;
|
|
560
|
+
if (next && root.contains(next)) return;
|
|
561
|
+
stickyColumn.reset();
|
|
562
|
+
edgeAffinity.reset();
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
|
|
566
|
+
// Its own effect: this reads no root binding, so pairing it with the focusout one
|
|
567
|
+
// would make it wait for the bind and re-install on every root change.
|
|
568
|
+
$effect(() =>
|
|
569
|
+
onRoot(document, 'visibilitychange', () => {
|
|
570
|
+
if (document.visibilityState === 'hidden') {
|
|
571
|
+
stickyColumn.reset();
|
|
572
|
+
edgeAffinity.reset();
|
|
573
|
+
}
|
|
574
|
+
})
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
// Register as a body-chord claimant so the document-level keydown handler routes a
|
|
578
|
+
// body-level chord to exactly one instance: a lone editor claims unconditionally,
|
|
579
|
+
// among several the last-interacted one wins.
|
|
580
|
+
$effect(() => {
|
|
581
|
+
if (!editorEl) return;
|
|
582
|
+
const root = editorEl;
|
|
583
|
+
registerEditor(root);
|
|
584
|
+
const removeMark = onRoot(root, 'focusin', () => markEditorInteracted(root));
|
|
585
|
+
return () => {
|
|
586
|
+
removeMark();
|
|
587
|
+
releaseInteractedEditor(root);
|
|
588
|
+
unregisterEditor(root);
|
|
589
|
+
};
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
// ── Block element and component lookup ──────────────────────────────
|
|
593
|
+
|
|
594
|
+
// The measurement surface for cross-block caret math. Table cells carry no
|
|
595
|
+
// data-block-path (they render without BlockHost), so a deep cell path resolves
|
|
596
|
+
// the table wrapper and walks into the cell DOM.
|
|
597
|
+
const getBlockElByPath: BlockElLookup = (path) => {
|
|
598
|
+
if (!editorEl) return null;
|
|
599
|
+
const directWrapper = editorEl.querySelector(`[data-block-path='${JSON.stringify(path)}']`);
|
|
600
|
+
if (directWrapper) {
|
|
601
|
+
return directWrapper.querySelector(BLOCK_CONTENT_SELECTOR) as HTMLElement | null;
|
|
602
|
+
}
|
|
603
|
+
if (path.length < 3) return null;
|
|
604
|
+
const tablePath = path.slice(0, -2);
|
|
605
|
+
const rowIdx = path[path.length - 2];
|
|
606
|
+
const colIdx = path[path.length - 1];
|
|
607
|
+
const tableWrapper = editorEl.querySelector(`[data-block-path='${JSON.stringify(tablePath)}']`);
|
|
608
|
+
if (!tableWrapper) return null;
|
|
609
|
+
const tableEl = tableWrapper.querySelector(':scope > [role="table"]');
|
|
610
|
+
if (!tableEl) return null;
|
|
611
|
+
const rowEl = tableEl.querySelector(`:scope > [data-table-row-idx='${rowIdx}']`);
|
|
612
|
+
if (!rowEl) return null;
|
|
613
|
+
const cells = rowEl.querySelectorAll(':scope > [role="cell"]');
|
|
614
|
+
return (cells[colIdx] as HTMLElement | undefined) ?? null;
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
// The non-scrolling sibling of revealPath, and the one descent both the rect API
|
|
618
|
+
// and the test surface consume — a second closure would drift from it.
|
|
619
|
+
function getBlockComponent(path: number[]): BlockComponent | null {
|
|
620
|
+
if (path.length === 0) return null;
|
|
621
|
+
const [first, ...rest] = path;
|
|
622
|
+
const ref = blockRefs[first];
|
|
623
|
+
if (!ref) return null;
|
|
624
|
+
if (rest.length === 0) return ref;
|
|
625
|
+
return ref.getBlockComponentByPath?.(rest) ?? null;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// ── Action Bundles ──────────────────────────────────────────────────
|
|
629
|
+
|
|
630
|
+
// Hoisted so the deps literal below can reference it before the VR state it reads
|
|
631
|
+
// is declared; the body runs only at call time, post-init.
|
|
632
|
+
async function revealPath(path: number[]): Promise<BlockComponent | null> {
|
|
633
|
+
if (path.length === 0) return null;
|
|
634
|
+
const top = path[0];
|
|
635
|
+
// The shared reveal-and-wait gate skips an already-mounted block, re-checks after
|
|
636
|
+
// a spurious cross-level wake, and — load-bearing for VR-5 — degrades instead of
|
|
637
|
+
// hanging when a stale model left `top` outside the recomputed window.
|
|
638
|
+
await revealChildOrWait(top, {
|
|
639
|
+
slots: blockRefSlots,
|
|
640
|
+
childCount: doc.children.length,
|
|
641
|
+
revealChild: topWindowing.revealChild,
|
|
642
|
+
isInWindow: topWindowing.isInWindow
|
|
643
|
+
});
|
|
644
|
+
const ref = blockRefs[top];
|
|
645
|
+
if (!ref) return null;
|
|
646
|
+
if (path.length === 1) return ref;
|
|
647
|
+
return ref.revealByPath
|
|
648
|
+
? await ref.revealByPath(path.slice(1))
|
|
649
|
+
: (ref.getBlockComponentByPath?.(path.slice(1)) ?? null);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// The instance's resolution over the global block definitions: an unlisted plugin's kind
|
|
653
|
+
// resolves no component here and its opener leaves this grammar. A prop-less editor reads
|
|
654
|
+
// the global registry verbatim.
|
|
655
|
+
// The harness door composes rather than replaces: widening past what the prop activated
|
|
656
|
+
// would prove a resolution the shipped path cannot reach.
|
|
657
|
+
// svelte-ignore state_referenced_locally
|
|
658
|
+
const registryView = createRegistryView({
|
|
659
|
+
isEnabled: bothEnable(
|
|
660
|
+
pluginEntries ? kindEnablementFor(activePlugins) : undefined,
|
|
661
|
+
__registryEnablement
|
|
662
|
+
)
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
const editorActionsDeps: EditorActionsDeps = {
|
|
666
|
+
get doc() {
|
|
667
|
+
return doc;
|
|
668
|
+
},
|
|
669
|
+
get blockIds() {
|
|
670
|
+
return blockIds;
|
|
671
|
+
},
|
|
672
|
+
get blockRefs() {
|
|
673
|
+
return blockRefs;
|
|
674
|
+
},
|
|
675
|
+
blockRefSlots,
|
|
676
|
+
setDoc: (v) => {
|
|
677
|
+
doc = v;
|
|
678
|
+
},
|
|
679
|
+
bumpContentVersion: contentVersion.bump,
|
|
680
|
+
setBlockIds: (v) => {
|
|
681
|
+
blockIds = v;
|
|
682
|
+
},
|
|
683
|
+
setBlockRefs: (v) => replaceRefs(blockRefs, v),
|
|
684
|
+
undoManager,
|
|
685
|
+
sharing,
|
|
686
|
+
stickyColumn,
|
|
687
|
+
edgeAffinity,
|
|
688
|
+
selectionState,
|
|
689
|
+
getBlockElByPath,
|
|
690
|
+
revealPath,
|
|
691
|
+
events,
|
|
692
|
+
grammar: registryView.grammar,
|
|
693
|
+
getPresentationMode: () => effectiveMode,
|
|
694
|
+
get linkRef() {
|
|
695
|
+
return linkRefView;
|
|
696
|
+
}
|
|
697
|
+
};
|
|
698
|
+
const { blockEdit, focus, history, containerEdit, controller } =
|
|
699
|
+
createEditorActions(editorActionsDeps);
|
|
700
|
+
|
|
701
|
+
// A getter, so block components read the live doc at keystroke time rather than
|
|
702
|
+
// the snapshot they mounted with.
|
|
703
|
+
const getDoc: DocumentGetter = () => doc;
|
|
704
|
+
|
|
705
|
+
// Ahead of the plugin contexts because the plugin door hands its registry into
|
|
706
|
+
// createEditorPluginContexts below.
|
|
707
|
+
const decorationEngine = createDecorationEngine({
|
|
708
|
+
getDoc,
|
|
709
|
+
onSourceError: (source, error) =>
|
|
710
|
+
events.emit('error', { origin: 'decoration', error, context: { source } })
|
|
711
|
+
});
|
|
712
|
+
const decorations: DecorationRegistry = { addSource: decorationEngine.addSource };
|
|
713
|
+
|
|
714
|
+
// Reuses revealPath/getBlockElByPath/getBlockComponent so nothing measures through
|
|
715
|
+
// a second closure.
|
|
716
|
+
const rects = createEditorRects({
|
|
717
|
+
getBlockElByPath,
|
|
718
|
+
getBlockComponentByPath: getBlockComponent,
|
|
719
|
+
revealPath,
|
|
720
|
+
getEditorRoot: () => editorEl ?? null,
|
|
721
|
+
isHostScroll: () => hostScroll,
|
|
722
|
+
getClipBounds,
|
|
723
|
+
isCrossBlock: () => selectionState.isCrossBlock,
|
|
724
|
+
isHostChrome,
|
|
725
|
+
revealAnchor,
|
|
726
|
+
// A navigation holds its pin, unlike the consumer restore door: nothing follows
|
|
727
|
+
// it that wants the viewport back, and the band should outlive a late decode.
|
|
728
|
+
landCaretAt: landCaretAtOffset
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// After getDoc so it reuses that one live-doc closure: a second getDoc would be a
|
|
732
|
+
// TDZ reference here, and the rule is one getter, never a captured value.
|
|
733
|
+
const editorId = mintEditorId();
|
|
734
|
+
const pluginContexts = createEditorPluginContexts({
|
|
735
|
+
editorId,
|
|
736
|
+
getDoc,
|
|
737
|
+
events,
|
|
738
|
+
optionsFor: (name) => pluginEntries?.optionsByName.get(name),
|
|
739
|
+
decorations,
|
|
740
|
+
rects,
|
|
741
|
+
// The one injection point of the mode into the dispatch tiers; they read it back
|
|
742
|
+
// through the pluginEditor lookup they already thread.
|
|
743
|
+
getPresentationMode: () => effectiveMode,
|
|
744
|
+
getTheme: () => theme,
|
|
745
|
+
activation: activePlugins
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// One definition, threaded by every dispatch tier that can reach a plugin-global
|
|
749
|
+
// handler, so leaf, cross-block and editor-root route identically.
|
|
750
|
+
const pluginEditorLookup: PluginEditorLookup = (name) => pluginContexts.get(name);
|
|
751
|
+
const commandErrorSink: CommandErrorSink = (report) => emitCommandError(events, report);
|
|
752
|
+
|
|
753
|
+
// onMount, NEVER a plain $effect: attachAll synchronously runs plugin callbacks that
|
|
754
|
+
// read reactive state, so an effect would take doc.children as a dependency and the
|
|
755
|
+
// first structural edit would dispose every subscription (casebook.md's re-init scar).
|
|
756
|
+
onMount(() => {
|
|
757
|
+
pluginContexts.attachAll(({ plugin, error }) =>
|
|
758
|
+
events.emit('error', { origin: 'subscriber', error, context: { plugin } })
|
|
759
|
+
);
|
|
760
|
+
return () => pluginContexts.dispose();
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
// Aborted on unmount; document-level listeners observe it to cancel mid-operation work.
|
|
764
|
+
const lifetimeController = new AbortController();
|
|
765
|
+
$effect(() => () => {
|
|
766
|
+
// Same reason as the source swap: a timer outliving the component emits into
|
|
767
|
+
// subscribers the host still holds, for a document that is gone.
|
|
768
|
+
controller.flushDebouncedCheckpoint();
|
|
769
|
+
lifetimeController.abort();
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
// Per-instance so two editors on one page never leak load failures into each
|
|
773
|
+
// other's broken-state recompute.
|
|
774
|
+
const brokenImageUrls = new Set<string>();
|
|
775
|
+
|
|
776
|
+
const pasteCoordinator = createPasteCoordinator(controller, revealPath);
|
|
777
|
+
|
|
778
|
+
// The document caret while chrome holds focus (selection/caret-restore.ts). One instance PER
|
|
779
|
+
// consumer: a card opened over an open search bar would otherwise overwrite the pre-search
|
|
780
|
+
// caret with its own, and closing the bar would land the user at the link.
|
|
781
|
+
const searchCaret = createCaretRestore(() => editorEl ?? null);
|
|
782
|
+
const linkCardCaret = createCaretRestore(() => editorEl ?? null);
|
|
783
|
+
|
|
784
|
+
const searchReplace = createSearchReplace(editorActionsDeps, controller);
|
|
785
|
+
// Find stays live in reading mode; replace is an edit and no-ops at this seam.
|
|
786
|
+
const gatedSearchReplace: typeof searchReplace = {
|
|
787
|
+
replaceOne: (match, template) =>
|
|
788
|
+
canReplace ? searchReplace.replaceOne(match, template) : Promise.resolve(0),
|
|
789
|
+
replaceAll: (matches, template) =>
|
|
790
|
+
canReplace ? searchReplace.replaceAll(matches, template) : Promise.resolve(0)
|
|
791
|
+
};
|
|
792
|
+
const searchState = createSearchState({
|
|
793
|
+
getDoc,
|
|
794
|
+
getDocumentGeneration: () => documentGeneration,
|
|
795
|
+
decorations,
|
|
796
|
+
replace: gatedSearchReplace,
|
|
797
|
+
// Rides the one public seam, which also owns the reveal anchor (top-pinned by
|
|
798
|
+
// default, which is what search wants), so the band's async image-decode churn
|
|
799
|
+
// can't clamp the reveal off the match.
|
|
800
|
+
reveal: (p) => rects.scrollTo(p),
|
|
801
|
+
onClose: searchCaret.restore
|
|
802
|
+
});
|
|
803
|
+
// Lives here, not in SearchBar, so the root Ctrl+H and the bar's chevron share one
|
|
804
|
+
// source of truth.
|
|
805
|
+
let replaceExpanded = $state(false);
|
|
806
|
+
|
|
807
|
+
const announceReorder = async (message: string) => {
|
|
808
|
+
// Clear first: Svelte skips the DOM write on a ===-equal assignment, which drops
|
|
809
|
+
// the second of two identical announcements.
|
|
810
|
+
reorderAnnouncement = '';
|
|
811
|
+
await tick();
|
|
812
|
+
reorderAnnouncement = message;
|
|
813
|
+
};
|
|
814
|
+
const reorder = createReorderAction(editorActionsDeps, controller, (to, total) => {
|
|
815
|
+
announceReorder(movedBlockToPosition(to + 1, total));
|
|
816
|
+
});
|
|
817
|
+
|
|
818
|
+
// ── Context provision ───────────────────────────────────────────────
|
|
819
|
+
|
|
820
|
+
// One per instance, shared by every dispatch site's gates: the chord arm, a leaf's rebound
|
|
821
|
+
// chord and the `runCommand` door must reach the same cross-block road.
|
|
822
|
+
const crossBlockCommands = createCrossBlockCommands({
|
|
823
|
+
selection: selectionState,
|
|
824
|
+
getDoc,
|
|
825
|
+
getBlockElByPath,
|
|
826
|
+
revealPath,
|
|
827
|
+
controller,
|
|
828
|
+
getPresentationMode: () => effectiveMode,
|
|
829
|
+
grammar: registryView.grammar,
|
|
830
|
+
getContentVersion: contentVersion.read
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
// The action bundles stay per-key so a container re-provides exactly what it
|
|
834
|
+
// overrides; HISTORY is G1.4's single-provider subject. The rest rides three facets.
|
|
835
|
+
setContext(BLOCK_EDIT_KEY, blockEdit);
|
|
836
|
+
setContext(FOCUS_KEY, focus);
|
|
837
|
+
setContext(HISTORY_KEY, history);
|
|
838
|
+
setContext(CONTAINER_EDIT_KEY, containerEdit);
|
|
839
|
+
|
|
840
|
+
setContext(EDITOR_SERVICES_KEY, {
|
|
841
|
+
events,
|
|
842
|
+
decorations: decorationEngine,
|
|
843
|
+
selection: selectionState,
|
|
844
|
+
search: searchState,
|
|
845
|
+
stickyColumn,
|
|
846
|
+
edgeAffinity,
|
|
847
|
+
pendingMarks,
|
|
848
|
+
revealAnchor,
|
|
849
|
+
widgetSelection,
|
|
850
|
+
linkCard,
|
|
851
|
+
controller,
|
|
852
|
+
pasteCoordinator,
|
|
853
|
+
reorder,
|
|
854
|
+
reorderAnnounce: announceReorder,
|
|
855
|
+
registryView,
|
|
856
|
+
activePlugins,
|
|
857
|
+
rects,
|
|
858
|
+
crossBlockCommands
|
|
859
|
+
} satisfies EditorServices);
|
|
860
|
+
|
|
861
|
+
setContext(EDITOR_POLICIES_KEY, {
|
|
862
|
+
resolveImageUrl: resolveImageUrlImpl,
|
|
863
|
+
resolveLinkUrl: resolveLinkUrlImpl,
|
|
864
|
+
imageLoadPolicy: () => imageLoadPolicy,
|
|
865
|
+
// Reading mode forces the affordances off through the prop's own funnel; the handle
|
|
866
|
+
// and the table grips all read this one getter.
|
|
867
|
+
blockDragHandles: () => blockDragHandles && effectiveMode !== 'reading',
|
|
868
|
+
presentationMode: () => effectiveMode,
|
|
869
|
+
theme: () => theme,
|
|
870
|
+
keybindingOverrides: () => overridesMap,
|
|
871
|
+
// An accessor, not the `onPasteImage,` shorthand: the shorthand captures the prop
|
|
872
|
+
// and svelte-check reports `state_referenced_locally`, which
|
|
873
|
+
// `svelte/no-unused-svelte-ignore` won't let us suppress.
|
|
874
|
+
get onPasteImage() {
|
|
875
|
+
return onPasteImage;
|
|
876
|
+
},
|
|
877
|
+
brokenImageUrls
|
|
878
|
+
} satisfies EditorPolicies);
|
|
879
|
+
|
|
880
|
+
// ── Root DOM effects ────────────────────────────────────────────────
|
|
881
|
+
|
|
882
|
+
/** The focused leaf's caret as (path, raw offset): the per-backend road while a leaf holds
|
|
883
|
+
* focus, else the native range a toggle click leaves behind while chrome takes focus. */
|
|
884
|
+
function captureFlipCaret(): { path: number[]; offset: number } | null {
|
|
885
|
+
if (selectionState.isCrossBlock || selectionState.gapCaret) return null;
|
|
886
|
+
const focused = readCurrentSelection(selectionState, blockRefs)?.focus;
|
|
887
|
+
if (focused) return { path: focused.path, offset: focused.offset };
|
|
888
|
+
const sel = window.getSelection();
|
|
889
|
+
if (!sel?.focusNode || !editorEl?.contains(sel.focusNode) || isHostChrome(sel.focusNode))
|
|
890
|
+
return null;
|
|
891
|
+
const node = sel.focusNode;
|
|
892
|
+
const el = node instanceof Element ? node : node.parentElement;
|
|
893
|
+
const path = findSurfacePathForElement(el);
|
|
894
|
+
if (!path) return null;
|
|
895
|
+
const contentEl = getBlockElByPath(path);
|
|
896
|
+
if (!contentEl?.contains(node)) return null;
|
|
897
|
+
const offset = toClampedRawOffset(
|
|
898
|
+
domTextOffsetAtNode(contentEl, node, sel.focusOffset),
|
|
899
|
+
ambientLengthOf(contentEl)
|
|
900
|
+
);
|
|
901
|
+
return { path, offset };
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
// The flip's PRE phase, the last moment the outgoing mode owns the DOM: past it the mode's
|
|
905
|
+
// render key has rebuilt every block from its own CST bytes, and the reveal's ephemeral edit
|
|
906
|
+
// is gone. Reading keeps its entry snapshot — no caret of its own to recapture on the way out.
|
|
907
|
+
let flipCaret: { path: number[]; offset: number } | null = null;
|
|
908
|
+
// svelte-ignore state_referenced_locally
|
|
909
|
+
let preFlipSeenMode = effectiveMode;
|
|
910
|
+
$effect.pre(() => {
|
|
911
|
+
const mode = effectiveMode;
|
|
912
|
+
if (mode === preFlipSeenMode) return;
|
|
913
|
+
const from = preFlipSeenMode;
|
|
914
|
+
preFlipSeenMode = mode;
|
|
915
|
+
untrack(() => {
|
|
916
|
+
if (from !== 'reading') flipCaret = captureFlipCaret();
|
|
917
|
+
// A flip is a blur-class event: a live reveal or composition folds through the existing
|
|
918
|
+
// blur choke point. Host chrome is exempt, or a mode toggle blurs a title field mid-edit.
|
|
919
|
+
const active = document.activeElement;
|
|
920
|
+
if (active instanceof HTMLElement && editorEl?.contains(active) && !isHostChrome(active)) {
|
|
921
|
+
active.blur();
|
|
922
|
+
// A blur the editor performs announces the selection it drops: the document listener
|
|
923
|
+
// only reports a range the browser still anchors in the root, and this one is gone.
|
|
924
|
+
events.emit('selectionChange', getSelection());
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
// svelte-ignore state_referenced_locally
|
|
930
|
+
let lastEffectiveMode = effectiveMode;
|
|
931
|
+
$effect(() => {
|
|
932
|
+
const mode = effectiveMode;
|
|
933
|
+
if (mode === lastEffectiveMode) return;
|
|
934
|
+
lastEffectiveMode = mode;
|
|
935
|
+
// Which markers paint just changed, so a side recorded against the old geometry
|
|
936
|
+
// no longer names the offset the user meant.
|
|
937
|
+
edgeAffinity.reset();
|
|
938
|
+
// Hidden markers re-wrap prose, so every measured height is the other mode's. Not paired
|
|
939
|
+
// with `widthVersion++`: the flip has blurred, so a rebuild here loses the caret pin.
|
|
940
|
+
heightOracle.dropMeasured();
|
|
941
|
+
if (mode === 'reading') {
|
|
942
|
+
// The gap is an editor-owned caret no DOM blur can reach, so the flip clears it
|
|
943
|
+
// rather than each arrival path.
|
|
944
|
+
selectionState.clearGapCaret();
|
|
945
|
+
} else if (flipCaret) {
|
|
946
|
+
const caret = flipCaret;
|
|
947
|
+
flipCaret = null;
|
|
948
|
+
void (async () => {
|
|
949
|
+
// A post-tick focus like every structural op's; the road clamps the saved offset
|
|
950
|
+
// into the destination mode's landable range at the door. Yielding to a focused
|
|
951
|
+
// text-entry surface keeps the restore from stealing a host field mid-typing.
|
|
952
|
+
// The reveal is the bare MOUNT: a flip is a view operation, so it re-seats the
|
|
953
|
+
// caret without writing the scrollport the reader chose.
|
|
954
|
+
await tick();
|
|
955
|
+
if (effectiveMode !== mode || isTextEntrySurface(document.activeElement)) return;
|
|
956
|
+
await restoreThroughRevealRoad(caretAt(caret.path, caret.offset), 'mount');
|
|
957
|
+
})();
|
|
958
|
+
}
|
|
959
|
+
events.emit('presentationModeChange', mode);
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
// A theme flip invalidates no live edit, so it only has to be announced — for
|
|
963
|
+
// plugins that paint their own colors and can't see the flip through CSS.
|
|
964
|
+
// svelte-ignore state_referenced_locally
|
|
965
|
+
let lastTheme = theme;
|
|
966
|
+
$effect(() => {
|
|
967
|
+
const next = theme;
|
|
968
|
+
if (next === lastTheme) return;
|
|
969
|
+
lastTheme = next;
|
|
970
|
+
events.emit('themeChange', next);
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
// CSS keys on `data-cross-block` to hide the native caret and selection highlight
|
|
974
|
+
// while the overlay paints the cross-block range. Keyed on the OVERLAY's own predicate:
|
|
975
|
+
// a state it declines to paint must not also lose the caret, or the screen shows neither.
|
|
976
|
+
$effect(() => {
|
|
977
|
+
if (!editorEl) return;
|
|
978
|
+
if (selectionState.isCustomRendered) {
|
|
979
|
+
editorEl.setAttribute('data-cross-block', '');
|
|
980
|
+
} else {
|
|
981
|
+
editorEl.removeAttribute('data-cross-block');
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
$effect(() => {
|
|
986
|
+
if (!editorEl) return;
|
|
987
|
+
return installModActiveTracker(editorEl);
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
// A delegated handle-drag on the root, torn down on unmount via the lifetime signal.
|
|
991
|
+
$effect(() => {
|
|
992
|
+
if (!editorEl) return;
|
|
993
|
+
const handle = installReorderDrag({
|
|
994
|
+
editorRoot: editorEl,
|
|
995
|
+
getScrollHost,
|
|
996
|
+
moveReorderUnit: reorder.moveReorderUnit,
|
|
997
|
+
overlay: {
|
|
998
|
+
setGhost: (g) => (reorderGhost = g),
|
|
999
|
+
setLine: (l) => (reorderLine = l)
|
|
1000
|
+
},
|
|
1001
|
+
lifetimeSignal: lifetimeController.signal
|
|
1002
|
+
});
|
|
1003
|
+
return () => handle.dispose();
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
$effect(() => {
|
|
1007
|
+
if (!editorEl) return;
|
|
1008
|
+
return installSelectionChangeBridge({
|
|
1009
|
+
root: editorEl,
|
|
1010
|
+
isHostChrome,
|
|
1011
|
+
emit: () => events.emit('selectionChange', getSelection())
|
|
1012
|
+
});
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
$effect(() => {
|
|
1016
|
+
if (!editorEl) return;
|
|
1017
|
+
return installEditorBlurAnnouncer({
|
|
1018
|
+
root: editorEl,
|
|
1019
|
+
emit: () => events.emit('selectionChange', getSelection())
|
|
1020
|
+
});
|
|
1021
|
+
});
|
|
1022
|
+
|
|
1023
|
+
// ── Editor-root keydown routing ──────────────────────────────────────
|
|
1024
|
+
//
|
|
1025
|
+
// When the caret's block windows out, focus drops to <body> and the per-block keydown handlers
|
|
1026
|
+
// go silent, so this editor-scope handler reuses the same cross-block composer with the root
|
|
1027
|
+
// and the focus path standing in for `getEl` and `getMyPath`.
|
|
1028
|
+
const editorCrossBlock = createCrossBlockHandlers({
|
|
1029
|
+
getEl: () => editorEl ?? null,
|
|
1030
|
+
getMyPath: () => selectionState.focus?.path ?? [],
|
|
1031
|
+
getIndex: () => selectionState.focus?.path?.[0] ?? 0,
|
|
1032
|
+
selection: selectionState,
|
|
1033
|
+
getDoc,
|
|
1034
|
+
getBlockElByPath,
|
|
1035
|
+
revealPath,
|
|
1036
|
+
getEditorRoot: () => editorEl ?? null,
|
|
1037
|
+
getScrollHost,
|
|
1038
|
+
getEditorLifetime: () => lifetimeController.signal,
|
|
1039
|
+
stickyColumn,
|
|
1040
|
+
edgeAffinity,
|
|
1041
|
+
blockEdit,
|
|
1042
|
+
controller,
|
|
1043
|
+
history,
|
|
1044
|
+
pluginEditor: pluginEditorLookup,
|
|
1045
|
+
getPresentationMode: () => effectiveMode,
|
|
1046
|
+
linkRef: linkRefView,
|
|
1047
|
+
onCommandError: commandErrorSink,
|
|
1048
|
+
crossBlockCommands,
|
|
1049
|
+
pasteCoordinator,
|
|
1050
|
+
getKeybindingOverrides: () => overridesMap,
|
|
1051
|
+
grammar: registryView.grammar,
|
|
1052
|
+
activePlugins,
|
|
1053
|
+
events,
|
|
1054
|
+
getCursorOffset: () => selectionState.focus?.offset ?? null,
|
|
1055
|
+
afterReactivity: () => tick()
|
|
1056
|
+
});
|
|
1057
|
+
|
|
1058
|
+
// Document-level chords for the windowed-out caret, plus the search shortcuts. Every
|
|
1059
|
+
// arm is contained to THIS instance: the listener sees every editor's keystrokes on
|
|
1060
|
+
// the page, so an unguarded handler let one Ctrl+Z revert two editors.
|
|
1061
|
+
const rootKeydown = createEditorRootKeydown({
|
|
1062
|
+
get searchBarEnabled() {
|
|
1063
|
+
return searchBar;
|
|
1064
|
+
},
|
|
1065
|
+
get mode() {
|
|
1066
|
+
return effectiveMode;
|
|
1067
|
+
},
|
|
1068
|
+
get canReplace() {
|
|
1069
|
+
return canReplace;
|
|
1070
|
+
},
|
|
1071
|
+
get keybindingOverrides() {
|
|
1072
|
+
return overridesMap;
|
|
1073
|
+
},
|
|
1074
|
+
get isCrossBlock() {
|
|
1075
|
+
return selectionState.isCrossBlock;
|
|
1076
|
+
},
|
|
1077
|
+
search: searchState,
|
|
1078
|
+
history,
|
|
1079
|
+
pluginEditor: pluginEditorLookup,
|
|
1080
|
+
activation: activePlugins,
|
|
1081
|
+
onCommandError: commandErrorSink,
|
|
1082
|
+
crossBlock: editorCrossBlock,
|
|
1083
|
+
isHostChrome,
|
|
1084
|
+
saveSearchRange: searchCaret.save,
|
|
1085
|
+
setReplaceExpanded: (expanded) => (replaceExpanded = expanded)
|
|
1086
|
+
});
|
|
1087
|
+
|
|
1088
|
+
$effect(() => {
|
|
1089
|
+
if (!editorEl) return;
|
|
1090
|
+
const root = editorEl;
|
|
1091
|
+
return onRoot(root.ownerDocument, 'keydown', (e: KeyboardEvent) =>
|
|
1092
|
+
rootKeydown.handleKeyDown(e, root)
|
|
1093
|
+
);
|
|
1094
|
+
});
|
|
1095
|
+
|
|
1096
|
+
// ── Editor-root clipboard routing ────────────────────────────────────
|
|
1097
|
+
//
|
|
1098
|
+
// The keydown sibling's counterpart: a Ctrl+C/X/V that Chromium retargeted to <body> because
|
|
1099
|
+
// the selection found no text position to park a caret in. Same containment — the arms claim
|
|
1100
|
+
// only events landing on THIS root, or on the body with this instance holding the chord claim.
|
|
1101
|
+
const rootClipboard = createEditorRootClipboard({
|
|
1102
|
+
selection: selectionState,
|
|
1103
|
+
getDoc,
|
|
1104
|
+
crossBlock: editorCrossBlock,
|
|
1105
|
+
// A live policy value, like the accessor the blocks' policies context hands out.
|
|
1106
|
+
get onPasteImage() {
|
|
1107
|
+
return onPasteImage;
|
|
1108
|
+
},
|
|
1109
|
+
events,
|
|
1110
|
+
getSelectedWidgetBlock: () => {
|
|
1111
|
+
const selected = widgetSelection.getSelected();
|
|
1112
|
+
return selected ? getBlockComponent(selected.paragraphPath) : null;
|
|
1113
|
+
}
|
|
1114
|
+
});
|
|
1115
|
+
|
|
1116
|
+
$effect(() => {
|
|
1117
|
+
if (!editorEl) return;
|
|
1118
|
+
const root = editorEl;
|
|
1119
|
+
const doc = root.ownerDocument;
|
|
1120
|
+
return removeAll(
|
|
1121
|
+
onRoot(doc, 'copy', (e: ClipboardEvent) => rootClipboard.handleCopy(e, root)),
|
|
1122
|
+
onRoot(doc, 'cut', (e: ClipboardEvent) => rootClipboard.handleCut(e, root)),
|
|
1123
|
+
onRoot(doc, 'paste', (e: ClipboardEvent) => rootClipboard.handlePaste(e, root))
|
|
1124
|
+
);
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
// ── Height oracle ───────────────────────────────────────────────────
|
|
1128
|
+
|
|
1129
|
+
// How far the host scaled the type off the size HEIGHT_ESTIMATES were calibrated
|
|
1130
|
+
// at. Plain `let`, not `$state`: the oracle reads it inside `estimate()`, the
|
|
1131
|
+
// feature's hottest path, and `widthVersion` is already the rebuild signal.
|
|
1132
|
+
let typeScale = 1;
|
|
1133
|
+
|
|
1134
|
+
// Only the font-relative terms scale: block chrome is absolute padding and an
|
|
1135
|
+
// image's height is its own. Getters, so a scale change lands without rebuilding
|
|
1136
|
+
// the oracle or dropping its measured heights.
|
|
1137
|
+
const heightOracle = createHeightOracle({
|
|
1138
|
+
get lineHeight() {
|
|
1139
|
+
return HEIGHT_ESTIMATES.proseLineHeight * typeScale;
|
|
1140
|
+
},
|
|
1141
|
+
get codeLineHeight() {
|
|
1142
|
+
return HEIGHT_ESTIMATES.codeLineHeight * typeScale;
|
|
1143
|
+
},
|
|
1144
|
+
get avgCharWidth() {
|
|
1145
|
+
return HEIGHT_ESTIMATES.avgCharWidth * typeScale;
|
|
1146
|
+
},
|
|
1147
|
+
blockChrome: HEIGHT_ESTIMATES.blockChrome,
|
|
1148
|
+
imageBlockMinHeight: HEIGHT_ESTIMATES.imageBlockMinHeight
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
// ── Resize invalidation ─────────────────────────────────────────────
|
|
1152
|
+
|
|
1153
|
+
// A WIDTH change re-wraps prose, staling every cached height, so the scopes rebuild
|
|
1154
|
+
// off this counter; a height-only resize spares the measured cache. ResizeObserver's
|
|
1155
|
+
// per-callback batching is the coalescing — no setTimeout/rAF debounce (G4.4).
|
|
1156
|
+
let widthVersion = $state(0);
|
|
1157
|
+
$effect(() => {
|
|
1158
|
+
const el = editorEl;
|
|
1159
|
+
if (!el) return;
|
|
1160
|
+
let lastWidth = el.clientWidth;
|
|
1161
|
+
const observer = new ResizeObserver(() => {
|
|
1162
|
+
const width = el.clientWidth;
|
|
1163
|
+
if (width === lastWidth) return;
|
|
1164
|
+
lastWidth = width;
|
|
1165
|
+
heightOracle.dropMeasured();
|
|
1166
|
+
widthVersion++;
|
|
1167
|
+
});
|
|
1168
|
+
observer.observe(el);
|
|
1169
|
+
return () => observer.disconnect();
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
// The slice's own axis, watched on the RESOLVED port: the window's extent comes from the
|
|
1173
|
+
// scrollport's height, so a height-only resize otherwise leaves the newly-exposed band as
|
|
1174
|
+
// bare spacer until the next scroll. Its own counter, never `widthVersion` — that one
|
|
1175
|
+
// drops every measured height, which a resize re-wrapping no prose has not earned.
|
|
1176
|
+
let viewportHeightVersion = $state(0);
|
|
1177
|
+
$effect(() => {
|
|
1178
|
+
if (!editorEl) return;
|
|
1179
|
+
const target = getScrollHost();
|
|
1180
|
+
if (!target) return;
|
|
1181
|
+
return installViewportHeightWatcher(target, () => viewportHeightVersion++);
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1184
|
+
// ── Type scale ──────────────────────────────────────────────────────
|
|
1185
|
+
|
|
1186
|
+
// The width change's sibling: a font-size move puts estimates off several-fold, so a
|
|
1187
|
+
// document whose true height clears the activation watermark can fail to window at
|
|
1188
|
+
// all. Only the off-window set depends on the ESTIMATE moving. The width observer
|
|
1189
|
+
// can't see this (no other box in the root resizes), hence the `1em` probe.
|
|
1190
|
+
function applyTypeScale(fontSizePx: number): void {
|
|
1191
|
+
const next = fontSizePx / ESTIMATE_BASE_FONT_SIZE;
|
|
1192
|
+
// Sub-percent moves are sub-pixel on a line box — not worth a full rebuild.
|
|
1193
|
+
if (!(next > 0) || Math.abs(next - typeScale) < 0.01) return;
|
|
1194
|
+
typeScale = next;
|
|
1195
|
+
heightOracle.dropMeasured();
|
|
1196
|
+
widthVersion++;
|
|
1197
|
+
}
|
|
1198
|
+
$effect(() => {
|
|
1199
|
+
const el = typeScaleProbeEl;
|
|
1200
|
+
if (!el) return;
|
|
1201
|
+
applyTypeScale(el.getBoundingClientRect().height);
|
|
1202
|
+
const observer = new ResizeObserver((entries) => {
|
|
1203
|
+
const box = entries[0]?.borderBoxSize?.[0];
|
|
1204
|
+
applyTypeScale(box ? box.blockSize : el.getBoundingClientRect().height);
|
|
1205
|
+
});
|
|
1206
|
+
observer.observe(el);
|
|
1207
|
+
return () => observer.disconnect();
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
// ── Header slot compensation ────────────────────────────────────────
|
|
1211
|
+
|
|
1212
|
+
// The header slot's height lives outside the height model, so while the editor owns the
|
|
1213
|
+
// correction a growing header would slide the document under the reader. Compensating from
|
|
1214
|
+
// the SLOT's own resize is what composes with `correctAnchor` instead of double-correcting;
|
|
1215
|
+
// a reveal already holding the scroll outranks it.
|
|
1216
|
+
$effect(() => {
|
|
1217
|
+
const el = headerEl;
|
|
1218
|
+
if (!el || !editorEl) return;
|
|
1219
|
+
const port = getScrollport();
|
|
1220
|
+
if (!port) return;
|
|
1221
|
+
let lastHeight = el.getBoundingClientRect().height;
|
|
1222
|
+
const observer = new ResizeObserver((entries) => {
|
|
1223
|
+
// Border boxes throughout, seed and fallback alike, so a browser without
|
|
1224
|
+
// `borderBoxSize` computes the same delta.
|
|
1225
|
+
const box = entries[0]?.borderBoxSize?.[0];
|
|
1226
|
+
const height = box ? box.blockSize : el.getBoundingClientRect().height;
|
|
1227
|
+
const delta = height - lastHeight;
|
|
1228
|
+
lastHeight = height;
|
|
1229
|
+
if (delta === 0 || !ownsScrollCorrection() || port.scrollTop() === 0) return;
|
|
1230
|
+
if (!topWindowing.revealHoldsScroll()) port.setScrollTop(port.scrollTop() + delta);
|
|
1231
|
+
});
|
|
1232
|
+
observer.observe(el);
|
|
1233
|
+
return () => observer.disconnect();
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
// ── Focus attribution ───────────────────────────────────────────────
|
|
1237
|
+
|
|
1238
|
+
// Drives each windowing scope's per-level pin, so a scroll that pushes the caret
|
|
1239
|
+
// off-screen never tears down native focus/IME. Plain `let`, not $state: focusout
|
|
1240
|
+
// fires mid-teardown during a structural commit, where a reactive write would trip
|
|
1241
|
+
// state_unsafe_mutation.
|
|
1242
|
+
let focusedPath: number[] | null = null;
|
|
1243
|
+
|
|
1244
|
+
// Marks the block host whose leaf holds the caret; both preview modes' CSS key their
|
|
1245
|
+
// focused-block reveal off it. Imperative for focusedPath's teardown-safety reason,
|
|
1246
|
+
// and mode-gated so source/reading DOM stays byte-identical.
|
|
1247
|
+
let focusedHostEl: HTMLElement | null = null;
|
|
1248
|
+
function applyFocusedAttr(): void {
|
|
1249
|
+
if (focusedHostEl && isPreviewMode(effectiveMode)) {
|
|
1250
|
+
focusedHostEl.setAttribute('data-focused', '');
|
|
1251
|
+
} else {
|
|
1252
|
+
focusedHostEl?.removeAttribute('data-focused');
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
function setFocusedHost(host: HTMLElement | null): void {
|
|
1256
|
+
if (focusedHostEl === host) return;
|
|
1257
|
+
focusedHostEl?.removeAttribute('data-focused');
|
|
1258
|
+
focusedHostEl = host;
|
|
1259
|
+
applyFocusedAttr();
|
|
1260
|
+
}
|
|
1261
|
+
// Entering a preview mode marks the already-focused block, since no re-focus fires.
|
|
1262
|
+
$effect(() => {
|
|
1263
|
+
void effectiveMode;
|
|
1264
|
+
applyFocusedAttr();
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
$effect(() => {
|
|
1268
|
+
if (!editorEl) return;
|
|
1269
|
+
const root = editorEl;
|
|
1270
|
+
const onFocusIn = (e: FocusEvent) => {
|
|
1271
|
+
const host = (e.target as Element | null)?.closest('[data-block-path]');
|
|
1272
|
+
if (!host || !root.contains(host)) {
|
|
1273
|
+
focusedPath = null;
|
|
1274
|
+
setFocusedHost(null);
|
|
1275
|
+
return;
|
|
1276
|
+
}
|
|
1277
|
+
setFocusedHost(host as HTMLElement);
|
|
1278
|
+
const path = readBlockPath(host);
|
|
1279
|
+
focusedPath = path && path.length > 0 ? path : null;
|
|
1280
|
+
// G1.33 at the seam every caret door crosses: a door seats a caret by focusing the
|
|
1281
|
+
// surface, whoever minted it, so a consumer's own door inherits the guard here.
|
|
1282
|
+
const landed = e.target;
|
|
1283
|
+
if (landed instanceof HTMLElement) {
|
|
1284
|
+
assertInvariant('landable-caret', () =>
|
|
1285
|
+
checkLandableCaret(landed, effectiveMode, path ?? [])
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
const onFocusOut = (e: FocusEvent) => {
|
|
1290
|
+
const next = e.relatedTarget as Node | null;
|
|
1291
|
+
if (next && root.contains(next)) return; // moving between blocks — keep the pin
|
|
1292
|
+
focusedPath = null;
|
|
1293
|
+
setFocusedHost(null);
|
|
1294
|
+
};
|
|
1295
|
+
return removeAll(onRoot(root, 'focusin', onFocusIn), onRoot(root, 'focusout', onFocusOut));
|
|
1296
|
+
});
|
|
1297
|
+
// ── Top-level windowing ─────────────────────────────────────────────
|
|
1298
|
+
|
|
1299
|
+
// Assembled here, after the windowing signals it carries exist; the block
|
|
1300
|
+
// components and the windowing hook below both read it back through getContext.
|
|
1301
|
+
setContext(EDITOR_DOC_KEY, {
|
|
1302
|
+
doc: getDoc,
|
|
1303
|
+
contentVersion: contentVersion.read,
|
|
1304
|
+
linkRef: linkRefView,
|
|
1305
|
+
pluginEditor: pluginEditorLookup,
|
|
1306
|
+
lifetime: lifetimeController.signal,
|
|
1307
|
+
editorRoot: () => editorEl ?? null,
|
|
1308
|
+
scrollHost: getScrollHost,
|
|
1309
|
+
scrollport: getScrollport,
|
|
1310
|
+
blockElLookup: getBlockElByPath,
|
|
1311
|
+
focusedPath: () => focusedPath,
|
|
1312
|
+
heightOracle,
|
|
1313
|
+
correctsScroll: ownsScrollCorrection,
|
|
1314
|
+
widthVersion: () => widthVersion,
|
|
1315
|
+
viewportHeightVersion: () => viewportHeightVersion
|
|
1316
|
+
} satisfies EditorDoc);
|
|
1317
|
+
|
|
1318
|
+
// getListEl is the inner .block-list wrapper, never editorEl (== scrollEl): it
|
|
1319
|
+
// scrolls with content, so its top maps root scrollTop into local coordinates,
|
|
1320
|
+
// where editorEl would collapse to 0.
|
|
1321
|
+
const topWindowing = useContainerWindowing({
|
|
1322
|
+
getIndex: () => 0, // ignored — root has no parent sink (reportSelfHeight is undefined)
|
|
1323
|
+
getParentPath: () => [],
|
|
1324
|
+
getChildren: () => doc.children,
|
|
1325
|
+
getChildIds: () => blockIds,
|
|
1326
|
+
getListEl: () => editorEl?.querySelector(':scope > .block-list') ?? null,
|
|
1327
|
+
provideLeafChannel: true
|
|
1328
|
+
});
|
|
1329
|
+
|
|
1330
|
+
// Plain `let`, not $state or $derived: the correction path asks this mid-measure, where
|
|
1331
|
+
// evaluating the window derived would force the layout read the batched pass exists to
|
|
1332
|
+
// avoid (VR-4). Nothing reactive reads it — the root's own attribute reads the derived,
|
|
1333
|
+
// so the flag lags the attribute one flush at a watermark crossing (measured harmless).
|
|
1334
|
+
let rootWindowingActive = false;
|
|
1335
|
+
$effect(() => {
|
|
1336
|
+
rootWindowingActive = topWindowing.window.active;
|
|
1337
|
+
});
|
|
1338
|
+
|
|
1339
|
+
// Who holds the reader's place through a height mutation. Self mode always; host mode only
|
|
1340
|
+
// while windowing runs, since below the budget the host's own native anchoring does it and
|
|
1341
|
+
// both correcting would double-count. The `overflow-anchor` opt-out keys off the same fact.
|
|
1342
|
+
function ownsScrollCorrection(): boolean {
|
|
1343
|
+
return !hostScroll || rootWindowingActive;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
// ── Public API ──────────────────────────────────────────────────────
|
|
1347
|
+
|
|
1348
|
+
export function getSource(): string {
|
|
1349
|
+
return serialize(doc);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
// The kind alone, never the node: a host reads the tree's shape without holding a handle into
|
|
1353
|
+
// it. See `editor-props.ts` for the contract.
|
|
1354
|
+
export function getBlockKindAt(path: number[]): AnyBlockKind | null {
|
|
1355
|
+
return blockNodeAt(doc, path)?.kind ?? null;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/**
|
|
1359
|
+
* A frozen snapshot of the current selection, or null when nothing is focused.
|
|
1360
|
+
* Path arrays are copies, so mutating the result does not affect internal state.
|
|
1361
|
+
*/
|
|
1362
|
+
export function getSelection(): EditorSelection | null {
|
|
1363
|
+
return readCurrentSelection(selectionState, blockRefs);
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
/**
|
|
1367
|
+
* The one restore road: resolve + clamp, reveal, place. `reveal` picks which reveal. The two
|
|
1368
|
+
* scrolling ones differ in whether the pin outlives the call — a navigation holds, a consumer
|
|
1369
|
+
* restore hands the viewport back so a kept pin can't override the host's next scroll — and
|
|
1370
|
+
* `mount` is the history swap's bare mount, which promises no block IN VIEW (overscan keeps
|
|
1371
|
+
* blocks mounted past the fold) and in exchange writes no scrollport.
|
|
1372
|
+
*/
|
|
1373
|
+
function restoreThroughRevealRoad(
|
|
1374
|
+
selection: EditorSelection,
|
|
1375
|
+
reveal: 'hold' | 'release' | 'mount'
|
|
1376
|
+
): Promise<SelectionRestoreOutcome> {
|
|
1377
|
+
return restoreSelection(selection, {
|
|
1378
|
+
getDoc,
|
|
1379
|
+
selectionState,
|
|
1380
|
+
getBlockElByPath,
|
|
1381
|
+
revealTarget: async (path) =>
|
|
1382
|
+
reveal === 'mount'
|
|
1383
|
+
? (await revealPath(path)) !== null
|
|
1384
|
+
: rects.scrollTo(path, { block: 'nearest', hold: reveal === 'hold' })
|
|
1385
|
+
});
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function caretAt(path: number[], offset = 0): EditorSelection {
|
|
1389
|
+
return { anchor: { path, offset }, focus: { path, offset } };
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
/** Land the caret at a raw offset through the shared restore road — the link card's return
|
|
1393
|
+
* door after a commit, so the next keystroke (Ctrl+Z included) addresses the document. */
|
|
1394
|
+
async function landCaretAtOffset(path: number[], offset: number): Promise<boolean> {
|
|
1395
|
+
return (await restoreThroughRevealRoad(caretAt(path, offset), 'hold')) === 'applied';
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/**
|
|
1399
|
+
* Restore a snapshot from {@link getSelection}, sharing the whole restore road with
|
|
1400
|
+
* the undo swap and plugin navigation so the three cannot diverge. True iff the
|
|
1401
|
+
* selection was placed AND its focus block is in view; a later programmatic reveal
|
|
1402
|
+
* mid-settle owns the viewport and makes this false, a user gesture does not.
|
|
1403
|
+
*/
|
|
1404
|
+
export async function setSelection(selection: EditorSelection): Promise<boolean> {
|
|
1405
|
+
return (await restoreThroughRevealRoad(selection, 'release')) === 'applied';
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
// The dead-space click's own landing walk, minus the press/target discrimination a host
|
|
1409
|
+
// caller has already done for itself. See `editor-props.ts` for the contract.
|
|
1410
|
+
export function placeCaretAtPoint(x: number, y: number): boolean {
|
|
1411
|
+
return editorEl ? deadSpaceCaret.placeAtPoint(editorEl, x, y) : false;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* The block path behind `document.activeElement`, for the public doors that address the
|
|
1416
|
+
* focused surface. A gap caret declines: its proxy is not a block, and a NESTED gap's proxy
|
|
1417
|
+
* sits inside its container's host, which must not receive what was aimed at the gap.
|
|
1418
|
+
*/
|
|
1419
|
+
function focusedSurfacePath(): number[] | null {
|
|
1420
|
+
if (selectionState.gapCaret) return null;
|
|
1421
|
+
const active = document.activeElement;
|
|
1422
|
+
if (!(active instanceof HTMLElement) || !editorEl?.contains(active)) return null;
|
|
1423
|
+
return findSurfacePathForElement(active);
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
// Routed to the focused SURFACE, the way a paste event is: everything the pipeline owes
|
|
1427
|
+
// (transforms, delete-first, one undo entry, focus) lives below that seam, not here. See
|
|
1428
|
+
// `editor-props.ts` for the contract.
|
|
1429
|
+
export function insertMarkdown(md: string): boolean {
|
|
1430
|
+
const path = focusedSurfacePath();
|
|
1431
|
+
if (!path) return false;
|
|
1432
|
+
return getBlockComponent(path)?.insertMarkdown?.(md) ?? false;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
const commandDispatchContext: CommandDispatchContext = {
|
|
1436
|
+
history,
|
|
1437
|
+
pluginEditor: pluginEditorLookup,
|
|
1438
|
+
activation: activePlugins,
|
|
1439
|
+
getPresentationMode: () => effectiveMode,
|
|
1440
|
+
isCrossBlockRange: () => selectionState.isCrossBlock,
|
|
1441
|
+
crossBlockCommands: crossBlockCommands
|
|
1442
|
+
};
|
|
1443
|
+
|
|
1444
|
+
// A gap caret focuses a proxy, not a block, so no block-local command has a surface to run
|
|
1445
|
+
// on; global ones still reach the seam, exactly as the gap caret's own chord proxy does.
|
|
1446
|
+
function focusedCommandTarget(): KindCommandTarget | null {
|
|
1447
|
+
const path = focusedSurfacePath();
|
|
1448
|
+
if (!path) return null;
|
|
1449
|
+
const component = getBlockComponent(path);
|
|
1450
|
+
const node = blockNodeAt(doc, path);
|
|
1451
|
+
if (!component?.runCommand || !node) return null;
|
|
1452
|
+
return {
|
|
1453
|
+
kind: node.kind,
|
|
1454
|
+
runCommand: (id, arg) => component.runCommand!(id, arg),
|
|
1455
|
+
isCommandActive: component.isCommandActive
|
|
1456
|
+
? (id) => component.isCommandActive!(id)
|
|
1457
|
+
: undefined
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// The door only resolves the focused surface; every rule (the reading gate, the cross-block
|
|
1462
|
+
// range decline, the arms themselves) lives below the seam. See `editor-props.ts`.
|
|
1463
|
+
export function runCommand(commandId: string): boolean {
|
|
1464
|
+
return runCommandById(
|
|
1465
|
+
commandId as AnyCommandId,
|
|
1466
|
+
undefined,
|
|
1467
|
+
focusedCommandTarget(),
|
|
1468
|
+
commandDispatchContext,
|
|
1469
|
+
commandErrorSink
|
|
1470
|
+
);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// The same seam the door dispatches through, so what a host greys out and what a click declines
|
|
1474
|
+
// cannot drift. See `editor-props.ts` for the contract.
|
|
1475
|
+
export function canRunCommand(commandId: string): boolean {
|
|
1476
|
+
return canRunCommandById(
|
|
1477
|
+
commandId as AnyCommandId,
|
|
1478
|
+
focusedCommandTarget(),
|
|
1479
|
+
commandDispatchContext
|
|
1480
|
+
);
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// State, not admissibility: the focused surface reports its own toggle-state, so a toolbar's
|
|
1484
|
+
// pressed paint reads the same bytes the toggle would rewrite. See `editor-props.ts`.
|
|
1485
|
+
export function isCommandActive(commandId: string): boolean {
|
|
1486
|
+
return isCommandActiveById(
|
|
1487
|
+
commandId as AnyCommandId,
|
|
1488
|
+
focusedCommandTarget(),
|
|
1489
|
+
commandDispatchContext
|
|
1490
|
+
);
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
export function getEvents(): EditorEvents {
|
|
1494
|
+
return events;
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
export function getSearch(): SearchState {
|
|
1498
|
+
return searchState;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
export function getDecorations(): DecorationRegistry {
|
|
1502
|
+
return decorations;
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
export function getRects(): EditorRects {
|
|
1506
|
+
return rects;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
// Recomposed per call rather than derived: the kind and plugin registries are
|
|
1510
|
+
// process-global and mutate outside this component's reactive graph.
|
|
1511
|
+
export function reservedChords(): ReadonlySet<string> {
|
|
1512
|
+
return collectReservedChords({
|
|
1513
|
+
searchBar,
|
|
1514
|
+
keybindings: overridesMap,
|
|
1515
|
+
activation: activePlugins
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
export function claimsChord(event: KeyboardEvent): boolean {
|
|
1520
|
+
return chordIsClaimed(event, reservedChords());
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1523
|
+
// Reads the public snapshot, so it covers single-block carets the cross-block
|
|
1524
|
+
// SelectionState never holds.
|
|
1525
|
+
function selectionSummary(): string {
|
|
1526
|
+
const sel = getSelection();
|
|
1527
|
+
if (!sel) return '(no selection)';
|
|
1528
|
+
const fmt = (p: { path: number[]; offset: number }) => `[${p.path.join(',')}]@${p.offset}`;
|
|
1529
|
+
return `anchor=${fmt(sel.anchor)} focus=${fmt(sel.focus)}`;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
export function getDiagnostics(): EditorDiagnostics {
|
|
1533
|
+
return {
|
|
1534
|
+
enableTrace: enableInteractionTrace,
|
|
1535
|
+
disableTrace: disableInteractionTrace,
|
|
1536
|
+
isTraceEnabled: isInteractionTraceEnabled,
|
|
1537
|
+
traceSnapshot: interactionTraceSnapshot,
|
|
1538
|
+
serializeDiagnostics: (opts) => {
|
|
1539
|
+
const includeSource = opts?.includeSource ?? false;
|
|
1540
|
+
return buildDiagnosticsReport({
|
|
1541
|
+
timestamp: new Date().toISOString(),
|
|
1542
|
+
trace: dumpInteractionTrace(interactionTraceSnapshot()),
|
|
1543
|
+
opsLog: dumpOperationsLog(operationsLog),
|
|
1544
|
+
selection: selectionSummary(),
|
|
1545
|
+
// Serialize only when opted in — the report stays document-free by default.
|
|
1546
|
+
source: includeSource ? getSource() : '',
|
|
1547
|
+
includeSource
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// Compile-time conformance: the published handle can't drift from the exports.
|
|
1554
|
+
void ({
|
|
1555
|
+
getSource,
|
|
1556
|
+
getBlockKindAt,
|
|
1557
|
+
getSelection,
|
|
1558
|
+
setSelection,
|
|
1559
|
+
placeCaretAtPoint,
|
|
1560
|
+
insertMarkdown,
|
|
1561
|
+
runCommand,
|
|
1562
|
+
canRunCommand,
|
|
1563
|
+
isCommandActive,
|
|
1564
|
+
getEvents,
|
|
1565
|
+
getSearch,
|
|
1566
|
+
getDecorations,
|
|
1567
|
+
getRects,
|
|
1568
|
+
getDiagnostics,
|
|
1569
|
+
reservedChords,
|
|
1570
|
+
claimsChord
|
|
1571
|
+
} satisfies EditorInstance);
|
|
1572
|
+
|
|
1573
|
+
// ── Test-only surface ───────────────────────────────────────────────
|
|
1574
|
+
|
|
1575
|
+
function getOperationsLog() {
|
|
1576
|
+
return operationsLog;
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function getUndoStack() {
|
|
1580
|
+
return undoManager.getStacks();
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
/** The live CST Document; treat it as read-only, since mutating it bypasses the
|
|
1584
|
+
* undo pipeline. */
|
|
1585
|
+
function getDocument() {
|
|
1586
|
+
return doc;
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
export const __test = {
|
|
1590
|
+
getDocument,
|
|
1591
|
+
// The swap door's only oracle: every other door is reachable headlessly, but the
|
|
1592
|
+
// `source` prop's reset lives in this component.
|
|
1593
|
+
getContentVersion: contentVersion.read,
|
|
1594
|
+
getBlockComponent,
|
|
1595
|
+
getUndoStack,
|
|
1596
|
+
getOperationsLog,
|
|
1597
|
+
// Specs need the state, not the `data-cross-block` mirror: a deferred $effect
|
|
1598
|
+
// writes the attribute, and an intra-table rectangle keeps one path on both
|
|
1599
|
+
// endpoints, so neither the DOM nor getSelection() answers this reliably.
|
|
1600
|
+
isCrossBlockActive: () => selectionState.isCrossBlock,
|
|
1601
|
+
// The gap caret has no public selection shape and no paint yet, so the state is the
|
|
1602
|
+
// only place arrival can be observed.
|
|
1603
|
+
getGapCaret: () => selectionState.gapCaret,
|
|
1604
|
+
// The engine, not the `addSource`-only public registry: the derived per-path
|
|
1605
|
+
// buckets are the only oracle for a stale bucket, since jsdom measures every
|
|
1606
|
+
// range at zero width and no overlay ever paints there.
|
|
1607
|
+
getDecorationEngine: () => decorationEngine,
|
|
1608
|
+
// The measured-height cache is root-constructed and handed down through context,
|
|
1609
|
+
// so its lifetime against a document swap has no headless seam either.
|
|
1610
|
+
getHeightOracle: () => heightOracle,
|
|
1611
|
+
// The one signal a windowing scope rebuilds off when no id moved, so it is the
|
|
1612
|
+
// oracle for a mode flip having asked for the rebuild it needs.
|
|
1613
|
+
getWidthVersion: () => widthVersion,
|
|
1614
|
+
// Constructs the detached-slot artifact the windowed each-block's cleanup can
|
|
1615
|
+
// leave behind (see `isSlotDetached`); e2e-only.
|
|
1616
|
+
setBlockRefSlot: blockRefSlots.set
|
|
1617
|
+
};
|
|
1618
|
+
</script>
|
|
1619
|
+
|
|
1620
|
+
<!-- tabindex="-1": focusable so a windowed-out block hands focus here rather than to
|
|
1621
|
+
<body>, but not tab-reachable. Non-editable, so focusing it creates no native
|
|
1622
|
+
selection for the selectionchange bridge to collapse. -->
|
|
1623
|
+
<!-- data-presentation is ABSENT in source mode on purpose: the default path's DOM
|
|
1624
|
+
stays byte-identical, and reading-mode CSS keys on the attribute's presence. -->
|
|
1625
|
+
<div
|
|
1626
|
+
class="editor"
|
|
1627
|
+
data-editor-theme={theme}
|
|
1628
|
+
data-scroll-mode={hostScroll ? 'host' : undefined}
|
|
1629
|
+
data-windowing={topWindowing.window.active ? 'active' : undefined}
|
|
1630
|
+
data-presentation={effectiveMode === 'source' ? undefined : effectiveMode}
|
|
1631
|
+
bind:this={editorEl}
|
|
1632
|
+
tabindex="-1"
|
|
1633
|
+
role="group"
|
|
1634
|
+
aria-label={EDITOR_LABEL}
|
|
1635
|
+
>
|
|
1636
|
+
{#if searchBar}
|
|
1637
|
+
<!-- Zero-height sticky anchor, so the bar doesn't scroll away with content. Portaled
|
|
1638
|
+
out, it drops that positioning (the consumer's element is the box) and carries the
|
|
1639
|
+
editor's own theme scope, since custom properties resolve by DOM ancestry. -->
|
|
1640
|
+
<div
|
|
1641
|
+
class:search-anchor={!searchBarAnchor}
|
|
1642
|
+
class:aragonite-editor-theme={!!searchBarAnchor && inThemedScope}
|
|
1643
|
+
data-editor-theme={searchBarAnchor ? theme : undefined}
|
|
1644
|
+
{@attach portalInto(searchBarAnchor)}
|
|
1645
|
+
>
|
|
1646
|
+
<SearchBar
|
|
1647
|
+
replaceExpanded={replaceExpanded && canReplace}
|
|
1648
|
+
onToggleReplace={() => (replaceExpanded = canReplace && !replaceExpanded)}
|
|
1649
|
+
/>
|
|
1650
|
+
</div>
|
|
1651
|
+
{/if}
|
|
1652
|
+
<!-- One `em` tall and out of flow: its box IS the root's computed font size, which
|
|
1653
|
+
no other box reports, and windowing's activation decision needs that scale. -->
|
|
1654
|
+
<div class="type-scale-probe" bind:this={typeScaleProbeEl} aria-hidden="true"></div>
|
|
1655
|
+
{#if header}
|
|
1656
|
+
<!-- A SIBLING of the block list, never a wrapper: the windowing scope resolves
|
|
1657
|
+
its list as a direct child of this root. -->
|
|
1658
|
+
<div class="editor-header" bind:this={headerEl}>{@render header()}</div>
|
|
1659
|
+
{/if}
|
|
1660
|
+
<BlockList
|
|
1661
|
+
children={doc.children}
|
|
1662
|
+
{blockIds}
|
|
1663
|
+
slots={blockRefSlots}
|
|
1664
|
+
parentPath={[]}
|
|
1665
|
+
window={topWindowing.window}
|
|
1666
|
+
reorderable={true}
|
|
1667
|
+
/>
|
|
1668
|
+
<ImageOverlayHost
|
|
1669
|
+
{widgetSelection}
|
|
1670
|
+
{controller}
|
|
1671
|
+
{events}
|
|
1672
|
+
{getDoc}
|
|
1673
|
+
getEditorEl={() => editorEl ?? null}
|
|
1674
|
+
getSelectionIsCustomRendered={() => selectionState.isCustomRendered}
|
|
1675
|
+
getPresentationMode={() => effectiveMode}
|
|
1676
|
+
grammar={registryView.grammar}
|
|
1677
|
+
lifetime={lifetimeController.signal}
|
|
1678
|
+
/>
|
|
1679
|
+
<LinkCardHost
|
|
1680
|
+
card={linkCard}
|
|
1681
|
+
{controller}
|
|
1682
|
+
{events}
|
|
1683
|
+
{getDoc}
|
|
1684
|
+
getEditorEl={() => editorEl ?? null}
|
|
1685
|
+
measureRange={rects.rangeRects}
|
|
1686
|
+
landCaret={landCaretAtOffset}
|
|
1687
|
+
{activateLink}
|
|
1688
|
+
resolveLinkUrl={resolveLinkUrlImpl}
|
|
1689
|
+
caretRestore={linkCardCaret}
|
|
1690
|
+
linkRef={linkRefView}
|
|
1691
|
+
grammar={registryView.grammar}
|
|
1692
|
+
/>
|
|
1693
|
+
<div class="editor-sr-live" role="status" aria-live="polite">{selectionDescription}</div>
|
|
1694
|
+
<div class="editor-sr-live-reorder" role="status" aria-live="polite">{reorderAnnouncement}</div>
|
|
1695
|
+
{#if reorderLine}
|
|
1696
|
+
<div
|
|
1697
|
+
class="reorder-line"
|
|
1698
|
+
style="left:{reorderLine.left}px;top:{reorderLine.top}px;width:{reorderLine.width}px"
|
|
1699
|
+
></div>
|
|
1700
|
+
{/if}
|
|
1701
|
+
{#if reorderGhost}
|
|
1702
|
+
<div class="reorder-ghost" style="left:{reorderGhost.clientX}px;top:{reorderGhost.clientY}px">
|
|
1703
|
+
{reorderGhost.label}
|
|
1704
|
+
</div>
|
|
1705
|
+
{/if}
|
|
1706
|
+
</div>
|
|
1707
|
+
|
|
1708
|
+
<style>
|
|
1709
|
+
.editor {
|
|
1710
|
+
width: 100%;
|
|
1711
|
+
flex: 1;
|
|
1712
|
+
padding: 1rem;
|
|
1713
|
+
font-family: var(--font-editor, ui-monospace, monospace);
|
|
1714
|
+
/* The type-scale root: every construct sizes in `em` off this, so one
|
|
1715
|
+
declaration scales the whole surface. */
|
|
1716
|
+
font-size: var(--editor-font-size, 1rem);
|
|
1717
|
+
line-height: 1.6;
|
|
1718
|
+
color: var(--color-text-primary, #ffffff);
|
|
1719
|
+
min-height: 200px;
|
|
1720
|
+
overflow-y: auto;
|
|
1721
|
+
/* The editor corrects the scroll anchor by hand (list-windowing's
|
|
1722
|
+
correctAnchor); native anchoring independently rewrites scrollTop and
|
|
1723
|
+
double-corrects. Do NOT restore `overflow-anchor` (VR-2). */
|
|
1724
|
+
overflow-anchor: none;
|
|
1725
|
+
scrollbar-width: thin;
|
|
1726
|
+
scrollbar-color: var(--color-ui-muted, #a4a4a4) transparent;
|
|
1727
|
+
border: 1px solid var(--color-ui-muted, #a4a4a4);
|
|
1728
|
+
border-radius: 4px;
|
|
1729
|
+
/* Containing block for the image overlay portal. */
|
|
1730
|
+
position: relative;
|
|
1731
|
+
}
|
|
1732
|
+
|
|
1733
|
+
/* Embedded flow mode: an ancestor owns the scroll, so the root drops its scrollport and the
|
|
1734
|
+
standalone-widget chrome that would box every entry of a journal. Below the windowing
|
|
1735
|
+
budget nothing corrects by hand, so native anchoring is restored — `none` would strip the
|
|
1736
|
+
subtree from the HOST's anchor candidates and hold nothing in its place. */
|
|
1737
|
+
.editor[data-scroll-mode='host'] {
|
|
1738
|
+
overflow-y: visible;
|
|
1739
|
+
overflow-anchor: auto;
|
|
1740
|
+
min-height: 0;
|
|
1741
|
+
flex: none;
|
|
1742
|
+
border: none;
|
|
1743
|
+
padding: 0;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
/* The stated trade of windowing under host scroll: native anchoring and the manual
|
|
1747
|
+
correction cannot coexist, so an active editor withdraws its own subtree from the host's
|
|
1748
|
+
anchor candidates and holds the line itself (VR-2). The host's scroller is untouched. */
|
|
1749
|
+
.editor[data-scroll-mode='host'][data-windowing='active'] {
|
|
1750
|
+
overflow-anchor: none;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
/* Absolute and zero-width so it takes no part in layout; never `display: none`,
|
|
1754
|
+
which stops a ResizeObserver reporting. */
|
|
1755
|
+
.type-scale-probe {
|
|
1756
|
+
position: absolute;
|
|
1757
|
+
top: 0;
|
|
1758
|
+
left: 0;
|
|
1759
|
+
width: 0;
|
|
1760
|
+
height: 1em;
|
|
1761
|
+
visibility: hidden;
|
|
1762
|
+
pointer-events: none;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
/* Sticks to the scrollport top reserving no space; the bar positions absolutely
|
|
1766
|
+
against it and stays put as the editor scrolls. */
|
|
1767
|
+
.search-anchor {
|
|
1768
|
+
position: sticky;
|
|
1769
|
+
top: 0;
|
|
1770
|
+
height: 0;
|
|
1771
|
+
z-index: 5;
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
/* Sticky resolves against the nearest SCROLLPORT, which in flow mode is the host's,
|
|
1775
|
+
floating the bar over unrelated page content; absolute re-homes it to the root. */
|
|
1776
|
+
.editor[data-scroll-mode='host'] .search-anchor {
|
|
1777
|
+
position: absolute;
|
|
1778
|
+
top: 0;
|
|
1779
|
+
left: 0;
|
|
1780
|
+
right: 0;
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
.editor::-webkit-scrollbar {
|
|
1784
|
+
width: 6px;
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
.editor::-webkit-scrollbar-track {
|
|
1788
|
+
background: transparent;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
.editor::-webkit-scrollbar-thumb {
|
|
1792
|
+
background: var(--color-ui-muted, #a4a4a4);
|
|
1793
|
+
border-radius: 3px;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
.editor::-webkit-scrollbar-thumb:hover {
|
|
1797
|
+
background: var(--color-ui-dulled, #afb1b3);
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
.editor-sr-live,
|
|
1801
|
+
.editor-sr-live-reorder {
|
|
1802
|
+
position: absolute;
|
|
1803
|
+
width: 1px;
|
|
1804
|
+
height: 1px;
|
|
1805
|
+
padding: 0;
|
|
1806
|
+
margin: -1px;
|
|
1807
|
+
overflow: hidden;
|
|
1808
|
+
clip: rect(0 0 0 0);
|
|
1809
|
+
white-space: nowrap;
|
|
1810
|
+
border: 0;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
/* A nested drag reorders only within its container, so the container takes a faint
|
|
1814
|
+
transient wash — deliberately not an outline (a document should feel like a
|
|
1815
|
+
document, not a pile of blocks). Applied by editor-actions/reorder-drag.ts. */
|
|
1816
|
+
:global(.reorder-scope) {
|
|
1817
|
+
background: var(--reorder-scope-bg, rgba(100, 150, 255, 0.14));
|
|
1818
|
+
border-radius: 4px;
|
|
1819
|
+
transition: background-color 0.12s ease;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
/* Viewport-fixed, since the rects come from client coords; pointer-events:none so
|
|
1823
|
+
they never intercept the drag's own pointer stream. */
|
|
1824
|
+
.reorder-line {
|
|
1825
|
+
position: fixed;
|
|
1826
|
+
height: 2px;
|
|
1827
|
+
background: var(--md-reorder-indicator);
|
|
1828
|
+
border-radius: 2px;
|
|
1829
|
+
pointer-events: none;
|
|
1830
|
+
z-index: 20;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
.reorder-ghost {
|
|
1834
|
+
position: fixed;
|
|
1835
|
+
transform: translate(0.75rem, 0.5rem);
|
|
1836
|
+
max-width: 16rem;
|
|
1837
|
+
padding: 0.15rem 0.5rem;
|
|
1838
|
+
font-size: 0.85em;
|
|
1839
|
+
white-space: nowrap;
|
|
1840
|
+
overflow: hidden;
|
|
1841
|
+
text-overflow: ellipsis;
|
|
1842
|
+
background: var(--md-reorder-indicator);
|
|
1843
|
+
color: #fff;
|
|
1844
|
+
border-radius: 4px;
|
|
1845
|
+
opacity: 0.9;
|
|
1846
|
+
pointer-events: none;
|
|
1847
|
+
z-index: 21;
|
|
1848
|
+
}
|
|
1849
|
+
</style>
|