@inkami/blx 0.17.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +98 -0
  2. package/dist/attributed-string/attributed-string.d.ts +37 -0
  3. package/dist/attributed-string/index.d.ts +2 -0
  4. package/dist/attributed-string/types.d.ts +13 -0
  5. package/dist/autocomplete/autocomplete-menu.d.ts +24 -0
  6. package/dist/autocomplete/index.d.ts +2 -0
  7. package/dist/autocomplete/types.d.ts +29 -0
  8. package/dist/blob-store/index.d.ts +32 -0
  9. package/dist/blob-store/types.d.ts +24 -0
  10. package/dist/block/block-controller.d.ts +205 -0
  11. package/dist/block/index.d.ts +2 -0
  12. package/dist/block/types.d.ts +15 -0
  13. package/dist/block-action-menu/block-action-menu.d.ts +62 -0
  14. package/dist/block-action-menu/index.d.ts +2 -0
  15. package/dist/block-context-menu/block-context-menu.d.ts +33 -0
  16. package/dist/block-context-menu/index.d.ts +2 -0
  17. package/dist/blx.js +16245 -0
  18. package/dist/blx.js.map +1 -0
  19. package/dist/caret/custom-caret.d.ts +30 -0
  20. package/dist/clipboard/clipboard-manager.d.ts +28 -0
  21. package/dist/clipboard/index.d.ts +2 -0
  22. package/dist/clipboard/paste-parser.d.ts +30 -0
  23. package/dist/commands/command-registry.d.ts +26 -0
  24. package/dist/commands/index.d.ts +2 -0
  25. package/dist/crdt/automerge-sync.d.ts +157 -0
  26. package/dist/crdt/crdt-log.d.ts +57 -0
  27. package/dist/crdt/index.d.ts +8 -0
  28. package/dist/crdt/remote-cursor-renderer.d.ts +28 -0
  29. package/dist/crdt/replay-ui.d.ts +34 -0
  30. package/dist/crdt/types.d.ts +47 -0
  31. package/dist/date-picker/agenda-utils.d.ts +37 -0
  32. package/dist/date-picker/date-parser.d.ts +25 -0
  33. package/dist/date-picker/date-picker.d.ts +115 -0
  34. package/dist/date-picker/index.d.ts +6 -0
  35. package/dist/drag-handle/drag-handle.d.ts +105 -0
  36. package/dist/drag-handle/index.d.ts +1 -0
  37. package/dist/editor.d.ts +864 -0
  38. package/dist/emoji-picker/emoji-data.d.ts +22 -0
  39. package/dist/emoji-picker/emoji-picker.d.ts +43 -0
  40. package/dist/emoji-picker/index.d.ts +3 -0
  41. package/dist/event-bus/event-bus.d.ts +14 -0
  42. package/dist/event-bus/index.d.ts +2 -0
  43. package/dist/event-bus/types.d.ts +320 -0
  44. package/dist/fold/fold-manager.d.ts +27 -0
  45. package/dist/fold/index.d.ts +2 -0
  46. package/dist/grapheme.d.ts +28 -0
  47. package/dist/index.d.ts +62 -0
  48. package/dist/inline-toolbar/highlight-picker.d.ts +41 -0
  49. package/dist/inline-toolbar/index.d.ts +5 -0
  50. package/dist/inline-toolbar/inline-toolbar.d.ts +97 -0
  51. package/dist/inline-toolbar/link-popover.d.ts +24 -0
  52. package/dist/keybindings/defaults.d.ts +2 -0
  53. package/dist/keybindings/index.d.ts +4 -0
  54. package/dist/keybindings/key-matcher.d.ts +41 -0
  55. package/dist/keybindings/types.d.ts +6 -0
  56. package/dist/keybindings/vim.d.ts +2 -0
  57. package/dist/markdown/block-shortcuts.d.ts +35 -0
  58. package/dist/markdown/emoticon-substitutions.d.ts +22 -0
  59. package/dist/markdown/export.d.ts +5 -0
  60. package/dist/markdown/index.d.ts +4 -0
  61. package/dist/markdown/inline-shortcuts.d.ts +13 -0
  62. package/dist/markdown/types.d.ts +29 -0
  63. package/dist/plugins/blockquote.d.ts +6 -0
  64. package/dist/plugins/checklist.d.ts +18 -0
  65. package/dist/plugins/code.d.ts +50 -0
  66. package/dist/plugins/excalidraw.d.ts +26 -0
  67. package/dist/plugins/heading.d.ts +20 -0
  68. package/dist/plugins/horizontal-rule.d.ts +5 -0
  69. package/dist/plugins/image.d.ts +19 -0
  70. package/dist/plugins/index.d.ts +28 -0
  71. package/dist/plugins/list.d.ts +18 -0
  72. package/dist/plugins/mermaid.d.ts +11 -0
  73. package/dist/plugins/paragraph.d.ts +18 -0
  74. package/dist/plugins/prism-loader.d.ts +16 -0
  75. package/dist/plugins/render-utils.d.ts +43 -0
  76. package/dist/plugins/table.d.ts +22 -0
  77. package/dist/plugins/youtube.d.ts +21 -0
  78. package/dist/schema/index.d.ts +2 -0
  79. package/dist/schema/schema-registry.d.ts +35 -0
  80. package/dist/schema/types.d.ts +67 -0
  81. package/dist/search/index.d.ts +4 -0
  82. package/dist/search/search-engine.d.ts +39 -0
  83. package/dist/search/search-ui.d.ts +59 -0
  84. package/dist/selection/index.d.ts +3 -0
  85. package/dist/selection/selection-manager.d.ts +41 -0
  86. package/dist/selection/types.d.ts +20 -0
  87. package/dist/slash-menu/index.d.ts +2 -0
  88. package/dist/slash-menu/slash-menu.d.ts +27 -0
  89. package/dist/table-of-contents/index.d.ts +1 -0
  90. package/dist/table-of-contents/table-of-contents.d.ts +74 -0
  91. package/dist/transaction/index.d.ts +2 -0
  92. package/dist/transaction/transaction.d.ts +29 -0
  93. package/dist/transaction/types.d.ts +40 -0
  94. package/dist/types.d.ts +21 -0
  95. package/dist/ui/context-menu.d.ts +103 -0
  96. package/dist/ui/dismiss.d.ts +33 -0
  97. package/dist/ui/dropdown.d.ts +81 -0
  98. package/dist/ui/floating-scroll-manager.d.ts +50 -0
  99. package/dist/ui/index.d.ts +18 -0
  100. package/dist/ui/modal.d.ts +109 -0
  101. package/dist/ui/popover.d.ts +59 -0
  102. package/dist/ui/position-fixed.d.ts +35 -0
  103. package/dist/ui/toast.d.ts +60 -0
  104. package/dist/ui/tooltip.d.ts +64 -0
  105. package/dist/undo/index.d.ts +5 -0
  106. package/dist/undo/jump-list.d.ts +37 -0
  107. package/dist/undo/types.d.ts +49 -0
  108. package/dist/undo/undo-manager.d.ts +47 -0
  109. package/dist/util/icons.d.ts +13 -0
  110. package/dist/util/id.d.ts +10 -0
  111. package/dist/viewport/block-viewport.d.ts +106 -0
  112. package/dist/viewport/index.d.ts +2 -0
  113. package/package.json +66 -0
  114. package/style.css +4670 -0
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # @inkami/blx
2
+
3
+ **A block-based editor for the web.** Notion-style editing — rich text, media, tables, nesting — in a single TypeScript library with zero framework dependencies.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @inkami/blx
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```ts
14
+ import { Editor, AttributedString } from "@inkami/blx";
15
+ import "@inkami/blx/style.css";
16
+
17
+ const editor = new Editor(document.getElementById("editor")!, {
18
+ initialBlocks: [
19
+ {
20
+ id: "1",
21
+ type: "paragraph",
22
+ content: AttributedString.fromPlain("Hello, world!"),
23
+ metadata: {},
24
+ },
25
+ ],
26
+ });
27
+ ```
28
+
29
+ ## Features
30
+
31
+ | | |
32
+ |---|---|
33
+ | **Rich text** | Bold, italic, underline, strikethrough, inline code, highlights (6 colors) |
34
+ | **Block types** | Paragraphs, headings (1-6), ordered/unordered lists, task lists, code (syntax highlighted), images, YouTube embeds, tables, horizontal rules |
35
+ | **Links** | Inline links with `[markdown](syntax)`, Cmd+K popover, `[[` internal block references with autocomplete |
36
+ | **Editing** | Slash menu, inline toolbar, block action menu, drag handles, multi-block selection |
37
+ | **Search** | Cmd+F with regex, case-sensitive toggle, find & replace |
38
+ | **Undo/Redo** | Transaction-based history with smart grouping |
39
+ | **Nesting** | Tab/Shift+Tab for lists and task lists, 4 indent levels |
40
+ | **Theming** | Light & dark mode via CSS custom properties |
41
+ | **Extensible** | Custom block types, toolbar buttons, block actions, slash menu items, autocomplete providers |
42
+
43
+ ## Custom Block Types
44
+
45
+ ```ts
46
+ import type { BlockTypePlugin } from "@inkami/blx";
47
+
48
+ const calloutPlugin: BlockTypePlugin = {
49
+ type: "callout",
50
+ label: "Callout",
51
+ hasTextContent: true,
52
+ defaultContent: () => AttributedString.empty(),
53
+ defaultMetadata: () => ({ emoji: "💡" }),
54
+ render(container, content, metadata) {
55
+ // Build your DOM from the model
56
+ },
57
+ };
58
+
59
+ editor.registerBlockType(calloutPlugin);
60
+ ```
61
+
62
+ ## API
63
+
64
+ ### Editor
65
+
66
+ | Method | Description |
67
+ |---|---|
68
+ | `new Editor(root, options?)` | Create editor |
69
+ | `addBlock(typeOrBlock, index?)` | Add a block |
70
+ | `removeBlock(id)` | Remove a block |
71
+ | `moveBlock(id, toIndex)` | Reorder a block |
72
+ | `changeBlockType(id, newType)` | Convert block type |
73
+ | `focusBlock(id, offset?)` | Focus a block |
74
+ | `registerBlockType(plugin)` | Register a custom block type |
75
+ | `addToolbarButton(config)` | Add inline toolbar button |
76
+ | `addBlockActionItem(config)` | Add block action menu item |
77
+ | `addSlashMenuItem(config)` | Add slash menu entry |
78
+ | `addAutoCompleteProvider(provider)` | Add `[[` autocomplete provider |
79
+ | `toJSON()` / `Editor.fromJSON()` | Serialize / restore |
80
+ | `destroy()` | Clean up |
81
+
82
+ ### AttributedString
83
+
84
+ | Method | Description |
85
+ |---|---|
86
+ | `AttributedString.empty()` | Create empty string |
87
+ | `AttributedString.fromPlain(text, attrs?)` | Create from plain text |
88
+ | `.insertAt(offset, text, attrs?)` | Insert text |
89
+ | `.deleteRange(start, end)` | Delete range |
90
+ | `.applyAttributes(start, end, attrs)` | Apply formatting |
91
+ | `.removeAttributes(start, end, keys)` | Remove formatting |
92
+ | `.slice(start, end)` | Extract substring |
93
+ | `.concat(other)` | Concatenate |
94
+ | `.toJSON()` / `AttributedString.fromJSON()` | Serialize / restore |
95
+
96
+ ## License
97
+
98
+ MIT
@@ -0,0 +1,37 @@
1
+ import type { Attributes, AttributedStringJSON, Span } from "./types";
2
+ /**
3
+ * Immutable attributed string — every mutation returns a new instance.
4
+ * Internally stores an array of non-overlapping, ordered Spans that cover
5
+ * the full string. Adjacent spans with identical attributes are always merged.
6
+ */
7
+ export declare class AttributedString {
8
+ readonly spans: ReadonlyArray<Span>;
9
+ private constructor();
10
+ static empty(): AttributedString;
11
+ static fromPlain(text: string, attrs?: Attributes): AttributedString;
12
+ static fromSpans(spans: ReadonlyArray<Span>): AttributedString;
13
+ static fromJSON(json: AttributedStringJSON): AttributedString;
14
+ get length(): number;
15
+ get text(): string;
16
+ isEmpty(): boolean;
17
+ /**
18
+ * Get attributes at a character offset.
19
+ *
20
+ * @param side - Which side's attributes to prefer at a boundary:
21
+ * - `'right'` (default): return the attributes of the span that starts at
22
+ * or contains the offset — this is the classic behavior.
23
+ * - `'left'`: return the attributes of the span to the *left* of the
24
+ * cursor. At position 0 this means "nothing to the left" → `{}`.
25
+ * Use `'left'` for text insertion so that typing before a formatted
26
+ * span does not inherit its formatting.
27
+ */
28
+ attributesAt(offset: number, side?: "left" | "right"): Attributes;
29
+ insertAt(offset: number, text: string, attrs?: Attributes): AttributedString;
30
+ deleteRange(start: number, end: number): AttributedString;
31
+ applyAttributes(start: number, end: number, attrs: Attributes): AttributedString;
32
+ removeAttributes(start: number, end: number, keys: string[]): AttributedString;
33
+ slice(start: number, end: number): AttributedString;
34
+ concat(other: AttributedString): AttributedString;
35
+ toJSON(): AttributedStringJSON;
36
+ equals(other: AttributedString): boolean;
37
+ }
@@ -0,0 +1,2 @@
1
+ export { AttributedString } from "./attributed-string";
2
+ export type { Attributes, AttributeValue, Span, AttributedStringJSON } from "./types";
@@ -0,0 +1,13 @@
1
+ /** Attribute values can be strings, numbers, or booleans */
2
+ export type AttributeValue = string | number | boolean;
3
+ /** A dictionary of formatting attributes */
4
+ export type Attributes = Record<string, AttributeValue>;
5
+ /** A span of text with uniform attributes */
6
+ export interface Span {
7
+ text: string;
8
+ attributes: Attributes;
9
+ }
10
+ /** JSON-serializable representation of an AttributedString */
11
+ export interface AttributedStringJSON {
12
+ spans: ReadonlyArray<Span>;
13
+ }
@@ -0,0 +1,24 @@
1
+ import type { AutoCompleteEntry } from "./types";
2
+ /**
3
+ * Floating menu showing autocomplete results.
4
+ * Triggered by `[[` in the editor; wraps ContextMenu.
5
+ */
6
+ export declare class AutoCompleteMenu {
7
+ private menu;
8
+ private items;
9
+ private onSelectCb;
10
+ constructor(onSelect: (entry: AutoCompleteEntry) => void, onClose: () => void);
11
+ get element(): HTMLElement;
12
+ get visible(): boolean;
13
+ get hasItems(): boolean;
14
+ show(anchorRect: DOMRect): void;
15
+ /** Reposition the menu relative to a new anchor rect (e.g. caret moved). */
16
+ reposition(anchorRect: DOMRect): void;
17
+ hide(): void;
18
+ /** Update displayed results. Resets selection to first item. */
19
+ updateResults(entries: AutoCompleteEntry[]): void;
20
+ /** Handle keyboard events. Returns true if the event was consumed. */
21
+ handleKeyDown(e: KeyboardEvent): boolean;
22
+ destroy(): void;
23
+ private toMenuItems;
24
+ }
@@ -0,0 +1,2 @@
1
+ export { AutoCompleteMenu } from "./autocomplete-menu";
2
+ export type { AutoCompleteEntry, AutoCompleteProvider } from "./types";
@@ -0,0 +1,29 @@
1
+ import type { Attributes } from "../attributed-string/types";
2
+ /** A single entry in the autocomplete results. */
3
+ export interface AutoCompleteEntry {
4
+ /** Unique identifier (e.g. block ID, external doc ID). */
5
+ id: string;
6
+ /** Display text shown in the menu. */
7
+ label: string;
8
+ /** Optional secondary description. */
9
+ description?: string;
10
+ /** Optional icon (emoji or HTML string). */
11
+ icon?: string;
12
+ /** Text to insert into the document when selected. Defaults to `label`. */
13
+ insertText?: string;
14
+ /** Attributes to apply on the inserted text (e.g. `{ blockRef: id }`). */
15
+ attributes?: Attributes;
16
+ }
17
+ /**
18
+ * Provider that supplies autocomplete entries.
19
+ * Multiple providers can be registered; results are merged in registration order.
20
+ */
21
+ export interface AutoCompleteProvider {
22
+ /** Unique identifier for this provider. */
23
+ id: string;
24
+ /**
25
+ * Search for entries matching the query.
26
+ * Called on every keystroke while the `[[` menu is open.
27
+ */
28
+ search(query: string): AutoCompleteEntry[] | Promise<AutoCompleteEntry[]>;
29
+ }
@@ -0,0 +1,32 @@
1
+ export type { BlobStore } from "./types";
2
+ import type { BlobStore } from "./types";
3
+ /** Register a blob store for the editor to use for image storage. */
4
+ export declare function setBlobStore(store: BlobStore | null): void;
5
+ /** Get the currently registered blob store (if any). */
6
+ export declare function getBlobStore(): BlobStore | null;
7
+ /** Check whether a `src` value is a blob-store reference. */
8
+ export declare function isBlobRef(src: unknown): boolean;
9
+ /** Build a blob reference string from a SHA-256 hex digest. */
10
+ export declare function makeBlobRef(sha256Hex: string): string;
11
+ /** Extract the hash portion from a blob reference string. */
12
+ export declare function blobRefHash(ref: string): string;
13
+ /**
14
+ * Resolve a `src` string to a displayable URL.
15
+ *
16
+ * - Regular URLs / data URIs are returned as-is.
17
+ * - Blob references (`blxblob:…`) are resolved through the registered
18
+ * blob store. Returns `null` if the store is missing or the key is
19
+ * not found.
20
+ */
21
+ export declare function resolveBlobSrc(src: unknown): Promise<string | null>;
22
+ /**
23
+ * Store a file's contents in the blob store (if one is registered) and
24
+ * return the blob reference key. Falls back to a base64 data URI when
25
+ * no blob store is available.
26
+ */
27
+ export declare function storeFileAsBlob(file: File): Promise<string>;
28
+ /**
29
+ * Compute the SHA-256 hex digest of an ArrayBuffer.
30
+ * Uses the Web Crypto API (available in browsers and Node 18+).
31
+ */
32
+ export declare function sha256Hex(data: ArrayBuffer): Promise<string>;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Interface for content-addressed binary blob storage.
3
+ *
4
+ * Implementations store image data (and other large binaries) outside of the
5
+ * CRDT document, keeping only a small hash reference in block metadata.
6
+ */
7
+ export interface BlobStore {
8
+ /**
9
+ * Store binary data and return a content-addressed key.
10
+ * Calling `put` with identical data must return the same key
11
+ * (content-addressed / idempotent).
12
+ */
13
+ put(data: ArrayBuffer): Promise<string>;
14
+ /**
15
+ * Retrieve a displayable URL for the given blob key.
16
+ * Returns `null` if the key is not found in the store.
17
+ *
18
+ * Implementations may return an object URL (`URL.createObjectURL`),
19
+ * a data URI, or a remote URL — the caller treats it as opaque.
20
+ */
21
+ getUrl(key: string): Promise<string | null>;
22
+ /** Check whether a blob exists in the store. */
23
+ has(key: string): Promise<boolean>;
24
+ }
@@ -0,0 +1,205 @@
1
+ import type { Block } from "../types";
2
+ import type { BlockView } from "./types";
3
+ import type { EventBus } from "../event-bus";
4
+ import type { EditorEventMap } from "../event-bus";
5
+ import type { BlockTypePlugin } from "../schema";
6
+ import { Transaction } from "../transaction";
7
+ /**
8
+ * Per-block controller that bridges model and DOM.
9
+ *
10
+ * Strategy:
11
+ * - `beforeinput` → preventDefault → create Transaction → apply → re-render
12
+ * - MutationObserver as fallback for IME / spell-check
13
+ * - Model is always source of truth
14
+ */
15
+ export declare class BlockController implements BlockView {
16
+ readonly blockId: string;
17
+ readonly container: HTMLElement;
18
+ private _contentEditable;
19
+ private _mounted;
20
+ private _contentRendered;
21
+ private _listenersAttached;
22
+ private block;
23
+ private plugin;
24
+ private bus;
25
+ private observer;
26
+ private suppressObserver;
27
+ /** One-shot attribute override for the next insertText, set after inline shortcuts */
28
+ private nextInsertAttrs;
29
+ /**
30
+ * Persistent attribute overrides set by format shortcuts (Ctrl-B/I/U) on a
31
+ * collapsed cursor. Unlike nextInsertAttrs, these persist across multiple
32
+ * character inserts until the cursor moves or a non-insert action occurs.
33
+ * Keys are attribute names, values indicate desired state (true = add, false = remove).
34
+ */
35
+ private _pendingMarks;
36
+ /** Flag to suppress clearing pendingMarks on the next selectionchange event */
37
+ private _suppressPendingMarksClear;
38
+ /** Offset of the opening ':' for emoji picker, or null when inactive */
39
+ private emojiColonOffset;
40
+ /** Offset of the opening '/' for slash command menu, or null when inactive */
41
+ private slashCommandOffset;
42
+ /** Offset of a pending first '[' for autocomplete trigger */
43
+ private pendingBracketOffset;
44
+ /** Offset of the opening '[[' for autocomplete menu, or null when inactive */
45
+ private autoCompleteOffset;
46
+ /** Offset of the '@' that triggered the agenda picker, or null when inactive */
47
+ private agendaAtOffset;
48
+ /** Detected role prefix from inline syntax like @due( or @scheduled( */
49
+ private agendaRole;
50
+ /** Length of the prefix portion (e.g., "due(" = 4) that has been typed, used to strip from query */
51
+ private agendaPrefixLen;
52
+ constructor(block: Block, plugin: BlockTypePlugin, bus: EventBus<EditorEventMap>);
53
+ get contentEditable(): HTMLElement | null;
54
+ get mounted(): boolean;
55
+ /** Whether block content is currently rendered into the container */
56
+ get contentRendered(): boolean;
57
+ /** Mount this block — render into container and attach event listeners */
58
+ mount(parent: HTMLElement, before?: HTMLElement | null): void;
59
+ /**
60
+ * Insert the container element into the DOM without rendering content.
61
+ * Used by the viewport system to place all block containers for scroll
62
+ * position stability while deferring expensive content rendering.
63
+ */
64
+ mountContainer(parent: HTMLElement | DocumentFragment, before?: HTMLElement | null): void;
65
+ /**
66
+ * Render content into an already-mounted container and attach event
67
+ * listeners. If the block was previously ejected (content hidden but
68
+ * still in the DOM), restores it without re-rendering.
69
+ */
70
+ mountContent(): void;
71
+ /**
72
+ * Eject block content: detach listeners and hide rendering via CSS.
73
+ * The DOM stays intact so the container height is perfectly preserved —
74
+ * no layout shift, no scroll compensation needed.
75
+ */
76
+ ejectContent(preReadHeight?: number): void;
77
+ /**
78
+ * Restore a previously ejected block: re-attach listeners and show content.
79
+ * Since the DOM was never cleared, this is a zero-cost operation with no
80
+ * layout shift.
81
+ */
82
+ restoreContent(): void;
83
+ /**
84
+ * Remove rendered content and event listeners but keep the container
85
+ * in the DOM. Saves the current height as minHeight for scroll stability.
86
+ * The block data model is preserved — only the DOM representation is removed.
87
+ *
88
+ * Used for full cleanup (block deletion, deep eviction). For normal
89
+ * viewport cycling, prefer ejectContent/restoreContent which preserve
90
+ * the DOM and avoid layout shifts.
91
+ *
92
+ * @param preReadHeight — If provided, skip the offsetHeight read and use
93
+ * this value instead. This avoids forced layout when the caller has
94
+ * already batched height reads (e.g. BlockViewport.handleIntersection).
95
+ */
96
+ unmountContent(preReadHeight?: number): void;
97
+ /** Apply a transaction — updates the model and re-renders */
98
+ applyTransaction(tx: Transaction): void;
99
+ /** Get current block data (read-only snapshot) */
100
+ getBlock(): Block;
101
+ /** Update the block data directly (used by editor for split/merge/type-change) */
102
+ updateBlock(updates: Partial<Pick<Block, "content" | "metadata" | "type">>): void;
103
+ /** Re-render with a new plugin (after type change) */
104
+ setPlugin(plugin: BlockTypePlugin): void;
105
+ /**
106
+ * Re-render block content with cursor preservation.
107
+ * Used by remote sync (AutomergeSync) to update content without losing
108
+ * the user's cursor position or triggering spurious MutationObserver events.
109
+ */
110
+ rerender(): void;
111
+ /** Ensure the contenteditable is visible (e.g. mermaid blocks hide their editor). */
112
+ private ensureEditable;
113
+ focus(offset?: number): void;
114
+ /** Returns the bounding rect of the current caret, or null if layout unavailable (jsdom). */
115
+ getCaretRect(): DOMRect | null;
116
+ /** Is the caret on the first visual line of this block? Returns true when layout is unavailable. */
117
+ isCaretOnFirstLine(): boolean;
118
+ /** Is the caret on the last visual line of this block? Returns true when layout is unavailable. */
119
+ isCaretOnLastLine(): boolean;
120
+ /** Focus this block and position cursor at horizontal X on the first or last line. */
121
+ focusAtX(x: number, line: "first" | "last"): void;
122
+ /**
123
+ * Focus this block and position cursor at the character nearest to (x, y).
124
+ * Uses caretRangeFromPoint with the raw click coordinates; if the point
125
+ * falls outside the text lines it clamps to the nearest (first/last) line.
126
+ * If the click is outside the contenteditable's horizontal bounds, x is
127
+ * clamped inside so caretRangeFromPoint returns the nearest line edge rather
128
+ * than falling through to an offset-0 fallback.
129
+ */
130
+ focusAtPoint(x: number, y: number): void;
131
+ /** Get the vertical center of the first or last text line in this block, or null. */
132
+ private getLineY;
133
+ /**
134
+ * Wrap characters [start, end) in a <mark> element with the given CSS class.
135
+ * Handles ranges that span across multiple inline elements (bold, italic, etc.)
136
+ * by wrapping each text node portion separately.
137
+ * Must suppress MutationObserver to avoid treating marks as user edits.
138
+ */
139
+ highlightRange(start: number, end: number, className: string): void;
140
+ /**
141
+ * Remove all <mark> elements with the given CSS class, restoring the original
142
+ * text nodes. Normalizes the parent to merge adjacent text nodes.
143
+ */
144
+ clearHighlights(className: string): void;
145
+ destroy(): void;
146
+ /** Scroll the current caret position into view if needed */
147
+ private scrollCaretIntoView;
148
+ private renderContent;
149
+ private attachListeners;
150
+ private detachListeners;
151
+ /** Connect the MutationObserver to the contentEditable element */
152
+ private connectObserver;
153
+ /** Disconnect the MutationObserver */
154
+ private disconnectObserver;
155
+ private handleBeforeInput;
156
+ private handlePaste;
157
+ private handleKeyDown;
158
+ private handleFocus;
159
+ private handleBlur;
160
+ private handleCodeLanguageChange;
161
+ private handleChecklistToggle;
162
+ /** Handle click on an agenda badge/label — open date picker for editing */
163
+ private handleAgendaEdit;
164
+ private handleHeadingTaskToggle;
165
+ private handleMutation;
166
+ /** Apply visual indentation based on metadata.indentLevel */
167
+ private applyIndentation;
168
+ /** Clear emoji picker state (called when picker is closed externally) */
169
+ clearEmojiState(): void;
170
+ /** Clear slash menu state (called when menu is closed externally) */
171
+ clearSlashState(): void;
172
+ /** Clear autocomplete state (called when menu is closed/committed externally) */
173
+ clearAutoCompleteState(): void;
174
+ /** Clear agenda picker state (called when picker is closed externally) */
175
+ clearAgendaState(): void;
176
+ /** Get the offset of the '@' that triggered the agenda picker */
177
+ getAgendaAtOffset(): number | null;
178
+ /**
179
+ * Set a one-shot attribute override for the next inserted character.
180
+ * Pass `{}` to force no attributes (preventing inheritance from adjacent spans).
181
+ */
182
+ setNextInsertAttrs(attrs: Record<string, unknown>): void;
183
+ /**
184
+ * Toggle a pending mark for format shortcuts on collapsed cursor.
185
+ * Computes the effective state (pending override or inherited) and flips it.
186
+ * If the new desired state matches what would be inherited, the pending
187
+ * override is removed (since inheritance already produces the right result).
188
+ */
189
+ togglePendingMark(attr: string, cursorOffset: number): void;
190
+ /** Get the current pending marks (for UI feedback), or null if none. */
191
+ getPendingMarks(): Record<string, boolean> | null;
192
+ /** Clear all pending marks. */
193
+ clearPendingMarks(): void;
194
+ /**
195
+ * Called by selectionchange handler. Clears pending marks unless the
196
+ * clear was suppressed (e.g. because we just inserted text).
197
+ */
198
+ selectionDidChange(): void;
199
+ /** Get current cursor offset within the block's text content */
200
+ getCursorOffset(): number | null;
201
+ /** Set cursor to a specific offset */
202
+ private setCursorOffset;
203
+ private getSelectionRange;
204
+ private findTextPosition;
205
+ }
@@ -0,0 +1,2 @@
1
+ export { BlockController } from "./block-controller";
2
+ export type { BlockView } from "./types";
@@ -0,0 +1,15 @@
1
+ /** Interface for interacting with a mounted block */
2
+ export interface BlockView {
3
+ /** The block's unique ID */
4
+ readonly blockId: string;
5
+ /** The outer container element */
6
+ readonly container: HTMLElement;
7
+ /** The contenteditable element (null for non-text blocks) */
8
+ readonly contentEditable: HTMLElement | null;
9
+ /** Whether the block is currently mounted in the DOM */
10
+ readonly mounted: boolean;
11
+ /** Focus this block, optionally at a specific offset */
12
+ focus(offset?: number): void;
13
+ /** Unmount and clean up */
14
+ destroy(): void;
15
+ }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Block action menu: a ⊞ trigger button that appears on hover and opens
3
+ * a context menu of block-level actions (convert, delete, copy link, etc.).
4
+ */
5
+ export interface BlockActionMenuEntry {
6
+ key: string;
7
+ label: string;
8
+ icon?: string | ((container: HTMLElement) => void);
9
+ danger?: boolean;
10
+ section?: "convert" | "action" | "task" | "danger";
11
+ disabled?: boolean;
12
+ /** Dynamic disabled check — called when menu opens with the target block IDs. */
13
+ isDisabled?: (blockIds: string[]) => boolean;
14
+ hint?: string;
15
+ commandName?: string;
16
+ onAction: (blockIds: string[]) => void;
17
+ }
18
+ export declare class BlockActionMenu {
19
+ private editorRoot;
20
+ private gutterEl;
21
+ private btn;
22
+ private menu;
23
+ private getTargetBlockIds;
24
+ private getMenuItems;
25
+ private hoveredBlockEl;
26
+ private menuOpen;
27
+ private _dragActive;
28
+ /** Called when the action menu opens/closes — used by editor to hide drag handle */
29
+ onOpen: (() => void) | null;
30
+ onClose: (() => void) | null;
31
+ private keyHandler;
32
+ private dismissHandle;
33
+ private scrollHandler;
34
+ private shouldShowForBlock;
35
+ constructor(editorRoot: HTMLElement, gutterEl: HTMLElement, getTargetBlockIds: () => string[], getMenuItems: () => BlockActionMenuEntry[], options?: {
36
+ shouldShowForBlock?: (blockId: string) => boolean;
37
+ });
38
+ /** The block ID the trigger button is currently associated with */
39
+ get hoveredBlockId(): string | null;
40
+ /** Hide the button and close menu if open (e.g. when user starts typing or dragging) */
41
+ hide(): void;
42
+ /** Called by the editor when a block drag starts — suppresses the action button */
43
+ notifyDragStart(): void;
44
+ /** Called by the editor when a block drag ends */
45
+ notifyDragEnd(): void;
46
+ destroy(): void;
47
+ private attachListeners;
48
+ private handleTouchStart;
49
+ private handleMouseMove;
50
+ private showBtn;
51
+ private hideBtn;
52
+ private findBlockAtY;
53
+ private handleMouseLeave;
54
+ private handleBtnLeave;
55
+ private positionBtn;
56
+ private handleBtnClick;
57
+ /** Touch equivalent of click for the action button */
58
+ private handleBtnTouchEnd;
59
+ private openMenu;
60
+ closeMenu(): void;
61
+ private toMenuItems;
62
+ }
@@ -0,0 +1,2 @@
1
+ export { BlockActionMenu } from "./block-action-menu";
2
+ export type { BlockActionMenuEntry } from "./block-action-menu";
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Right-click context menu for blocks.
3
+ *
4
+ * Shows clipboard actions (Copy, Cut, Paste) and extensible action items
5
+ * when the user right-clicks on a block. Reuses the shared ContextMenu
6
+ * component for rendering and keyboard navigation.
7
+ */
8
+ export interface BlockContextMenuEntry {
9
+ key: string;
10
+ label: string;
11
+ icon?: string | ((container: HTMLElement) => void);
12
+ danger?: boolean;
13
+ section?: string;
14
+ disabled?: boolean;
15
+ onAction: (blockIds: string[]) => void;
16
+ }
17
+ export declare class BlockContextMenu {
18
+ private editorRoot;
19
+ private menu;
20
+ private getMenuItems;
21
+ private resolveTargetBlockIds;
22
+ private menuOpen;
23
+ private keyHandler;
24
+ private dismissHandle;
25
+ private scrollHandler;
26
+ private currentBlockIds;
27
+ constructor(editorRoot: HTMLElement, resolveTargetBlockIds: (e: MouseEvent) => string[], getMenuItems: (blockIds: string[]) => BlockContextMenuEntry[]);
28
+ get isOpen(): boolean;
29
+ close(): void;
30
+ destroy(): void;
31
+ private handleContextMenu;
32
+ private toMenuItems;
33
+ }
@@ -0,0 +1,2 @@
1
+ export { BlockContextMenu } from "./block-context-menu";
2
+ export type { BlockContextMenuEntry } from "./block-context-menu";